Errors
LumoAuth uses standard HTTP response codes and returns error bodies in a consistent format across all APIs.
HTTP Status Codes
| Code | Meaning | Action |
|---|---|---|
200 OK | Request succeeded. | — |
201 Created | Resource was created. | — |
204 No Content | Succeeded with no response body (common for DELETE). | — |
400 Bad Request | Malformed request or invalid parameters. | Fix the request |
401 Unauthorized | Missing or invalid credentials/token. | Refresh token or re-authenticate |
403 Forbidden | Authenticated but not authorized. | Check scopes and permissions |
404 Not Found | Resource does not exist. | Check the endpoint URL and org ID |
409 Conflict | Resource already exists (e.g. duplicate email). | Read or update instead of create |
422 Unprocessable Entity | Validation errors on the submitted data. | See details field |
429 Too Many Requests | Rate limit exceeded. | Back off — check Retry-After header |
500 Internal Server Error | Unexpected server error. | Retry with exponential backoff |
503 Service Unavailable | Temporarily unavailable (maintenance, overload). | Retry later |
Error Response Format
Generic API Error
{
"error": "validation_error",
"message": "Validation failed",
"status": 400,
"details": {
"email": "Invalid email format",
"password": "Must be at least 8 characters"
}
}
OAuth Error (RFC 6749)
{
"error": "invalid_client",
"error_description": "Client authentication failed: unknown client_id",
"error_uri": "https://docs.lumoauth.dev/errors#invalid_client"
}
Common API Error Codes
error | Status | Description |
|---|---|---|
validation_error | 400 / 422 | One or more request fields failed validation. See details. |
invalid_request | 400 | Missing or malformed request parameter. |
invalid_client | 401 | OAuth client authentication failed. |
invalid_token | 401 | Access token is missing, expired, or invalid. |
unauthorized | 401 | No credentials provided. |
access_denied | 403 | Authenticated but insufficient privileges. |
insufficient_scope | 403 | Token scope does not cover this resource. |
not_found | 404 | Resource does not exist. |
conflict | 409 | Resource already exists. |
rate_limit_exceeded | 429 | Too many requests. Retry after Retry-After seconds. |
server_error | 500 | Unexpected internal error. |
temporarily_unavailable | 503 | Service temporarily unavailable. |
OAuth 2.0 Errors (RFC 6749)
Returned by the authorization, token, and introspection endpoints. RFC 6749 — OAuth 2.0 Authorization Framework defines the core OAuth protocol (authorize and token endpoints, grant types, and the error body shape shown below).
error | Status | Common cause |
|---|---|---|
invalid_request | 400 | Missing grant_type, invalid JSON body, unsupported content type |
invalid_client | 401 | Wrong client_id, wrong client_secret, client not in organization |
invalid_grant | 400 | Authorization code expired or reused, refresh token revoked |
unauthorized_client | 401 | Client is not authorized for this grant type |
unsupported_grant_type | 400 | Grant type typo or not enabled |
invalid_scope | 400 | Scope not registered or exceeds client's allowed scopes |
access_denied | 403 | User denied consent, or policy blocked the request |
server_error | 500 | Internal server error |
temporarily_unavailable | 503 | Maintenance or overload |
Authorization API Errors
error | Status | Description |
|---|---|---|
MISSING_PERMISSION | 400 | Required field permission is missing |
INVALID_PERMISSIONS | 400 | permissions must be a non-empty array |
MISSING_FIELDS | 400 | Zanzibar check missing object, relation, or user |
CHECK_FAILED | 500 | Permission check failed due to server error |
ZANZIBAR_CHECK_FAILED | 400 | Invalid tuple format or unknown namespace |
LIST_FAILED | 500 | Failed to retrieve user permissions |
Agent & Token Exchange Errors
error | Status | Description |
|---|---|---|
invalid_target | 400 | Token exchange target (audience) is invalid |
unsupported_token_type | 400 | subject_token_type or actor_token_type not supported |
delegation_not_allowed | 403 | Client is not configured to allow token exchange |
budget_exceeded | 429 | Agent has exceeded its configured budget limits |
capability_denied | 403 | Agent lacks the required capability |
workload_identity_failed | 401 | Failed to verify workload identity token |
Dynamic Client Registration Errors
error | Status | Description |
|---|---|---|
invalid_redirect_uri | 400 | One or more redirect URIs use a non-allowed scheme |
registration_not_allowed | 403 | Dynamic registration is disabled for this organization |
invalid_client_metadata | 400 | Client metadata is invalid or unsupported |
Validation Errors
When a validation_error is returned, the details object maps each invalid field to a human-readable message:
422 Validation Error
{
"error": "validation_error",
"message": "Validation failed",
"status": 422,
"details": {
"name": "Name is required",
"redirectUris[0]": "Must be a valid HTTPS URL"
}
}
Rate Limit Errors
429 Rate Limit Exceeded
{
"error": "rate_limit_exceeded",
"message": "Too many requests. Please retry after 30 seconds.",
"status": 429,
"retryAfter": 30
}
The response also includes the Retry-After HTTP header with the number of seconds to wait.
Error-handling example
import requests, time
class LumoAuthClient:
MAX_RETRIES = 3
def call_api(self, method, url, **kwargs):
for attempt in range(self.MAX_RETRIES):
response = requests.request(method, url, **kwargs)
if response.ok:
return response.json()
if response.status_code == 401:
self.refresh_token()
continue
if response.status_code == 429:
time.sleep(int(response.headers.get("Retry-After", 60)))
continue
if response.status_code >= 500:
time.sleep(2 ** attempt)
continue
err = response.json()
raise LumoAuthError(code=err.get("error"), message=err.get("message") or err.get("error_description"))
raise LumoAuthError(code="max_retries", message="Max retries exceeded")