Errors
Every non-2xx response carries the same envelope, modeled on Stripe's error shape:
{ "error": { "type": ..., "code": ..., "message": ..., "doc_url": ..., "param": ... } }
param is only present when the error is attributable to a specific
request field. doc_url always resolves to a stable anchor on
/api/errors.
Switch on code, not message
code is the stable machine-parseable contract — adding a new code
is a CHANGELOG entry and a minor version bump; changing the meaning
of an existing code is a major version bump. message is
human-readable copy and may be reworded at any time.
Error types
Every error envelope's type is one of seven:
authentication_error— token missing, malformed, revoked (token_not_found,token_revoked)permission_error— token authentic but lacks scope or capability (token_scope_insufficient)rate_limit_error— quota exceeded (rate_limit_exceeded)idempotency_error— key reused with a different body (idempotency_conflict)validation_error(invalid_request_error) — request shape wrong (param_invalid_format,invalid_cursor)not_found_error— resource ID does not exist or is not visibleapi_error— server-side; we caused it (always paired with a request-id header for support tickets)
See also: Rate limits and Idempotency for the two error types most commonly seen in production traffic.