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 visible
  • api_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.