{"error": "<code>"} (sometimes with a
"detail" string). The rest of the KINDI API returns
{"detail": "<message>"}; see Error codes for
that shape. Branch your error handling on the route: /api/v1/proxy/*
reads error; everything else reads detail.1234{ "error": "model_not_allowed_on_demo", "detail": "switch to your own key" }
error is a stable machine-readable code. detail is an optional
human-readable hint and may be absent.| Status | error | When it fires | Retry? |
400 | provider_key_required | A Bearer caller sent no X-Provider-Key, and the managed key isn't available to it. The most common proxy error — the managed demo is session-only, so programmatic callers must always bring a key. | no |
400 | invalid_request_body | The body isn't valid JSON, or decodes to something that isn't a JSON object. | no |
400 | managed_key_disabled | The managed demo is switched off on this deployment. Check managed_enabled on /proxy/status. | no |
400 | model_not_allowed_on_demo | A model not in the managed-demo allowlist was requested on the managed path. Bring your own key for unrestricted models. | no |
402 | insufficient_tokens | Your KINDI masking-fee balance (free quota + paid) is exhausted. detail carries needed_tokens=… available_tokens=…; top up at /dashboard/billing. This is KINDI's fee, not the provider's inference cost. | no |
402 | managed_allowance_exhausted | Your personal daily managed-demo token cap is used up. detail: "switch to your own key". | no¹ |
403 | demo_requires_verification | The managed demo requires a logged-in, email-verified dashboard session. Unverified/anonymous callers are blocked. | no |
404 | unknown_provider | The {provider} path segment isn't openai or anthropic. | no |
404 | glossary_not_found | The glossary_id you referenced doesn't exist or isn't yours. | no |
413 | request_too_large | The request body exceeds the proxy body-size cap. Applies to all callers, not just the demo. | no |
429 | rate_limited | Your per-key (or per-user) limit is full; on the managed path a second, tighter demo bucket applies too. Carries a Retry-After header; sleep for it rather than spinning. | yes |
501 | endpoint_not_supported_in_v1 | The requested provider endpoint is outside the v1 scope (chat/completions + messages only). | no |
502 | egress_not_allowed | The resolved upstream host isn't on KINDI's egress allow-list. A configuration problem, not a transient one. | no |
502 | provider_unreachable | KINDI couldn't reach OpenAI/Anthropic at all (DNS, TLS, connection). | yes |
503 | demo_unavailable | The global daily managed-demo budget is exhausted across all users. detail: "demo temporarily unavailable, use your own key". | yes² |
503 | managed_key_unavailable | The managed demo is enabled but KINDI has no usable managed key for that provider right now. | yes |
503 | masker_unavailable / masker_overloaded | Detection is down or shedding load, so the prompt can't be masked; KINDI fails closed rather than forwarding your text unmasked. masker_overloaded carries Retry-After: 1. | yes |
503 | image_redact_unavailable | The request carries inline images and the image-redaction service is unreachable. Carries Retry-After: 2; fails closed for the same reason. | yes |
413 / 400 | image_redact_rejected | An inline image was rejected as too large (413) or unprocessable (400). | no |
X-Provider-Key).{"error": code}. So a 401 with an OpenAI-shaped
error body means your X-Provider-Key was rejected by OpenAI, not by
KINDI.401) and the geo-block (403 with no body at all) are handled
before the proxy route runs, so they keep the site-wide shape documented in
Error codes — 401 returns {"detail": …} and the
geo-block returns nothing to parse.insufficient_tokens, rate_limited,
masker_unavailable, and glossary_not_found — is rendered in the
proxy's top-level {"error": code} shape and is listed in the catalogue
above. So on /api/v1/proxy/* you read error for anything with a JSON
body except a 401.