Skip to main content

Errors

Every error response carries a single typed envelope under an error key. Branch on type, not HTTP status.

Shape

Fields

Types (branch on these)

Selected codes worth knowing

Retry rules

When the envelope carries retryable, it wins over the type rule: false means the same request can never succeed. Never silently retry invalid_request / authentication / permission / not_found / conflict / idempotency_conflict / unprocessable — they’re deterministic.

Rate limiting

  • Surfaced as 429 rate_limited with Retry-After header (seconds).
  • Applied per API key and per endpoint (e.g. exports are individually rate-capped).
  • Slow down, don’t hammer.
  • The RateLimit-* response headers standardized in RFC 9240 are not shipped yet. Don’t read them. Use Retry-After and the error’s retry_after_ms.

Logging & support

  • Always log request_id on every error. When you email support@moda.app, include it.
  • Log type and code; don’t log message as the signal (it can change without notice).
  • Redact the API key from any log line.

Error handling template (TypeScript)

Error handling template (Python)

Common wrong guesses

  • Branching on HTTP status only. Status codes collapse types (both rate_limited and idempotency_conflict can be 409-adjacent in practice). Branch on type.
  • Parsing message. Can change without notice. Use type and code for logic; message for display only.
  • Retrying not_found / permission. Deterministic.
  • Ignoring Retry-After on 429. You’ll be back to rate-limited in seconds.
  • Expecting RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset response headers. Not shipped yet. Use Retry-After and retry_after_ms.
  • Not including request_id in support requests. Without it, logs are hard to find.

Upstream