Adrata Developers
Errors
Errors use RFC 7807 problem details with request IDs, docs URLs, retryability, and field errors.
{
"type": "https://adrata.com/developers/errors#VALIDATION_ERROR",
"title": "Unprocessable Entity",
"status": 422,
"detail": "One or more fields are invalid.",
"instance": "/companies",
"request_id": "req_01J...",
"error_code": "VALIDATION_ERROR",
"docs_url": "https://adrata.com/developers/errors",
"retryable": false,
"field_errors": [{ "field": "domain", "message": "must be a valid domain", "code": "invalid_format" }]
}Error codes
Dispatch on error_code, not on detail — the code is stable, the message is not. The type URI is this page with the code as its fragment, so every row below is directly linkable: #RATE_LIMITED lands on the rate-limit row.
| Code | Status | Retry | Meaning |
|---|---|---|---|
| INVALID_REQUEST | 400 | No | The request was malformed or a parameter was rejected. `detail` names the problem. |
| UNSUPPORTED_VERSION | 400 | No | The `Adrata-Version` header names a version that is not supported. Omit the header to get the latest stable version. |
| ROUTE_NOT_FOUND | 400 / 404 | No | No route matches the path. Under `/api/*` this is 400 rather than 404, because CloudFront rewrites origin 404s into the web app shell. |
| UNAUTHORIZED | 401 | No | Credentials are missing, or the presented credentials are not valid for this workspace. |
| AUTHENTICATION_ERROR | 401 | No | The bearer token could not be verified — expired, malformed, or signed by an unknown key. Refresh and retry once. |
| FORBIDDEN | 403 | No | Authenticated, but not permitted to perform this action on this resource. |
| insufficient_scope | 403 | No | The token is valid but lacks a required scope. `detail` lists the scopes the route requires. Lowercase by RFC 6750 convention. |
| RESOURCE_NOT_FOUND | 404 | No | The resource does not exist, or is not visible to this workspace. The two are deliberately indistinguishable. |
| CONFLICT | 409 | No | The request conflicts with current state — a duplicate unique key, or a concurrent write. Re-read before retrying. |
| VALIDATION_ERROR | 422 | No | The request parsed but failed validation. `field_errors` carries the per-field reasons; surface them rather than the summary. |
| RATE_LIMITED | 429 | Yes | Rate limit exceeded. Wait for `Retry-After`, then retry with exponential backoff and jitter. |
| USAGE_LIMIT_EXCEEDED | 429 | Yes | A plan usage limit is exhausted, not a per-second rate limit. `Retry-After` reflects when the allowance resets, which may be hours. |
| REQUEST_FAILED | 4xx | No | A client error with no more specific code. Treat as non-retryable and log `request_id`. |
| INTERNAL_ERROR | 500 | Yes | An unhandled server error. Retry with backoff; if it persists, send us `request_id`. |
| DATABASE_ERROR | 500 | Yes | A database call failed. Transient in most cases — retry with backoff. |
| CACHE_ERROR | 500 | Yes | A cache call failed. Transient — retry with backoff. |
| SERIALIZATION_ERROR | 500 | Yes | The response could not be serialized. Retry once, then report `request_id`. |
| NOT_IMPLEMENTED | 501 | No | The endpoint exists but the behaviour is not available. Retrying can never succeed, which is why this 5xx is not retryable. |
| SERVICE_UNAVAILABLE | 503 | Yes | A dependency is unavailable. Retry with backoff. |
| STORAGE_UNAVAILABLE | 503 | Yes | Object storage is unavailable, so an upload or download could not be served. Retry with backoff. |
Handling
SDKs raise `AuthError`, `RateLimitError`, `ConflictError`, and `AdrataError`. Log `request_id` and preserve `field_errors` for user-facing validation.
Retry rules
Retry 429s after `Retry-After`, retry explicitly retryable 5xx responses with exponential backoff, and never retry non-idempotent writes unless you sent an `Idempotency-Key`.
The retryable member is authoritative and does not follow the status class: NOT_IMPLEMENTED is a 5xx that will never succeed on retry, so it is sent as retryable: false. Honour the field rather than inferring from the status.