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.
Error formats
Section titled “Error formats”OpenAI format
Section titled “OpenAI format”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. The Workspace API sets type from the HTTP status instead, such as conflict_error for every 409.
Anthropic format
Section titled “Anthropic format”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.type | HTTP status |
|---|---|
invalid_request_error | 400, or the provider’s own status for a provider rejection |
not_found_error | 400, or the provider’s own status for a provider rejection |
payment_required | 402 |
permission_error | 403 |
rate_limit_error | 429 |
api_error | 400, 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.
The Claude Managed Agents passthrough uses this envelope as well, with a code on the errors kenari raises itself, such as auth_failed. An error from Anthropic passes through unchanged and has no code. Branch on code when it is there and on error.type when it is not.
Errors in a stream
Section titled “Errors in a stream”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 format | How the error arrives |
|---|---|
| Chat completions | A data: chunk with an error object whose type is upstream_error |
| Messages | An event: error event with the Anthropic body above |
| Responses | A 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.
Plain-text responses
Section titled “Plain-text responses”Some responses are plain text, not JSON, because the request is rejected before it reaches the API.
| Status | Body | Cause |
|---|---|---|
| 400 | Failed to parse the request body as JSON... | On /v1/web/search and /v1/ocr, the body is not valid JSON |
| 401 | missing authorization | No key header was sent |
| 401 | invalid key | The key does not start with kn-, does not exist, was revoked, has expired, or belongs to an account that is suspended or being deleted |
| 408 | request body timed out | The body stopped arriving for 30 seconds, or took more than 5 minutes to arrive. Send it again |
| 413 | request body too large | The request body is over 32 MiB |
| 415 | Expected request with Content-Type: application/json | On /v1/web/search and /v1/ocr, the Content-Type: application/json header is missing |
| 422 | Failed to deserialize the JSON body... | On /v1/web/search and /v1/ocr, a required field is missing or has the wrong type |
| 429 | too many requests, try again shortly | One client IP sent too many requests on a shared key. See Rate limits. |
| 500 | db error | kenari could not look up the key. Retry. |
| 503 | server busy, try again shortly | kenari is receiving too many large request bodies at once. Retry shortly |
The Claude Managed Agents passthrough answers differently. Its 401, its 429 on a shared key and its db error arrive as JSON in the Anthropic format with the code auth_failed. A 500 for any other failure inside kenari is the plain text internal error.
For keys, see Authentication and API keys.
Error codes
Section titled “Error codes”400 Bad request
Section titled “400 Bad request”| Code | When | What to do |
|---|---|---|
bad_request | The 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 limit | Read message, fix the request. |
model_not_found | On 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 now | Pick an id from GET /v1/models, or retry later. |
request_blocked | kenari refused the request under its content policy before it reached a provider | Change the content of the request. |
byok_malformed_model_id | A byok/ model id is not in the form byok/<provider>/<model> | Fix the id. See Providers. |
byok_provider_not_found | You have no key or connection for that provider | Add the key or connect the subscription, or fix the provider name. |
invalid_path | A path on the Claude Managed Agents passthrough contains .., an encoded slash or a backslash | Send a normal path. |
upstream_error | A provider answered with a client error that kenari did not classify | Check the model and the request fields, or try another model. |
invalid_request | A Workspace API request has a body that is not valid JSON for the route, a value that the workspace rules refuse, a malformed id in the path or a malformed Idempotency-Key header | Read message, fix the request. |
idempotency_key_required | A Workspace API route that buys, changes or reactivates seats, or moves Personal balance, has no Idempotency-Key header | Send a unique Idempotency-Key. See Workspace API. |
pay_from_personal_not_supported | A Workspace API seat request has a pay_from other than workspace | Move Personal balance into the workspace first, then buy the seats. See Workspace API. |
Provider rejections
Section titled “Provider rejections”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.
| Code | When | What to do |
|---|---|---|
invalid_request_error | The provider rejected the request as malformed or unsupported | Send the same request to another model. If it works there, report it to kenari support. |
context_length_exceeded | The prompt plus max_tokens exceed the model’s context window | Shorten the input, lower max_tokens, or use a model with a longer context. |
model_not_found | The provider does not serve the model right now | Try again later or use another model. |
content_policy_violation | The provider blocked the request under its content policy | Change the content of the request. |
prefill_unsupported | The last message is from the assistant, and the model cannot continue it | End the conversation with a user or tool message. |
reference_image_fetch_failed | A video request carried a reference image the provider could not fetch | Send each reference as a public https URL or a data URL. |
upstream_rejected | The provider rejected the request for a reason kenari could not classify | Check 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.
401 Unauthorized
Section titled “401 Unauthorized”The body is plain text. See Plain-text responses. Check the key, or create a new one in the dashboard.
The Claude Managed Agents passthrough answers in JSON instead, with this code:
| Code | When | What to do |
|---|---|---|
auth_failed | The kn- key is missing, wrong, expired or revoked | Check the key, or create a new one in the dashboard. |
402 Payment required
Section titled “402 Payment required”| Code | When | What to do |
|---|---|---|
insufficient_balance | Your balance cannot cover the request or the fee of a web search, page fetch or X search, or the owner of a shared API key has run out of funds | Top 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. 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_reached | You reached the PAYG balance limit you set in your subscription settings. It covers a rolling day or 30 days | Raise or remove the limit, or wait for the window to roll. Plan quota and free models are not affected. |
key_spend_cap_reached | The API key’s spend cap or token cap is used up. Your balance is not the problem | The key’s owner raises the spend cap or resets its spend on the API Keys page (an unshared key with a spend cap), or uses another key. A token cap and a shared key’s caps cannot be edited, so the owner creates a new key. The owner also gets a notification the first time the cap runs out. |
member_spend_cap_reached | You are a member of a team workspace and you reached the spending limit its Owner or an Admin set for you. The limit counts what you draw from the workspace balance over a rolling 30 days: PAYG requests, use beyond your seat’s quota, web search, web fetch, X search, images and video. Before that point kenari lowers max_tokens to what your remaining limit can pay for, the same way it does for a low balance | Ask the workspace Owner or an Admin to raise or remove your limit on the Team page, or wait for the 30-day window to roll. Your seat’s quota and free models are not affected. You also get a notification the first time you reach the limit in a window. |
See How billing works.
403 Forbidden
Section titled “403 Forbidden”The Workspace API has its own 403 codes. They are listed here, and the page explains each one.
| Code | When | What to do |
|---|---|---|
subscribers_only | The model is only for accounts with an active subscription | Subscribe, or pick another model. See Subscriptions. |
subscription_required | A byok/chatgpt/... or byok/grok/... request, or a request to the Claude Managed Agents passthrough, from an account without an active plan | Subscribe. See Providers. |
byok_credential_unavailable | You have keys or connections for that provider, but none can serve the request right now because they are disabled, paused or signed out | Enable the key, wait for a paused one to recover, or connect the subscription again. |
byok_spend_capped_key | A key with a spend cap called a byok/ model | Use a key without a spend cap. A token cap is allowed. |
shared_key_not_allowed | A shared API key called GET /v1/account/balance, GET /v1/account/quota, a Usage endpoint, a Per-tool keys endpoint, a Workspace API route or the Claude Managed Agents passthrough | Use a key from the account itself. |
restricted_key_not_allowed | A restricted key called GET /v1/account/balance, GET /v1/account/quota, a Per-tool keys endpoint, a Workspace API route or the Claude Managed Agents passthrough, or passed user_id to a Usage endpoint | Use a key with no model restriction, capability restriction, spend cap or token cap. The key’s own usage is still readable from GET /v1/usage. See Account overview. |
cli_login_key_required | A key other than the one from kenari login called a Per-tool keys endpoint | Use the key from kenari login. Run kenari login again if your key is older than per-tool keys. |
workspace_access_required | A Workspace API route needs a higher workspace access level than the key has, or the key is one that kenari login saved or a per-tool key | Create a key with the level the route needs. See Authentication and API keys. |
teams_not_enabled | A Workspace API route or a kenari_workspace_* MCP tool was called before team workspaces are open for the account | Do not retry. The request succeeds once team workspaces are open for the account. |
not_a_team_workspace | A Workspace API route other than GET /v1/workspace was called with a key in a Personal workspace | Use a key from a team workspace. |
permission_denied | A Workspace API route for an Owner or Admin was called with a key held by a Member, the workspace rules refuse the change (removing the Owner, for example), or a key held by a Member passed another person’s user_id to a Usage endpoint | Use a key held by an Owner or Admin, or change the request. |
fund_cap_reached | A POST /v1/workspace/fund-from-personal transfer would pass the key’s daily limit | Wait for the rolling 24 hours to pass, or create a key with a higher limit. The message says how much is left. |
404 Not found
Section titled “404 Not found”| Code | When | What to do |
|---|---|---|
not_found | DELETE /v1/account/keys/{id} got an id that is not an active per-tool key of the calling key, or a Workspace API route got a member, invite or seat that is not in the workspace | List your keys with GET /v1/account/keys and use an id from it. See Per-tool keys. For the Workspace API, list the members, invites or seats and use an id from the list. |
credential_not_found | The x-kenari-credential header on the Claude Managed Agents passthrough is not an active Anthropic key of your account | Use the id of an active Anthropic key. |
409 Conflict
Section titled “409 Conflict”| Code | When | What to do |
|---|---|---|
child_key_cap | The login key already holds the most per-tool keys it can | Revoke one with DELETE /v1/account/keys/{id}, then create the new key. See Per-tool keys. |
anthropic_credential_required | The Claude Managed Agents passthrough found no active Anthropic API key on your account | Add an Anthropic key on the Providers tab. |
credit_limit_reached | The Anthropic key named in x-kenari-credential on the passthrough has used up its Claude credit, and auto-stop is on | Raise the credit, turn auto-stop off or wait for the renewal day. See Providers. |
credential_required | The passthrough found two or more active Anthropic keys and no x-kenari-credential header | Name one key in the header, or disable the others. |
conflict | The state of the workspace refuses a Workspace API change, such as inviting a person who is already a member, a workspace at its member limit, or removing the last seat | Read message, and read the state with the GET routes. |
idempotency_key_reused | A Workspace API Idempotency-Key was already used for a different body or route | Use a new Idempotency-Key for a new request. |
request_in_progress | The first Workspace API request with this Idempotency-Key is still running | Read the state with the GET routes before you retry. |
413 Payload too large
Section titled “413 Payload too large”A request body over 32 MiB is refused with a plain-text response, not JSON. See Plain-text responses. A 413 from a model’s provider is a JSON error. See Provider rejections. The code below is for the Claude Managed Agents passthrough, which accepts bodies up to 100 MB.
| Code | When | What to do |
|---|---|---|
request_too_large | A request body on the Claude Managed Agents passthrough is over 100 MB | Send a smaller body. |
429 Too many requests
Section titled “429 Too many requests”Every 429 code is explained, with Retry-After behavior, in Rate limits.
| Code | When |
|---|---|
rate_limit_exceeded | The 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_rpm | The free-model per-minute limit |
free_quota_daily | The free-model daily quota, which resets at 00:00 UTC |
plan_limit_reached | A subscription quota window is used up, and PAYG and the free fallback are off |
upstream_error | A provider rate limited the request |
passthrough_busy | Too many Claude Managed Agents passthrough requests are in progress. Try again shortly |
rate_limited | The per-key limits on changes through the Workspace API |
pending_limit_reached | A workspace has too many pending invites |
daily_limit_reached | A workspace has reached its daily invite limit |
500 Internal server error
Section titled “500 Internal server error”| Code | When | What to do |
|---|---|---|
internal_error | Something unexpected failed in kenari | Retry once. If it keeps happening, report the request-id header to kenari support. |
503 Service unavailable
Section titled “503 Service unavailable”| Code | When | What to do |
|---|---|---|
upstream_error | A provider timed out, could not be reached, or failed | Retry with backoff. |
all_providers_failed | Every provider, or every step of a route, failed. Retry-After is set when the providers are paused or at capacity | Retry after the Retry-After seconds, or use a route with a fallback step. See Routing and routes. |
no_wire_for_modality | No provider currently serves this kind of request for the model, such as chat, image, edit or embedding | Try again later, or use another model. |
Provider names
Section titled “Provider names”Error messages never name the provider behind a model. kenari writes every message itself.
Long requests
Section titled “Long requests”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.
Handle errors
Section titled “Handle errors”- On OpenAI-format APIs, use the HTTP status and
code. On Messages, use the HTTP status anderror.type. Treatmessageas text for people. - Retry
429withrate_limit_exceeded,free_quota_rpmorupstream_error, and retry500and503. Wait forRetry-Afterwhen it is present, and use exponential backoff with jitter when it is not. - Do not retry other
4xxerrors unchanged. Fix the request, the key or the balance first. - For
402, top up or change the limit that was reached, then send the request again. - Check streams for error events, and do not assume every error body is JSON. See Plain-text responses.
- Log the
request-idresponse header, and include it when you contact kenari support.