Errors and limits
The error envelopes of each API surface, the common error codes, rate limits, account lockout and the platform caps.
Obexal has three API surfaces, and each uses the error convention its protocol expects. This page lists the shapes, the stable codes, and every enforced limit.
Error formats
JSON API (everything under /v1/): a single envelope on every non-2xx response. code is a stable machine string; message is human-readable and may change.
{"error": {"code": "rate_limited", "message": "too many requests"}}OAuth endpoints (/oauth/*): the RFC 6749 shape, with snake_case fields at the top level.
{"error": "invalid_grant", "error_description": "code invalide, expiré ou déjà utilisé"}On /oauth/authorize, errors are redirected to the client as error, error_description and state query parameters once the redirect_uri has been validated; before that, the response is a direct 400 (never an open redirect). A failed client_secret_basic authentication returns 401 with a WWW-Authenticate: Basic header. /oauth/userinfo returns 401 with a WWW-Authenticate: Bearer error="invalid_token" header (its body uses the JSON envelope).
SCIM (/scim/v2/*): the standard SCIM error body, see the SCIM reference.
{"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"], "status": "404", "detail": "utilisateur introuvable"}Common API error codes
| Code | HTTP status | Meaning |
|---|---|---|
invalid_request | 400 | Malformed body or invalid parameters |
unauthenticated | 401 | Session missing, expired or revoked |
invalid_credentials | 401 | Wrong e-mail or password |
unauthorized | 401 | Admin API token missing, invalid, expired or revoked |
forbidden | 403 | Permission missing (RBAC deny-by-default, or outside the token's scopes) |
csrf_failed | 403 | Missing or mismatched X-CSRF-Token header on a cookie-authenticated mutation |
step_up_required | 403 | Sensitive action requires a fresh second-factor check (POST /v1/mfa/step-up) |
mfa_enrollment_required | 403 | The tenant requires MFA and no factor is enrolled (strict mode) |
not_found | 404 | Resource absent from your tenant |
conflict | 409 | Resource already exists |
payload_too_large | 413 | Request body over the limit |
rate_limited | 429 | Too many attempts, retry later |
account_locked | 429 | Account locked after repeated sign-in failures |
internal_error | 500 | Unexpected server error |
Feature-specific codes also exist (for example slug_taken, email_taken, invalid_invite, password_expired): treat the HTTP status as the class and the code as the precise reason.
OAuth error codes
| Code | Typical cause |
|---|---|
invalid_request | Missing or malformed parameter (missing code, missing code_verifier, unsupported token type URN) |
invalid_client | Unknown client, wrong or expired client_secret (401 when presented via Basic) |
invalid_grant | Code or refresh token invalid, expired, replayed or bound to another client; agent disabled or expired; suspended tenant |
unauthorized_client | Grant type not enabled for this client |
unsupported_grant_type | Unknown grant_type value |
unsupported_response_type | Anything other than code |
invalid_scope | Requested scope outside the allowed set (client scopes, subject scopes, agent ceiling or user authorization) |
access_denied | User or tenant boundary refused the request |
login_required, consent_required | Interaction needed but prompt=none forbids it |
invalid_target | resource is not an absolute URI or not in the agent's allowed audiences (RFC 8693/8707) |
invalid_dpop_proof | Missing, malformed or replayed DPoP proof |
server_error | Unexpected server error |
Rate limits
Sensitive flows are throttled with fixed windows, keyed per e-mail and per client IP independently. Two families exist; their default values are in the configuration reference:
| Family | Covered flows |
|---|---|
Sign-in (RATE_LIMIT_LOGIN_*) | Password sign-in, passwordless, MFA challenges and resends, passkey sign-in |
Account (RATE_LIMIT_SIGNUP_*) | Sign-up, organization creation, password reset requests |
E-mail verification, e-mail change, invitation and social sign-in flows are throttled by the same limiters. Exceeding a limit returns 429 rate_limited and the event is recorded in the audit log.
Account lockout
Independently of IP-based limits, repeated password failures lock the account itself, which resists IP rotation (LOGIN_LOCKOUT_MAX, LOGIN_LOCKOUT_WINDOW, LOGIN_LOCKOUT_DURATION); the default values are in the configuration reference. The response is 429 account_locked. An administrator can lift the lock with POST /v1/admin/users/unlock.
One-time codes have their own caps: e-mail OTPs allow 5 attempts and expire after 10 minutes; MFA challenges allow 5 attempts and expire after 5 minutes (defaults).
Platform caps
| Limit | Value |
|---|---|
| Request body (JSON APIs) | 1 MiB |
List pagination (?limit) | Default 100, maximum 500 |
Audit export (GET /v1/admin/audit/export) | 10000 events per call; the response sets truncated: true beyond |
Audit search query (?q) | 200 characters |
| Access policy versions kept | The 50 most recent |
| Policy simulation sample | Sign-ins from the last 30 days, up to 1000 distinct IPs |
| Countries per conditional access rule | 250 |
| SCIM list page | 200 resources |
These caps are enforced server side; clients should paginate rather than raise them.