Error Codes
All API errors return a consistent JSON format:
{ "error": "Human-readable error message", "code": "ERROR_CODE", "details": {}}The details field is optional and provides additional context for some error types.
Error codes
| Code | HTTP Status | Description |
|---|---|---|
VALIDATION_ERROR | 400 | Invalid request body, missing required fields, or invalid key format |
UNAUTHORIZED | 401 | Missing, invalid, or expired Bearer token |
FORBIDDEN | 403 | Token lacks required permissions |
NOT_FOUND | 404 | Resource does not exist |
METHOD_NOT_ALLOWED | 405 | HTTP method not supported for this endpoint |
CONFLICT | 409 | Resource already exists, or version conflict on update |
PAYLOAD_TOO_LARGE | 413 | Request body exceeds 1MB limit |
RATE_LIMITED | 429 | Rate limit exceeded |
INTERNAL_ERROR | 500 | Unexpected server error |
Examples
Validation error
curl -X POST \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "enabled": true }' \ "https://edgeflags.net/api/v1/flags"{ "error": "Missing required fields: key, enabled", "code": "VALIDATION_ERROR"}Invalid key format
curl -X POST \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "key": "INVALID KEY!", "enabled": true }' \ "https://edgeflags.net/api/v1/flags"{ "error": "Invalid key format: must match [a-z0-9][a-z0-9._-]{0,127}", "code": "VALIDATION_ERROR"}Unauthorized
curl "https://edgeflags.net/api/v1/flags"{ "error": "Unauthorized", "code": "UNAUTHORIZED"}Forbidden
{ "error": "Insufficient permissions", "code": "FORBIDDEN"}For admin-only endpoints:
{ "error": "Admin permission required", "code": "FORBIDDEN"}Not found
{ "error": "Flag not found", "code": "NOT_FOUND"}Conflict — duplicate key
{ "error": "Flag already exists", "code": "CONFLICT"}Conflict — version mismatch
When using If-Match for optimistic concurrency:
{ "error": "Version conflict", "code": "CONFLICT", "details": { "current_version": 3 }}Rate limited
{ "error": "Rate limit exceeded", "code": "RATE_LIMITED"}Rate-limited responses include these headers:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests per minute |
X-RateLimit-Remaining | Remaining requests in current window |
X-RateLimit-Reset | Unix timestamp (ms) when the window resets |
Retry-After | Seconds until the window resets |
Payload too large
Returned when Content-Length exceeds 1MB for POST/PUT requests:
{ "error": "Request body too large", "code": "PAYLOAD_TOO_LARGE"}Response headers
Every response includes:
| Header | Description |
|---|---|
X-Request-Id | Unique UUID for the request (useful for debugging) |
X-Content-Type-Options | nosniff |
X-Frame-Options | DENY |
Referrer-Policy | strict-origin-when-cross-origin |
Strict-Transport-Security | max-age=31536000; includeSubDomains |