Errors
All errors return OpenAI-shaped JSON:
{ "error": { "message": "...", "type": "...", "code": "..." } }
| HTTP | code | type | Retryable | What to do |
|---|---|---|---|---|
| Request | ||||
| 400 | invalid_json | invalid_request_error | no | The body is not valid JSON. |
| 400 | invalid_anthropic_request | invalid_request_error | no | A /v1/messages body that could not be read as an Anthropic Messages request. The message says why. |
| 400 | missing_model | invalid_request_error | no | Add "model" to the body or use a prompt that pins one. |
| 400 | unknown_model | invalid_request_error | no | Slug not in the catalogue. List models with GET /v1/models. |
| 400 | model_not_allowed | invalid_request_error | no | Workspace allowlist rejected this model. Add it under Routing → Allowed models. |
| 400 | route_not_found | invalid_request_error | no | You sent "model": "odnoga/<name>" and this workspace has no live route of that name. Create it under Workspace → Routing → Routes (an archived route can be brought back there). |
| 400 | route_missing_capability | invalid_request_error | no | None of the route's models can do what the request needs (for example images or tools). Add one that can to the route, or name a model directly. |
| 400 | auto_not_configured | invalid_request_error | no | You sent "model": "auto" but the workspace has no automatic chain. Set up the automatic chain (Workspace → Routing → Routes → Automatic chain; it offers ready-made starting points). |
| 400 | model_deprecated | invalid_request_error | no | The model is retired. The message names the retirement date and the replacement; move to it. An organisation admin can instead enable Organisation → Defaults → Retired models so the same-vendor replacement serves the old name automatically (the response then carries x-odnoga-model-fallback). |
| 400 | model_not_serving | invalid_request_error | no | The model is in the catalogue but not serving: awaiting activation, or switched off because odnoga cannot serve it reliably (for example a vendor that offers it only on another API). Nothing was charged. Pick another model — models.list lists only models that serve. Inside a fallback chain such a model is skipped instead. |
| 400 | missing_capability | invalid_request_error | no | You named a model and the request needs something this model does not offer through odnoga — tools, json_schema structured outputs, vision input… The message names it. Nothing was charged. Every capability odnoga lists for a model is measured against the vendor; choose a model that has it. |
| 400 | auto_chain_missing_capability | invalid_request_error | no | "auto" was sent, and no model in the automatic chain supports what the request needs. The message names the capability; add a model that has it to the * rule, or name a model directly. |
| 400 | unsupported_parameter / not_in_enum / below_min / above_max / wrong_type / conflicts_with / requires_flag | invalid_request_error | no | A parameter this model cannot honour and odnoga cannot safely repair — e.g. tool_choice forcing one specific tool on a model that only accepts auto. The message names the parameter and what the model accepts. Most mismatches are repaired instead (dropped, clamped or set) and reported on x-airouter-stripped / x-airouter-clamped / x-airouter-set (see Headers). |
| 400 | required_with_tools | invalid_request_error | no | The model accepts tools only at one reasoning_effort (a vendor rule — none for gpt-5.6-luna, gpt-5.6-sol and gpt-6-sol) and you set another. Leave reasoning_effort out and odnoga sets it for you, reported on x-airouter-set. |
| 400 | requires_reasoning_effort | invalid_request_error | no | Strict mode only: a sampling control (temperature, top_p, …) that this model accepts only at one reasoning_effort. In the default lenient mode the control is dropped instead and reported on x-airouter-stripped. |
| 400 | json_schema_unsupported / json_mode_unsupported / invalid_response_format / invalid_output_schema | invalid_request_error | no | The response_format (or the prompt's attached schema) can't be used with this model, or is malformed. The message says which. |
| 404 | output_schema_not_found | invalid_request_error | no | The named output schema doesn't exist in this workspace. schemas.list lists them. |
| 400 | web_search_unsupported | invalid_request_error | no | The request asks for live web access and the model cannot provide it. Pick a model that can. |
| 403 | web_search_disabled | invalid_request_error | no | Web access is off for this workspace. An admin enables it under Models → Web access. |
| 403 | residency_blocked | invalid_request_error | no | The workspace's strict data-residency setting forbids the vendor that serves this model. Pick an in-region model or change the residency policy. |
| 400 | unsupported_vendor | invalid_request_error | no | This endpoint can't be served by the model's vendor. |
| 404 | not_found | invalid_request_error | no | No such endpoint. Check the path is under https://api.odnoga.com/v1. |
Managed prompts ("prompt": {…} in the body) | ||||
| 400 | invalid_prompt | prompt_error | no | prompt.slug missing, or the prompt/variables are malformed. |
| 404 | prompt_not_found | prompt_error | no | No prompt with that slug (or that label/version) in this workspace. |
| 403 | version_pin_not_allowed | prompt_error | no | prompt.version_id with a browser-scoped key or an end-user token. Use prompt.label. |
| 400 | invalid_prompt_version / invalid_experiment_assignment | prompt_error | no | The resolved version pins a model or schema that can't be used, or its A/B experiment is misconfigured. Fix the prompt in the editor. |
| 400 | too_many_variables / variable_too_large / variables_too_large | prompt_error | no | Variables over the limits in Prompt variables. |
| Authentication and access | ||||
| 401 | invalid_api_key | invalid_request_error | no | Missing, wrong or revoked API key (or expired end-user token). Create a new one under API keys. |
| 403 | account_suspended | forbidden | no | The organisation is suspended. Nothing is served until it is reinstated; contact odnoga. |
| Money — nothing is sent to a vendor and nothing is charged | ||||
| 402 | budget_exhausted | insufficient_quota | after top-up / reset | Prepaid credit is used up, a workspace or organisation budget cap is reached, or the odnoga subscription is not active. The message says which. Top up or fix billing in Pay odnoga, raise the cap, or wait for the budget window to reset (x-ratelimit-reset, when a window exists). |
| 402 | end_user_exhausted | insufficient_quota | no | That end user's wallet is empty. Nothing was sent to a vendor and nothing was charged. Expected, not an incident — catch it and show your own top-up or upgrade screen. The message names the unit you configured. Credit the wallet with wallets.adjust, or turn off "block at zero" to meter without enforcing. |
| Idempotency (non-streamed requests with an idempotency key) | ||||
| 409 | idempotency_in_progress | invalid_request_error | yes | The first request with this key is still running. Retry after Retry-After with the same key. |
| 422 | idempotency_key_reused | invalid_request_error | no | The key was already used with a different body. Use a new key. |
| Rate and capacity limits — rejected before any vendor is contacted, never charged | ||||
| 429 | workspace_concurrency_limit | rate_limit_error | yes | This workspace's concurrent-session allocation is full. Honour Retry-After; see Concurrent sessions. |
| 429 | tenant_concurrency_limit | rate_limit_error | yes | The organisation's session pool is full. Honour Retry-After, or add sessions. |
| 429 | workspace_rpm_limit | rate_limit_error | yes | Requests-per-minute limit reached. Honour Retry-After. |
| 429 | workspace_tpm_limit | rate_limit_error | yes | Tokens-per-minute limit reached. Honour Retry-After. |
| 429 | capacity_unavailable | rate_limit_error | yes | odnoga briefly stopped admitting calls — our ceiling, not yours. Honour Retry-After. |
| Upstream and platform | ||||
| 503 | vendor_unavailable | service_unavailable | wait | A dependency inside odnoga is failing. The message is deliberately generic and never carries the provider's own text: a supplier-account problem is ours, not yours. x-odnoga-ticket carries the ticket already open with the odnoga team at highest priority. Retry shortly or send another model. |
| 4xx (as sent by the vendor) | the HTTP status, e.g. "400" | upstream_error | no | The vendor rejected something about your request (a bad parameter, an oversized image). The vendor's own message is passed through so you can fix it. Anything about odnoga's account with the vendor is never passed through — that becomes 503 vendor_unavailable. |
| 4xx / 502 | own_vendor_key_failed | vendor_error | depends | Your own vendor key (bring-your-own-key) failed; the vendor's answer is passed through and x-airouter-key-source: own is set. Check the key under Organisation → Vendor keys, or let odnoga's pool take over when your key fails. |
| 503 | billing_unavailable | service_unavailable | wait | odnoga refused the call before sending it to the model, because of a problem on our side with that model. Nothing reached a vendor and nothing was charged. x-odnoga-ticket carries the ticket already open with the odnoga team. Retry later or send another model; a fallback chain skips the model for you. |
| 403 | use_api_odnoga_com | invalid_request_error | fix | The request carried an odnoga API key but was not sent to https://api.odnoga.com/v1. Only that address serves API traffic; change the base URL and retry. Nothing was run or charged. |
| 503 | no_vendor_key | service_unavailable | wait | No usable credential for this model's vendor right now. Retry later or send another model. |
| 503 | no_candidate_model | service_unavailable | wait | Every model in the chain was skipped (missing capability or vendor cooldown). x-airouter-skipped-models lists why; x-airouter-cooldown-until says when the first is expected back. |
| 503 | capacity_unavailable | service_unavailable | yes | odnoga was briefly at capacity mid-request. Nothing reached a vendor and nothing was charged. Honour Retry-After (seconds); the official SDKs retry a 503 automatically. |
| 503 | backend_unavailable | service_unavailable | yes | A platform dependency was briefly unreachable. Retry after Retry-After. |
| 504 | statement_timeout | timeout | yes | odnoga's own bookkeeping timed out before the call was sent. Retry after Retry-After. |
| 500 | internal_error | internal_error | yes | Unexpected error inside odnoga. The message carries a reference id (also in x-airouter-request-id); quote it when reporting. |
A model that does not answer within the upstream deadline (300 s, or your x-airouter-timeout-ms) is abandoned and the next model in the chain is tried; if none answers, the result is 503 vendor_unavailable. A non-streamed call that sends nothing for ~150 s is closed by the network in front of odnoga with a plain 504 — stream long calls (see Retries and timeouts).
Retry policy (consumer)
- Never retry
4xxother than408,409 idempotency_in_progressand429. They are deterministic; retrying wastes money. - Retry with exponential backoff for
429,5xxand network errors. Cap at 3 attempts. - Always respect
Retry-Afterwhen present.x-ratelimit-resetis when a budget window refills (only sent when one exists), not a retry hint. - A
402is not retryable until the money side changes (top-up, cap raised, window reset). - In a streamed call an error that happens after the stream opened arrives as a
data: {"error": …}frame followed by[DONE](HTTP status is already 200). Treat it exactly like the matching HTTP error.
See Retries and timeouts for a tested helper.