Skip to content
kenari.

Errors

kenari returns errors in the same format as the API you called. Handle an error by its HTTP status and its code, or error.type on Messages, never by the message text. Most messages are in English and a few are in Indonesian, and the wording can change.

Every endpoint except Messages returns this body: Chat completions, Responses, Embeddings, Rerank, Moderations, Images, audio, music, videos, OCR, web and X search, models and account quota.

{
"error": {
"message": "free-model rate limit reached; slow down and retry",
"type": "rate_limit_exceeded",
"param": null,
"code": "free_quota_rpm"
}
}

code is the field to branch on. type repeats code for most errors. The exceptions are the free-model limits (type is rate_limit_exceeded), provider rejections (type is invalid_request_error), shared_key_not_allowed, restricted_key_not_allowed and cli_login_key_required (type is permission_error) and an internal failure on GET /v1/account/balance, GET /v1/account/quota and the Usage endpoints (type is api_error, code is internal_error). param is always null, except on a 400 from the Usage endpoints, where it names the parameter.

Messages, including count_tokens, returns Anthropic-style errors. There is no code field, so branch on the HTTP status and error.type.

{
"type": "error",
"error": {
"type": "rate_limit_error",
"message": "free-model rate limit reached; slow down and retry"
}
}
error.typeHTTP status
invalid_request_error400, or the provider’s own status for a provider rejection
not_found_error400, or the provider’s own status for a provider rejection
payment_required402
permission_error403
rate_limit_error429
api_error400, 500 and 503

The HTTP status is the same as in the OpenAI format for the same failure. A provider rejection keeps the provider’s own status, so these two types are not always 400. See Provider rejections. api_error arrives with 400 when a provider answered with a client error that kenari did not classify.

After a stream has started, the HTTP status is already 200, so a failure arrives as an event in the stream. Check every stream for these events. See Streaming.

API formatHow the error arrives
Chat completionsA data: chunk with an error object whose type is upstream_error
MessagesAn event: error event with the Anthropic body above
ResponsesA response.failed event, with the error in response.error

On Chat completions and Responses, code is stream_interrupted when the provider stopped before the answer was complete. The Messages event has error.type and no code. Send the request again as a new request. A provider rejection in the stream uses the codes under Provider rejections.

Some responses are plain text, not JSON, because the request is rejected before it reaches the API.

StatusBodyCause
400Failed to parse the request body as JSON...On /v1/web/search and /v1/ocr, the body is not valid JSON
401missing authorizationNo key header was sent
401invalid keyThe key does not start with kn-, does not exist, was revoked, has expired, or belongs to an account that is suspended or being deleted
408request body timed outThe body stopped arriving for 30 seconds, or took more than 5 minutes to arrive. Send it again
413request body too largeThe request body is over 32 MiB
415Expected request with Content-Type: application/jsonOn /v1/web/search and /v1/ocr, the Content-Type: application/json header is missing
422Failed to deserialize the JSON body...On /v1/web/search and /v1/ocr, a required field is missing or has the wrong type
429too many requests, try again shortlyOne client IP sent too many requests on a shared key. See Rate limits.
500db errorkenari could not look up the key. Retry.
503server busy, try again shortlykenari is receiving too many large request bodies at once. Retry shortly

For keys, see Authentication and API keys.

CodeWhenWhat to do
bad_requestThe body or a parameter is invalid, or the key’s restrictions refuse the request (model not allowed, capability not granted, route on a restricted key). An attached document with an unsupported type, a size or page count over the limit, or a length over the model’s context is also refused here. On Embeddings, Rerank, Moderations and System One, a model that is unknown or of another kind is refused here too, and the message lists the ids that are available. Creating a per-tool key is refused here with key limit reached when your account is at its key limitRead message, fix the request.
model_not_foundOn Chat completions, Responses and Messages, the model id or route name does not exist, or you cannot use it. On Embeddings, Rerank, Moderations and System One, the model is listed but no provider serves it right nowPick an id from GET /v1/models, or retry later.
request_blockedkenari refused the request under its content policy before it reached a providerChange the content of the request.
byok_malformed_model_idA byok/ model id is not in the form byok/<provider>/<model>Fix the id. See BYOK.
byok_provider_not_foundYou have no BYOK key for that providerAdd the key, or fix the provider name.
upstream_errorA provider answered with a client error that kenari did not classifyCheck the model and the request fields, or try another model.

These codes mean the model’s provider rejected the request itself. The HTTP status is 400, or the provider’s own 403, 404, 413, 415, 422 or 429, and type is invalid_request_error.

CodeWhenWhat to do
invalid_request_errorThe provider rejected the request as malformed or unsupportedSend the same request to another model. If it works there, report it to kenari support.
context_length_exceededThe prompt plus max_tokens exceed the model’s context windowShorten the input, lower max_tokens, or use a model with a longer context.
model_not_foundThe provider does not serve the model right nowTry again later or use another model.
content_policy_violationThe provider blocked the request under its content policyChange the content of the request.
prefill_unsupportedThe last message is from the assistant, and the model cannot continue itEnd the conversation with a user or tool message.
upstream_rejectedThe provider rejected the request for a reason kenari could not classifyCheck the model id and the request fields.

