Docs
Errors
Kunavo returns OpenAI-shaped error payloads with HTTP status codes that signal whether you should retry. Stick to standard OpenAI-SDK error handling and you'll be fine.
Every non-2xx response has the same JSON shape — the OpenAI standard. Match on error.code for programmatic handling, on status_code for retry policy.
Error payload
{
"error": {
"message": "Model 'xyz-2025' is not available",
"type": "model_not_found",
"code": "model_not_found",
"param": null
}
}| Field | Type | Notes |
|---|---|---|
message | string | Human-readable. Safe to log; never includes secrets. |
type | string | Coarse category (authentication_error, invalid_request_error, …). |
code | string | Fine-grained identifier. Use for programmatic branching. |
param | string|null | When the error is about a specific field, names it. |
HTTP status codes
| HTTP | Meaning | Retry? |
|---|---|---|
| 400 | invalid_request_error — your payload is malformed. | No — fix the request. |
| 401 | authentication_error — bad / missing / revoked API key. | No — rotate the key. |
| 402 | insufficient_quota — wallet empty (code: insufficient_balance) or a monthly cap hit. | No — top up at /app/billing. Exception: a Retry-After header means an auto-recharge is in flight; retry after it. |
| 403 | permission_error — the key is valid, but its IP allowlist does not cover you. | No — call from an allowed address. |
| 404 | model_not_found — model id unknown or disabled. | No — check /v1/models. |
| 404 | missing_v1_prefix — an endpoint path on api.kunavo.com without /v1, or a POST to the bare host. | No — set the base URL to https://api.kunavo.com/v1. |
| 404 | unknown_url — no endpoint at that path under /v1; the message names the closest one. | No — fix the path. |
| 408 | Client request timeout. | Yes, with idempotency caution. |
| 422 | upstream_422 — upstream rejected your input (e.g. unsupported size). | No — adjust input. |
| 429 | Rate limited — too many concurrent calls. | Yes — exponential backoff. |
| 500 | Internal Kunavo error. | Yes — short backoff. |
| 502 | upstream_error — upstream provider failed. | Yes — exponential backoff. |
| 504 | Task timed out (image / video generation exceeded our poll budget). | Yes, but expect cost. |
usage_logs is $0. Exception: 504 timeouts on long-running task APIs may show upstream cost if the task completed but we couldn't deliver the result — rare.Common error codes
authentication_error
- Forgot to set
Authorization: Bearer ...header. - API key was revoked from
/app/keys. - Key still has wrong prefix (must start with
sk-kn-).
insufficient_quota
- Wallet balance too low for this request →
error.codeisinsufficient_balance, and the error carries what the wallet held (balance_usd), what this request reserves (needed_usd), and two links:topup_urlopens the billing page with a top-up preselected,auto_recharge_urlopens it on the auto-recharge settings. Neither link charges anything by itself. - Per-key monthly cap reached. Reset is at calendar month start (UTC). Raise the cap at
/app/keys.
{
"error": {
"message": "Wallet balance is too low for this request. Top up at https://kunavo.com/app/billing — or turn on auto-recharge there, and a saved card refills the wallet before requests are refused.",
"type": "insufficient_quota",
"code": "insufficient_balance",
"param": null,
"balance_usd": 0.0142,
"needed_usd": 0.0381,
"topup_url": "https://kunavo.com/app/billing?topup=20",
"auto_recharge_url": "https://kunavo.com/app/billing?autorecharge=1&src=api_402"
}
}Stop retrying on insufficient_balance unless the response has a Retry-After header — that one means an auto-recharge is already charging your saved card, and the same request should succeed a few seconds later.
permission_error
- The key carries an IP allowlist and your call came from an address outside it. The message names the address we saw — add it (or its CIDR block) at
/app/keys, or drop the allowlist entirely to allow any address. - Moving an agent to a new host, a new NAT gateway, or a laptop on a different network all change that address. A key with no allowlist is never refused this way.
model_not_found
- Misspelled slug. Use /models for the canonical list.
- Model is currently disabled (upstream outage or pricing review).
upstream_error
Indicates an upstream model provider returned an error that we couldn't translate to a more specific code. Body includes the original message. Common causes:
- Provider-side overload.
- Content moderation rejection (try a different prompt).
- Region / availability gating.
Retry strategy
The simplest robust pattern: exponential backoff with jitter on 429 / 5xx only.
import time, random
from openai import OpenAI, RateLimitError, APIError
client = OpenAI(api_key=os.environ["KUNAVO_API_KEY"], base_url="https://api.kunavo.com/v1")
def call_with_retry(call, max_attempts=5):
for attempt in range(max_attempts):
try:
return call()
except RateLimitError:
if attempt + 1 == max_attempts: raise
# 429: exponential backoff with jitter
time.sleep(min(2 ** attempt, 30) + random.random())
except APIError as e:
if e.status_code and 500 <= e.status_code < 600 and attempt + 1 < max_attempts:
time.sleep(min(2 ** attempt, 30) + random.random())
else:
raise
result = call_with_retry(lambda: client.chat.completions.create(
model="claude-haiku-4-5",
messages=[{"role": "user", "content": "hi"}],
))- Cap the wait at 30 seconds per attempt and the total at 5 attempts.
- Don't retry 4xx (except 408 and 429). Doing so just burns time.
- For idempotent endpoints (image / video generation), retries are safe — failed calls were never billed.
- For chat with side effects in your own code, deduplicate via an idempotency key in your application layer.
Debugging from the dashboard
Every API call — successful or failed — is visible at /app/usage. Click any row for the full request/response detail page:
- HTTP status code returned to the client.
- Upstream error code (when applicable).
- Latency, request IP, user-agent.
- Filtering by status helps spot patterns (e.g. all 422s on a specific model).
Reporting an incident
If you see persistent 5xx for a model that should work, email support@kunavo.com with:
- The model slug and a sample request body (redact secrets).
- The time window when failures occurred.
- The error
code+messagefrom the response. - If possible, a usage-log link from
/app/usage/[id].
One-business-day response time, faster for urgent revenue-impacting issues.