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
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_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-formatdocs.moda.app/api-reference/errors— the catalog every envelope’sdoc_urlpoints at