Errors
Every error response carries a single typed envelope under anerror key. Branch on type, not HTTP status.
Shape
Fields
Types (branch on these)
Selected codes worth knowing
Retry rules
Never silently retry
invalid_request / authentication / permission / not_found / conflict / idempotency_conflict / unprocessable — they’re deterministic.
Rate limiting
- Surfaced as
429 rate_limitedwithRetry-Afterheader (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. UseRetry-Afterand the error’sretry_after_ms.
Logging & support
- Always log
request_idon every error. When you email support@moda.app, include it. - Log
typeandcode; don’t logmessageas 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_limitedandidempotency_conflictcan be 409-adjacent in practice). Branch ontype. - Parsing
message. Can change without notice. Usetypeandcodefor logic;messagefor display only. - Retrying
not_found/permission. Deterministic. - Ignoring
Retry-Afteron429. You’ll be back to rate-limited in seconds. - Expecting
RateLimit-Limit/RateLimit-Remaining/RateLimit-Resetresponse headers. Not shipped yet. UseRetry-Afterandretry_after_ms. - Not including
request_idin support requests. Without it, logs are hard to find.
Upstream
docs.moda.app/api#error-format- Per-code docs at
docs.moda.app/errors/<code>(linked from every envelope’sdoc_url)