Handling errors
The OpenAI-shaped error taxonomy, and what to retry.
Errors that Kontinent creates itself are OpenAI-shaped:
{ "error": { "message": "...", "type": "...", "code": "..." } }Status reference
| HTTP | code | type | Meaning / client action |
|---|---|---|---|
| 400 | invalid_request | invalid_request_error | Malformed body, over 2 MiB, or missing model. |
| 400 | tools_not_supported | invalid_request_error | The request sends tools, but this model cannot call tools. Choose a model whose supported_parameters include tools. |
| 401 | invalid_api_key | invalid_request_error | Bad, revoked, disabled or expired key: do not retry. |
| 402 | insufficient_credits | insufficient_quota | Balance at or below 0: top up, then retry. |
| 403 | model_access_blocked | invalid_request_error | Your organization's privacy settings or the key's guardrail rule out every provider for this model (zero retention, no training, sovereign only, § 203 only, allow or block list). The message names the rule. Do not retry: change the setting in the console or choose another model. |
| 403 | prompt_injection_blocked | invalid_request_error | The request matched a prompt-injection rule and the key's guardrail is set to block. The message names the rule. Rephrase, or set the guardrail to flag. |
| 403 | segment_not_permitted | invalid_request_error | Every candidate provider excludes your organization's registered usage segment by contract, or (in § 203 mode) has not signed a confidentiality undertaking: do not retry, choose a model from another provider. |
| 404 | model_not_found | invalid_request_error | Unknown or disabled model id. |
| 429 | rate_limit_exceeded | rate_limit_error | Per-key RPM hit (sliding 60 s window), or more than 20 failed authentications from one IP in the current minute. Carries Retry-After: 60: back off. |
| 429 | spend_limit_exceeded | insufficient_quota | A spend limit on the key, its team or its guardrail is used up for the current day, week or month. Retrying does not help until the window resets or the limit is raised. See Budgets & spend limits. |
| 429 | upstream_rate_limited | rate_limit_error | Provider throttled us: retry with backoff. |
| 500 | internal_error | api_error | Gateway bug: retriable. |
| 502 | upstream_auth_error | api_error | Provider rejected our credentials: our problem. |
| 502 | upstream_error | api_error | A provider answered and the answer was unusable: a 5xx, or (on a non-streaming request) a 2xx whose body could not be read, translated, or fit under the 32 MiB cap. Retriable, and worth routing elsewhere. |
| 503 | transcriptions_busy | api_error | Too many transcriptions are in progress on this gateway instance. Carries Retry-After: 10: retry after that many seconds. |
| 503 | no_provider_available | api_error | No provider could be reached at all. Transient on our side: retry as-is. |
On a streaming request these codes only apply before the first byte. Once a stream has started the status is 200 and cannot be changed, and the codes from the table are not replayed in-stream: whatever went wrong is announced as a terminal stream error with error.code: "upstream_error". On /v1/chat/completions that is a final chunk carrying a top-level error and finish_reason: "error", with no [DONE] after it; on /v1/responses it is a response.failed event whose response object carries status: "failed" and the error. Both keep whatever output was already generated. A client that only inspects HTTP status will read a truncated answer as a complete one. See Streaming.
502 and 503 look similar and mean different things. 502 says a specific provider is broken. A fallback or another model may well work right now. 503 says nothing in the chain answered, which is usually a network or credential problem on our side and affects every model of that provider at once; retrying the same request shortly is the right response.
Passthrough errors
Every upstream 4xx other than 401, 403 and 429 (for example, provider-side validation of your parameters) is passed through unchanged with its original status. Its body is the provider's own, so it is usually but not always OpenAI-shaped. An upstream 401 or 403 becomes 502 upstream_auth_error, because it concerns our credentials, not yours.
Retry guidance
Retriable
429 rate_limit_exceeded, 429 upstream_rate_limited, 500, 502 upstream_error, 503 no_provider_available, 503 transcriptions_busy (wait for Retry-After where present). Use exponential backoff with jitter.
Not retriable as-is
400, 401, 403, 404. Fix the request, the key or the setting first. For 403 segment_not_permitted, choose a model from another provider; for 403 model_access_blocked, change the privacy setting or guardrail named in the message. 402 is retriable only after a top-up, 429 spend_limit_exceeded only once the limit window resets or the limit is raised.
Common cases
404 model_not_found on a model you can see in the dashboard
The id must be in provider/model form, and the model must be active in the catalog. Confirm the exact id with GET /v1/models, which lists every active model. If the id exists but your privacy settings or guardrail rule out its providers, you get 403 model_access_blocked instead.
400 invalid_request with a request that works against OpenAI
Check the body size (the cap is 2 MiB) and that model is present. Provider-side parameter validation is passed through unchanged, so the message in the body comes straight from the upstream provider.
A stream that ends without [DONE]
Failover only happens before the first byte. After that the status code is already 200, so an upstream failure arrives as a chunk: a top-level error object plus finish_reason: "error", and no [DONE]. Treat it as a retriable error on your side, and remember that the tokens generated so far are billed. See Streaming.