Errors
The API uses conventional HTTP status codes and returns a consistent JSON envelope on every error:
{
"code": "unauthorized",
"message": "Unauthorized",
"details": "The user is not authorized to perform this action"
}
coderequiredA stable, machine-readable error code. Branch on this, not on message.
messagerequiredA short human-readable summary, safe to surface to users.
detailsExtra context for debugging. Wording may change — don't parse it.
Status codes
| Status | Meaning |
|---|---|
200 | Request succeeded. |
400 | Malformed request — missing fields, wrong types, failed validation. |
401 | Missing, expired or invalid access token. Refresh and retry once. |
403 | Authenticated, but not allowed to perform this action. |
404 | The resource doesn't exist or isn't visible to this user. |
423 | Account locked or deactivated. The body includes a can_reactivate flag. |
429 | Rate limited. See Rate limits — the body includes retry_after. |
500 | Something went wrong on our side. Safe to retry with backoff. |
503 | Temporarily unavailable — the body's code is service_unavailable. Retry with backoff. |
Locked accounts
Deactivated or locked accounts return 423 with a dedicated body:
{
"reason": "account_deactivated",
"can_reactivate": true
}
When can_reactivate is true, POST /v1/auth/reactivate restores access;
otherwise direct the user to support.
Retry guidance
- Never blind-retry
4xx(except429after waitingretry_after) — the request itself is wrong. - Retry
5xxwith exponential backoff and jitter. - On endpoints that accept an idempotency key, always retry with the same key so the operation can't double-apply.