Rate limits & errors
Canonical error codes, HTTP status mapping, and remediation.
View as Markdown →All control plane and gateway responses use the standard envelope from @ozma_app/core. Every response includes meta.request_id — cite it in support requests.
{ "ok": false, "error": { "code": "insufficient_budget", "message": "…", "details": {} }, "meta": { "request_id": "req_01…" } }
Canonical enum: packages/core/src/envelope.ts.
Rate limits
| Surface | Limit | Key |
|---|---|---|
Control plane (api.ozma.app) |
~120 requests / 60s | Per API-key prefix or Cloudflare IP (KV bucket) |
Gateway (gateway.ozma.app) |
~300 requests / min | Per API-key prefix (GATEWAY_RATE_LIMIT_PER_MIN) |
Exceeded limits return 429 rate_limited. Login OTP and signup abuse paths have additional per-email/IP caps.
Error code reference
| Code | HTTP | Surface | Meaning | Remediation |
|---|---|---|---|---|
unauthenticated |
401 | Both | Missing, invalid, or expired auth | Provide valid Bearer ozma_live_… (gateway) or key/session (API) |
forbidden |
403 | Both | Insufficient scope or policy block | Verify email for elevated scopes; check self-dealing; confirm org membership |
not_found |
404 | Both | Resource or route missing | Confirm slug/id; check API is live not draft |
conflict |
409 | API | Duplicate or state conflict | e.g. slug taken — choose another |
validation_failed |
400 / 413 | Both | Invalid input or oversize body | Fix request schema; reduce body size (413 on gateway) |
insufficient_budget |
402 | Gateway | Key lifetime budget exceeded | Raise budget_cents or use another key |
per_call_limit_exceeded |
402 | Gateway | Call price > per_call_max_cents |
Increase per-call max or cheaper endpoint |
daily_cap_exceeded |
402 | Gateway | Daily cap exceeded (UTC day) | Raise daily_cap_cents or wait for UTC midnight reset |
rate_limited |
429 | Both | Ozma rate limit | Exponential backoff; reduce request rate |
upstream_rate_limited |
429 | Gateway | Provider returned HTTP 429 | Backoff; honor upstream Retry-After in details |
upstream_error |
502 | Gateway | Provider error, timeout, bad JSON | Fix upstream path/params; inspect details.status |
payment_required |
402 | Gateway / API | Suspended org or prepaid wallet empty | Pay invoice or add payment method at dashboard |
precondition_failed |
412 | API | Wrong document/API state for action | e.g. activate only draft docs; edit only draft/live APIs |
terms_acceptance_required |
428 | Gateway | Required provider terms not accepted | Session accept via POST …/terms/accept or ozma terms accept |
not_configured |
503 | API | Optional subsystem unavailable | e.g. avatar upload without R2 — use avatar_url PATCH |
internal_error |
500 / 503 | Both | Unexpected server or dependency failure | Retry; report request_id; SpendCounter outage → 503 on gateway |
Gateway-specific notes
daily_cap_exceededvsinsufficient_budget: both HTTP 402; readerror.code— daily cap is calendar UTC, lifetime budget is cumulative on the key.terms_acceptance_required: includesapproval_url,snapshot_url,document_idindetails.- Idempotency replay: success responses may set
meta.replayed: true; not an error.