Error codes
The standard error envelope and stable machine-readable error codes.
Every error uses the same envelope, with a stable machine-readable code:
{
"error": {
"code": "insufficient_scope",
"message": "This request requires the 'clips:read' scope.",
"details": { "required_scope": "clips:read" }
},
"request_id": "req_..."
}Branch on error.code, never on the HTTP status alone and never on message -
messages are written for humans and will change. Log request_id and include it
when contacting support; it identifies the exact request in our logs.
details is optional and its contents vary by code. For validation_error it
usually names the offending field.
Codes
| HTTP | code | Meaning |
|---|---|---|
| 400 | validation_error | Invalid request body or parameters |
| 401 | missing_api_key | No API key was provided |
| 401 | invalid_api_key | Key not recognized or malformed |
| 401 | key_revoked | Key or its app has been revoked or disabled |
| 401 | key_expired | Key has passed its expiry |
| 401 | invalid_signature | HMAC request signature failed (signed mode) |
| 403 | insufficient_scope | Key lacks the scope this operation requires |
| 403 | ip_not_allowed | Source IP is not in the app's allowlist |
| 404 | not_found | Resource does not exist, or is not in your organization |
| 409 | idempotency_conflict | An Idempotency-Key was reused with a different body |
| 429 | rate_limited | Per-key rate limit exceeded. See Retry-After |
| 500 | internal_error | Unexpected server error |
| 501 | not_implemented | Reserved. No current endpoint returns this |
Codes worth handling explicitly
not_found hides authorization. A resource that exists but belongs to
another organization returns 404, not 403. This is deliberate - it prevents
probing for ids - so do not read 404 as proof that something never existed.
insufficient_scope is not retryable. The key's scopes are fixed at issue
time and are intersected with its parent app's scopes, so a key can lack a scope
that the app itself has. Fix it by issuing a new key, not by retrying.
idempotency_conflict means the bodies differed. Reusing a key with the
same body is the supported path and replays the original response with
Idempotent-Replayed: true. You only get 409 when the key matches but the
body does not. Generate a fresh key per logical operation - never reuse one
across different requests.
validation_error is not always 400. External storage endpoints return it
with 403 when the organization has no external storage configured, and
upstream failures can surface it with other 4xx statuses. Handle the code, and
treat the status as advisory.
Which errors can any endpoint return
Assume every operation can return 401, 403 and 429, regardless of what it
does - these come from authentication, scope checking and rate limiting, all of
which run before the handler. Every POST that accepts Idempotency-Key can
additionally return 409.
Retrying
| Code | Retry? |
|---|---|
rate_limited | Yes, after Retry-After seconds |
internal_error | Yes, with exponential backoff |
idempotency_conflict | No - fix the key or the body |
insufficient_scope, ip_not_allowed | No - a configuration change is needed |
validation_error, not_found | No - the request itself is wrong |
key_revoked, key_expired, invalid_api_key | No - issue a new key |
When retrying anything that writes, send an Idempotency-Key so a retry after a
timeout cannot duplicate the work. See
Rate limiting for backoff guidance.
Updated about 2 months ago
