Errors and status
kenari returns errors in the same format as the API you call: OpenAI style for /v1/chat/completions, /v1/responses, /v1/images/generations, /v1/audio/speech, /v1/audio/transcriptions, /v1/music/generations, /v1/videos/generations, /v1/videos/extensions, and /v1/models, while Anthropic style is used for /v1/messages. The HTTP status is identical on both paths.
OpenAI error format
Section titled “OpenAI error format”{ "error": { "message": "...", "type": "<code>", "param": null, "code": "<code>" } }type and code contain the same kenari error code, except for upstream rejections: type is always invalid_request_error and code contains the specific reason (context_length_exceeded, model_not_found, content_policy_violation, invalid_request_error, or upstream_rejected).
Error table
Section titled “Error table”| HTTP | code | meaning |
|---|---|---|
| 400 | bad_request | request body is invalid |
| 400 | model_not_found | model or route name is not available |
| 400 | upstream_error | upstream provider rejected the request (4xx status, except 429) |
| 401 | (plain text) | kn- key is missing or wrong |
| 402 | insufficient_balance | IDR balance is empty for a paid request, top up first |
| 402 | payg_limit_reached | your PAYG spending limit was reached, raise the limit in Settings or wait for the reset window |
| 403 | subscribers_only | subscription-only model |
| 403 | shared_key_not_allowed | shared keys cannot read account quota |
| 429 | rate_limit_exceeded | too many requests (account RPM limit or free model limit) |
| 429 | free_quota_daily | free-model daily allowance used up, resets at 00:00 UTC. Top up or subscribe to raise it (Retry-After header is accurate) |
| 429 | free_quota_rpm | free-model RPM limit reached, wait a few seconds (Retry-After header is accurate) |
| 429 | plan_limit_reached | subscription window quota is used up, enable PAYG or wait for reset |
| 429 | upstream_error | upstream provider rate-limited the request, wait and try again |
| 4xx | invalid_request_error | upstream rejected the request as invalid (wrong field/body) |
| 4xx | context_length_exceeded | request exceeds the model’s maximum context window |
| 4xx | content_policy_violation | upstream blocked the request because of content policy |
| 4xx | upstream_rejected | upstream rejection with an unknown reason (fallback) |
| 503 | upstream_error | one upstream provider failed (timeout / 5xx) |
| 503 | all_providers_failed | all route providers or steps failed |
| 503 | no_wire_for_modality | no active endpoint serves the requested modality (chat, image, edit, embedding) |
| 500 | internal_error | unexpected gateway error |
Anthropic error format
Section titled “Anthropic error format”POST /v1/messages uses Anthropic-style errors:
{ "type": "error", "error": { "type": "<type>", "message": "..." } }error.type contains invalid_request_error (400), not_found_error (400), payment_required (402), permission_error (403), rate_limit_error (429), and api_error (500/503). The HTTP status is the same as the table above.
Handling errors
Section titled “Handling errors”- 429/503 with the
Retry-Afterheader. The free model RPM limit, the free model daily quota, and the 503 failure when all provider keys are paused (cooling) includeRetry-After(seconds until the next window). Official SDKs already read this header and back off automatically. Other limits (account burst, subscription quota) do not sendRetry-After, so they do not mislead clients. - 402 insufficient_balance. Top up via QRIS, then retry. See Billing & Rupiah.
- 503 all_providers_failed. All backends for that model failed. This is usually temporary (
Retry-Afteris also sent when a key is paused). Try again, or use kenari Routing with a fallback step. - Very long prompts (above 100k tokens). Use
"stream": true: a non-stream response that runs past 100 seconds with no data out can get cut by the edge network before it reaches you, while the streaming endpoint sends periodic keepalive comment lines (ignored by every SSE parser) through prefill so the connection stays open even before the first real token. - Upstream rejection (
invalid_request_error/context_length_exceeded/model_not_found/content_policy_violation/upstream_rejected). Rejection from upstream purely because the request is wrong (malformed body, exhausted context, missing model, or content policy). The original 4xx status is kept when it is one of {400, 403, 404, 413, 422, 429}. Otherwise it becomes 400.codemarks the reason so SDKs can branch without reading text. The message is stable kenari text, not the upstream body. For a direct dispatch to a single provider (body forwarded as-is), the request is not retried within the same call. But inside nested routes or BYOK credential chains, this rejection is not final: kenari keeps trying the next backend or credential in the chain, because the request is rewritten per backend, so one rejection does not prove another backend would reject the same request.