Errors
Every error uses OpenAI’s envelope, so SDKs raise their usual exception types:
For the exact shape of every request and response, with curl, Python and Node examples, see the API reference.
{ "error": { "message": "'temperature' must be between 0 and 2.", "type": "invalid_request_error", "param": "temperature", "code": "invalid_value" }}type is one of invalid_request_error, permission_error, billing_error, rate_limit_error or server_error. param names the offending field when there is one. The x-request-id header is set on every response; quote it when you write to support.
400: bad request
Section titled “400: bad request”| code | Meaning |
|---|---|
invalid_json |
the body is not valid JSON or could not be read |
no_model_allowed |
an auto request whose model access leaves auto no model it can use. The message says which level to check: account, organization, project or key. See Model access |
missing_messages |
messages absent or empty (a missing model means auto) |
too_many_messages, too_many_tools |
more than 4096 messages or 256 tools |
invalid_value |
a field is out of range or malformed; param says which |
unsupported_parameter |
a field Zabio does not implement (n above 1, logprobs, audio, prediction, web_search_options, non text modalities, the legacy function role); tools sent to a model that cannot take them through Zabio (today gpt-6-astra) |
invalid_image_url |
an image could not be fetched or is not allowed; see Chat completions |
byok_key_invalid |
your organization’s own vendor key was rejected by the vendor; update it under Billing, vendor keys. Zabio never falls back to its own key |
upstream_invalid_request |
the vendor rejected the request; the message carries the vendor’s reason |
unsupported_for_model |
the model you named cannot do what the request asks; param names the field (jev-1.13.0 answers typed questions from a json_schema only, never text, tools, images or a stream) |
conversation_mismatch, conversation_too_large, context_length_exceeded |
Responses API; see Responses API |
401: authentication
Section titled “401: authentication”code: invalid_api_key when the key is missing, incorrect or revoked; the message says which. Keys are sent as Authorization: Bearer zb_live_....
code: key_expired when the key’s expiration has passed: "This API key expired on 2026-11-01 00:00 UTC. Create a new key or change its expiration in the web app." A key’s expiration is set when it is created or later in its Edit panel; its creator gets an email 7 days before. Background responses and batches the key queued before it expired end with the same code.
402: no credits
Section titled “402: no credits”{"error": {"message": "Your organization has no credits left. Add credits at zabio.ai/billing to continue.", "type": "billing_error", "code": "insufficient_credits"}}Returned before any vendor work, only when a zero balance means no service: the organization is set to stop at zero, or it is a person’s second organization, which has no Free allowance. Every other organization at zero runs on the Free limits instead (see Billing, “The Free plan”) and gets the free_tier_* codes below. The header x-zabio-balance-micros carries the balance. A named model served on your own vendor key is not refused; auto is, whatever keys you hold, because the classifier runs on Zabio’s key. A batch or a background response whose estimated cost exceeds the available balance is refused with this code too.
403: not allowed
Section titled “403: not allowed”| code | Meaning |
|---|---|
insufficient_scope |
the key lacks the scope for this endpoint (chat, embeddings or conversations) |
organization_suspended |
the organization is suspended |
conversations_closed |
stored conversations are closed for this organization |
free_tier_model |
the request names a model that is not on the free model list while the organization runs on the Free limits. The message is “Top up your account to unlock this model” with the top-up link; auto keeps working and picks among the free models. GET /v1/models marks locked models with zabio.availability: locked |
free_tier_limit with param members or api_keys |
the Free plan’s account caps, on the dashboard API: one member and two API keys until the first top-up |
404: not found
Section titled “404: not found”model_not_found: the model does not exist, or a level of the key’s model access (the account, the organization, the project or the key) does not allow it; an auto line a level unchecked is refused the same way. The Responses API adds previous_response_not_found, conversation_not_found and response_not_found.
413: too large
Section titled “413: too large”payload_too_large: the body exceeds 32 MB.
429: slow down
Section titled “429: slow down”Every 429 carries a Retry-After header with the number of seconds to wait. Four limits exist:
| code | Scope | Details |
|---|---|---|
rate_limit_exceeded |
API key | the key’s requests per minute or tokens per minute bucket is empty. Defaults: 600 requests and 200,000 tokens per minute; set per key. The token bucket is charged up front with the input estimate plus the output budget, clamped to what the served model can produce, and settled after the call to the real usage; a max_tokens far above what the job needs can hit the limit early, so set it near what you need. Headers x-ratelimit-limit-*, x-ratelimit-remaining-* and x-ratelimit-reset-* for requests and tokens are set on every response |
quota_exceeded |
key or member (organization level) | the key’s spend limit is reached for its period (a day, a week from Monday or a month, from 00:00 UTC, or the key’s whole life), or the monthly quota of the member who created the key is reached. The message says when it resets, for example "This API key's spend limit of 5.00 USD has been reached. It resets on 2026-10-19 at 00:00 UTC.". Headers x-zabio-quota-scope (key or member), x-zabio-quota-limit and x-zabio-quota-remaining are set |
ip_rate_limit_exceeded |
client address | too many requests from one address, checked before authentication. Default 300 per minute. Headers x-ratelimit-*-ip are set only on the refusal |
upstream_rate_limited |
vendor | the vendor is rate limiting and every fallback was exhausted |
free_tier_limit |
organization, on the Free limits | one of the Free limits is met; param and the header x-zabio-free-limit name it: requests_per_day (100 successful requests a UTC day; Retry-After is the time to 00:00 UTC), requests_per_minute (20), daily_cost (the daily fair-use limit; it resets at 00:00 UTC), input_tokens (the request has more than 16,000 input tokens; no Retry-After, send a shorter request) or max_tokens (an explicit max_tokens above 2,000; lower it or leave it unset). The message says which limit, when it resets, and that a top-up removes every limit. The Free rate-limit headers (x-ratelimit-*-requests-day and the minute bucket in x-ratelimit-*-requests) are on every Free response, refusals included. See Billing |
rate_limit_exceeded, quota_exceeded and free_tier_limit use type: rate_limit_error.
5xx: server side
Section titled “5xx: server side”| Status | code | Meaning |
|---|---|---|
| 500 | internal_error |
an unexpected condition; the cause is never in the body |
| 502 | upstream_error |
the vendor returned an error or an unreadable response after retries and fallbacks. Retry-After: 5 when a retry makes sense |
| 503 | no_provider_available |
no healthy provider for this request right now; Retry-After: 5 |
| 503 | vendor_unavailable |
the call named a model on a provider that is not available through Zabio right now; call another model or auto, or retry later; Retry-After: 5 |
| 503 | auth_unavailable, billing_unavailable, conversations_unavailable |
a Zabio dependency is temporarily unavailable |
| 503 | free_tier_unavailable |
the counters behind the Free limits could not be read, so a free call is refused rather than served unmetered; retry shortly. Paid calls are not affected. Also the answer of GET /v1/public/free-tier when the list cannot be read |
| 504 | upstream_timeout |
the vendor did not answer in time; Retry-After: 5 |
Errors after a committed 200 (long calls)
Section titled “Errors after a committed 200 (long calls)”A call that Zabio has kept alive for more than 90 seconds (see “Long calls” in the chat completions and Responses guides) already has its 200 status on the wire when the vendor fails, so the error travels in the body instead:
| Call | Shape |
|---|---|
| chat completions, not streamed | a chat.completion with choices: [] and an error object beside it (message, type, code) |
| chat completions, streamed | data: {"error": {"message", "type", "code"}} then data: [DONE] |
| Responses, not streamed | the response object with status: "failed" and error: {"code", "message"} |
| Responses, streamed | a response.failed event carrying that object |
The code values are the same as in the tables above. Check error whenever choices is empty or status is failed.
Vendor errors and fallback
Section titled “Vendor errors and fallback”When a provider fails, Zabio retries on the next eligible model according to the routing rules (up to 3 attempts; explicit models follow zabio.fallback). Only when every option is exhausted do you see a vendor error. The vendor’s body never reaches you; x-zabio-vendor names the vendor that failed and the message carries the vendor’s reason for 400s. Content the vendor refuses to produce comes back as a normal response with finish_reason: "content_filter", not as an error.
During streaming, a failure after the stream has started is reported as an error object inside a chunk (chat completions) or an error event (Responses API), and the stream ends.
Two more codes on the Responses API
Section titled “Two more codes on the Responses API”missing_field(400): a required field such asinput, or an itemrole,contentorcall_idis missing. A missingmodelmeansauto.previous_response_not_foundandconversation_not_found(404) onPOST /v1/responses;response_not_found(404) applies toGETandDELETE /v1/responses/{id}.
The full list per endpoint is in the API reference.