General error envelope
For most errors (400, 401, 403, 404, 409, 500):
Some errors include additional context (e.g., the conflicting resource ID on a
409) under a context or meta field — these are documented per endpoint.
Validation error envelope (422)
When the API rejects a request because field-level validation failed, the response includes aviolations array enumerating each failed field:
violations[] to surface each error next to the right form field. code is a stable Symfony validator UUID — useful if you want to localize the message without parsing the English text.
Status codes
400 Bad Request — fix the request
400 Bad Request — fix the request
The request is malformed at the HTTP/JSON level: invalid JSON syntax, wrong Retry? No. The same request will fail again. Inspect
Content-Type, missing required query parameter, unknown enum value.detail and fix.403 Forbidden — no permission for this resource
403 Forbidden — no permission for this resource
You’re authenticated, but your user (or the API token’s owner) lacks the permission required for this action.Common reasons:Retry? No. Have an admin grant the missing permission, or use a different token.
- The token is read-only and you sent a write
- The user has
HR_LOCALbut the resource is in a different local center - The endpoint requires
ADMIN_TENANTand the user isHR_LOCAL
404 Not Found — resource doesn't exist (or is in another tenant)
404 Not Found — resource doesn't exist (or is in another tenant)
The resource does not exist, OR exists but belongs to a different tenant than the one your auth is scoped to. Orgo returns Retry? No. Verify the ID/URL and the tenant host.
404 (not 403) in the second case so the absence of the resource is indistinguishable from “exists but forbidden” — preventing tenant-existence leaks.409 Conflict — optimistic-lock collision or illegal state
409 Conflict — optimistic-lock collision or illegal state
The request would create a conflict with the current state of the resource. Two common causes:Retry? Sometimes. For optimistic-lock collisions: refetch, reconcile your changes, retry. For illegal state transitions: no — fix the workflow. For duplicate IDs: no — use a different identifier or update the existing resource.
- Optimistic locking: another writer updated the record between your read and your write. Refetch and retry.
- Illegal state transition: e.g., trying to mark a contract as signed twice, or transition an adhesion to a state that is not reachable from its current state.
- Duplicate identifier: e.g., creating a resource whose external ID already exists.
422 Unprocessable Entity — validation failed
422 Unprocessable Entity — validation failed
The request is well-formed but one or more fields failed validation (required, format, length, custom rules).Retry? No. Iterate
violations[], surface each error next to the right form field, let the user correct, and resubmit.429 Too Many Requests — back off
429 Too Many Requests — back off
You hit the per-endpoint rate limit (usually 20/min on write-heavy endpoints like login or send-email).The response includes a
Retry-After header (seconds).Retry? Yes, after Retry-After. See Rate limits.500 Internal Server Error — server-side bug or transient outage
500 Internal Server Error — server-side bug or transient outage
Something went wrong inside Orgo. The
detail may be generic (“An error occurred”) to avoid leaking internals.Retry? Yes, with exponential backoff. If a 500 reproduces on the same call repeatedly, contact support with the request ID from the response headers.502 / 503 / 504 — transient infrastructure error
502 / 503 / 504 — transient infrastructure error
Load balancer, application server, or upstream dependency is unreachable.Retry? Yes, with exponential backoff (start ~1s, double up to ~30s, give up after ~5 minutes).
Retry strategy
A safe default for any integration:400,401,403,404,422— retrying produces the same failure409from illegal state transitions or duplicate IDs — retrying still conflicts409from optimistic locks — refetch first, then retry once (not in a tight loop)
Logging tips
Save the request ID from response headers (when present) — it lets support trace the exact request in our logs. The body’sstatus field mirrors the HTTP status, so you can log just the response body and recover the status from JSON without needing two fields.
Related
- Authentication — what 401 and 403 mean per auth method
- Tenancy — why a 404 may actually be a tenant mismatch
- Rate limits — how to avoid 429 in the first place

