Error catalog
The single error envelope and every stable error code.
Every error response uses a single envelope:
{ "error": { "code": "…", "message": "…" } }code is a stable, machine-readable identifier; message is a human-readable
description and may change. Branch on code (and the HTTP status), never on
message.
Codes
code | HTTP | When it happens |
|---|---|---|
unauthorized | 401 | Missing, malformed, unknown, expired, or revoked API key, or a key whose creator is no longer an active member of its team. |
forbidden | 403 | The key lacks the scope the endpoint requires (for example, a read-only key attempted a write). |
bad_request | 400 | Request body or query failed validation; or a cursor that does not match the requested sort/order. |
not_found | 404 | The resource does not exist, or is not in the key's team. |
limit_reached | 402 | The team is at its plan's limit for the resource being created. Carries the billing fields. |
feature_unavailable | 403 | The team's plan does not include a capability the request needs, api_access included. Carries the billing fields. |
conflict | 409 | A request with the same Idempotency-Key is still in flight; retry shortly. |
key_limit_reached | 409 | The team already holds the maximum number of active API keys. |
idempotency_key_conflict | 422 | An Idempotency-Key was reused with a different request body. |
idempotency_authorization_changed | 409 | Your authorization narrowed since the original request, so its stored response cannot be replayed. Retry without the key, or with a new one. |
rate_limited | 429 | A per-key or per-IP rate limit was exceeded. The response includes Retry-After and RateLimit-* headers. |
Billing refusals
limit_reached and feature_unavailable add machine-readable fields to the envelope so a
client can say what would lift the refusal instead of only reporting it:
{
"error": {
"code": "limit_reached",
"message": "You've reached your plan's limit for cvs.",
"resource": "cvs",
"limit": 10,
"current": 10,
"plan": "solo",
"upgrade_to": "team"
}
}{
"error": {
"code": "feature_unavailable",
"message": "Your plan doesn't include API access.",
"feature": "api_access",
"plan": "free",
"upgrade_to": "solo"
}
}| Field | On | Meaning |
|---|---|---|
resource | 402 | The exhausted ceiling. storage_bytes reports bytes; invoices_per_month and trips_per_month are per-calendar-month windows; the rest are row counts. |
limit | 402 | The plan's ceiling for resource. |
current | 402 | The team's usage against it, in the same unit as limit. |
feature | 403 | The capability the plan does not include. |
plan | both | The plan in force. |
upgrade_to | both | The tier that lifts this refusal, or null when none does. On a 402 it is the lowest tier whose ceiling for resource accommodates the request, which is not always the adjacent tier: a team can sit above the next tier's ceiling too, and some ceilings are the same on neighboring tiers. Read it as the answer, not as "one step up". |
The two are different situations for the customer: limit_reached means the plan includes the
resource and the team has used it all, so deleting something also resolves it; feature_unavailable
means the plan never included the capability, so only an upgrade does.
Notes
404, not403, for cross-team resources. An id that belongs to another team returns404, so the API never reveals whether an id exists elsewhere.- Validation failures are
400 bad_request. Themessagesummarizes the invalid field(s); do not parse it; treat it as human-facing.
For GraphQL, errors appear in the errors array with the same codes under
extensions.code. See the GraphQL guide.