kenari does not retry a rejected request on the same provider. In a route or a BYOK chain it can move on to the next step, because one provider’s rejection does not prove the next one will reject the same request, but a context length rejection ends the chain. The message is written by kenari and never contains the provider’s own text.

The body is plain text. See Plain-text responses. Check the key, or create a new one in the dashboard.

CodeWhenWhat to do
insufficient_balanceYour balance cannot cover the request, a key’s spend cap or token cap is reached, or the owner of a shared API key has run out of fundsTop up, or send a shorter prompt or a lower max_tokens. The message shows your available balance and the most this request could cost, which is the input estimate plus max_tokens at the output rate. For a capped key, the owner creates a replacement key with a higher cap and revokes the exhausted one, because a key’s caps cannot be changed. A document read with the ocr engine or /v1/ocr has its own message: it says that document reading is billed per page from your balance and not covered by plans, and shows the balance available and the most the document reserves. Top up, or send the file without the ocr engine so that it is read as ordinary input tokens.
payg_limit_reachedYou reached the PAYG balance limit you set in your subscription settings. It covers a rolling day or 30 daysRaise or remove the limit, or wait for the window to roll. Plan quota and free models are not affected.

See How billing works.

CodeWhenWhat to do
subscribers_onlyThe model is only for accounts with an active subscriptionSubscribe, or pick another model. See Subscriptions.
byok_credential_unavailableYou have BYOK keys for that provider, but none can serve the request right now because they are disabled or pausedEnable a key, or wait for a paused key to recover.
byok_spend_capped_keyA key with a spend cap called a byok/ modelUse a key without a spend cap. A token cap is allowed.
shared_key_not_allowedA shared API key called GET /v1/account/balance, GET /v1/account/quota, a Usage endpoint or a Per-tool keys endpointUse a key from the account itself.
restricted_key_not_allowedA restricted key called GET /v1/account/balance, GET /v1/account/quota or a Per-tool keys endpointUse a key with no model restriction, spend cap or token cap. See Account overview.
cli_login_key_requiredA key other than the one from kenari login called a Per-tool keys endpointUse the key from kenari login. Run kenari login again if your key is older than per-tool keys.
CodeWhenWhat to do
not_foundDELETE /v1/account/keys/{id} got an id that is not an active per-tool key of the calling keyList your keys with GET /v1/account/keys and use an id from it. See Per-tool keys.
CodeWhenWhat to do
child_key_capThe login key already holds the most per-tool keys it canRevoke one with DELETE /v1/account/keys/{id}, then create the new key. See Per-tool keys.

Every 429 code is explained, with Retry-After behavior, in Rate limits.

CodeWhen
rate_limit_exceededThe account per-minute limit, the usage reads limit, the per-tool key creation limit, a burst of free requests, a free-model token budget, or music concurrency
free_quota_rpmThe free-model per-minute limit
free_quota_dailyThe free-model daily quota, which resets at 00:00 UTC
plan_limit_reachedA subscription quota window is used up, and PAYG and the free fallback are off
upstream_errorA provider rate limited the request
CodeWhenWhat to do
internal_errorSomething unexpected failed in kenariRetry once. If it keeps happening, report the request-id header to kenari support.
CodeWhenWhat to do
upstream_errorA provider timed out, could not be reached, or failedRetry with backoff.
all_providers_failedEvery provider, or every step of a route, failed. Retry-After is set when the providers are paused or at capacityRetry after the Retry-After seconds, or use a route with a fallback step. See Routing and routes.
no_wire_for_modalityNo provider currently serves this kind of request for the model, such as chat, image, edit or embeddingTry again later, or use another model.

Error messages never name the provider behind a model. kenari writes every message itself.

A non-streaming request that runs for about two minutes with no response can be cut off by the network edge before the answer reaches you. For long prompts, such as more than 100,000 tokens, large max_tokens, or slow reasoning models, set "stream": true. A stream keeps the connection open while the model works. See Streaming.

When a non-streaming request is still running after a long wait, kenari can keep the connection open by sending spaces before the JSON body. JSON parsers ignore the spaces. The status line is already 200 by then, so check the body for an error object before you read the answer.

  1. On OpenAI-format APIs, use the HTTP status and code. On Messages, use the HTTP status and error.type. Treat message as text for people.
  2. Retry 429 with rate_limit_exceeded, free_quota_rpm or upstream_error, and retry 500 and 503. Wait for Retry-After when it is present, and use exponential backoff with jitter when it is not.
  3. Do not retry other 4xx errors unchanged. Fix the request, the key or the balance first.
  4. For 402, top up or change the limit that was reached, then send the request again.
  5. Check streams for error events, and do not assume every error body is JSON. See Plain-text responses.
  6. Log the request-id response header, and include it when you contact kenari support.