Skip to content
kenari.

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.

{ "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).

HTTPcodemeaning
400bad_requestrequest body is invalid
400model_not_foundmodel or route name is not available
400upstream_errorupstream provider rejected the request (4xx status, except 429)
401(plain text)kn- key is missing or wrong
402insufficient_balanceIDR balance is empty for a paid request, top up first
402payg_limit_reachedyour PAYG spending limit was reached, raise the limit in Settings or wait for the reset window
403subscribers_onlysubscription-only model
403shared_key_not_allowedshared keys cannot read account quota
429rate_limit_exceededtoo many requests (account RPM limit or free model limit)
429free_quota_dailyfree-model daily allowance used up, resets at 00:00 UTC. Top up or subscribe to raise it (Retry-After header is accurate)
429free_quota_rpmfree-model RPM limit reached, wait a few seconds (Retry-After header is accurate)
429plan_limit_reachedsubscription window quota is used up, enable PAYG or wait for reset
429upstream_errorupstream provider rate-limited the request, wait and try again
4xxinvalid_request_errorupstream rejected the request as invalid (wrong field/body)
4xxcontext_length_exceededrequest exceeds the model’s maximum context window
4xxcontent_policy_violationupstream blocked the request because of content policy
4xxupstream_rejectedupstream rejection with an unknown reason (fallback)
503upstream_errorone upstream provider failed (timeout / 5xx)
503all_providers_failedall route providers or steps failed
503no_wire_for_modalityno active endpoint serves the requested modality (chat, image, edit, embedding)
500internal_errorunexpected gateway error

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.

  • 429/503 with the Retry-After header. The free model RPM limit, the free model daily quota, and the 503 failure when all provider keys are paused (cooling) include Retry-After (seconds until the next window). Official SDKs already read this header and back off automatically. Other limits (account burst, subscription quota) do not send Retry-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-After is 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. code marks 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.