Errors
Every error uses one envelope:
{ "error": { "code": "quota_exhausted", "message": "Monthly quota exceeded", "details": {} } }
Code table
| HTTP | code | When |
|---|---|---|
| 400 | bad_request | The request is malformed or a field is invalid — the message says which. Also a pass-through model id that is :free, openrouter/*, unknown or unpriced (see Drop-in from OpenRouter) |
| 400 | bad_key | Invalid document key (incl. ./..) |
| 400 | already_subscribed | You already hold that subscription |
| 401 | unauthenticated / key_expired | Missing, bad, or expired API key |
| 402 | quota_exhausted / key_budget_exhausted / insufficient_credit | Money / quota walls — insufficient_credit is also a pass-through call whose estimated cost your credit can't cover. key_budget_exhausted means the key hit one of its spending limits; details carries period, limit_usd, spent_usd and resets_at (null for a total limit) |
| 402 | elevenlabs_daily_cap_reached | ElevenLabs drop-in: your account reached today's ElevenLabs limit — resets at 00:00 UTC |
| 402 | passthrough_daily_cap_reached | Pass-through models: your account reached today's pass-through spend limit — resets at 00:00 UTC (see Drop-in from OpenRouter) |
| 402 | storage_balance_negative | Object storage: your balance is below zero — uploads pause at once, downloads after 14 days (see Object storage) |
| 403 | model_not_in_plan | None of your plans include the requested model |
| 403 | reserved_key | Write/delete to a reserved kb-* document |
| 403 | model_denied / byok_not_enabled / forbidden | Policy walls |
| 403 | document_too_large / document_limit_reached | Document exceeds the char cap / per-account document cap |
| 403 | service_not_covered | No active plan includes this service and extra credits can't cover it |
| 403 | model_not_allowed_for_key | The model is outside this API key's model list |
| 403 | service_not_allowed_for_key | The service is outside this API key's service list |
| 403 | document_key_not_allowed | The document key is outside this API key's document filter |
| 403 | elevenlabs_disabled | ElevenLabs drop-in: switched off — voice and model listings still answer |
| 403 | voice_not_available | ElevenLabs drop-in: the voice isn't a premade voice or on KhaBot's allow-list (see Drop-in from ElevenLabs — errors there use ElevenLabs' {"detail": {"status", "message"}} shape) |
| 403 | storage_scope_denied | The bucket or action is outside this API key's storage scopes |
| 403 | key_not_authorized | An account-management endpoint was called with a key that doesn't carry that permission |
| 404 | model_not_found | An unknown khabot/* model id — the full list is GET /api/v1/models |
| 404 | not_available | An OpenRouter route KhaBot doesn't offer, such as /rerank or /models/user — see Drop-in from OpenRouter |
| 404 | not_found | The requested item does not exist (e.g. an unknown media id) |
| 404 | document_not_found | GET/DELETE a document that doesn't exist (not billed) |
| 404 | object_not_found | Object storage: no file has that key |
| 404 | page_not_found | HTML to Markdown: the page no longer exists |
| 409 | bucket_exists / bucket_not_empty | Object storage: the bucket name is taken, or the bucket still holds files |
| 413 | object_too_large | Object storage: a single upload over 1 GB — use a multipart upload |
| 422 | cost_limit / page_blocked / page_thin / page_unsupported | HTML to Markdown page-level refusals — see HTML to Markdown |
| 429 | rate_limited | Your rate limit is used up — wait retry-after seconds — or too many object-storage transfers at once |
| 502 | all_providers_failed | The request could not be completed upstream — retry |
| 503 | upstream_unavailable | A required service is temporarily unavailable |
| 503 | page_fetch_failed | HTML to Markdown: the page could not be retrieved — safe to retry |
| 503 | service_not_configured | The service isn't switched on |
| 503 | service_at_capacity | ElevenLabs drop-in: temporarily at capacity — try again later |
Always branch on error.code, not the human-readable message.