# Errors > The error response shape and common HTTP status codes. Source: https://kiln.bricksum.com/docs/en/errors Errors are returned with a standard HTTP status code. The body depends on which layer rejected the request, so branch on the HTTP status and error.code, not on the message text. Every body shape the API returns is listed below with an example. Note Branch on the status and error.code, never on message. Message text is written for people and can be reworded; the status and the code are the contract. ## HTTP statuses Every status the API returns, grouped by what you do about it. - 400, 404, 405: the request itself is wrong. Fix it before sending it again. - 401, 403: the API key cannot be used. Check or replace the key. - 402: the credit balance, the organization budget or the key's spend limit is exhausted. - 429: a rate limit was exceeded. Retry after the reset, or back off. - 500, 502, 503: a problem on our side. Retry with exponential backoff. ## Status codes This table is the single reference for status codes; other pages link here. Handle these explicitly in your client. | Status | error.code | Cause | What to do | | --- | --- | --- | --- | | 400 | empty_array, unsupported_parameter | Bad request. Malformed JSON, invalid parameters, a request over the model's context window (listed per model on the Models page), an empty or missing messages array on /v1/chat/completions, ignore_eos on gpt-oss models, or previous_response_id on /v1/responses. | Fix the request payload; do not retry as-is. | | 401 | - | Unauthorized. The API key is missing, malformed, unknown or expired. | Check the Authorization or x-api-key header, the key value and the key's expiry date. | | 402 | - | Payment required. The credit balance is exhausted, the organization reached its monthly budget, or the key reached its spend limit. | Top up credits, or ask an admin to raise the budget or the key's spend limit. Do not retry until then. | | 403 | - | Forbidden. The API key is suspended or revoked. Keys of an archived project count as suspended. | Reactivate the key in the console, or create a new key if it was revoked. | | 404 | model_not_found, unknown_url, not_found | Not found. The model does not exist or is not available to you (model_not_found), the path is not served (unknown_url), or a stored response was requested (not_found; responses are not retained). | Check the model ID against GET /v1/models and the path against the API reference. | | 405 | method_not_allowed | Method not allowed. The path is served, but not with this HTTP method. | Use the method named in the Allow response header. | | 429 | rate_limit_exceeded (concurrency only) | Too many requests. A per-organization limit was exceeded: 60 requests/min by default, the tokens/min limit if one is set, or 8 concurrent requests. | A requests/min or tokens/min 429 carries x-ratelimit-reset; wait that many seconds. A concurrency 429 carries no rate-limit headers; back off exponentially. | | 502 | - | Bad gateway. The upstream model backend returned an error or was unreachable. | Retry with exponential backoff; use streaming for long generations. Contact support if it persists. | | 503 | - | Service unavailable. The service is in maintenance mode, or no model backend is available. | During maintenance the response carries a Retry-After header (an HTTP date); wait until then. Otherwise retry with exponential backoff. | | 5xx | - | Server error. A transient problem on our side. | Retry with exponential backoff. Contact support if it persists. | ## Error body shapes - Authentication, credit and RPM/TPM limit rejections (401, 402, 403, 429): {"error":{"message","type":"authorization"}}. There is no code or param field. - Request validation and the concurrency limit: error.type is invalid_request_error, and the body adds param and code. code is one of model_not_found, empty_array, unsupported_parameter, rate_limit_exceeded, unknown_url, method_not_allowed, not_found. - On /v1/messages, errors returned after authentication use the Anthropic envelope: {"type":"error","error":{"type","message"}}. - GET /v1/key returns {"error":{"message","code"}}, with no type field. The code is unauthorized for a missing, unknown, suspended or expired key. - A 502, and a 503 caused by no available model backend, carry a plain-text body on the OpenAI-format endpoints, so do not parse them as JSON. A maintenance 503 is JSON with type "maintenance". **Gateway rejection** ```json { "error": { "message": "missing or malformed API key", "type": "authorization" } } ``` **Request validation** ```json { "error": { "message": "The model does not exist or you do not have access to it.", "type": "invalid_request_error", "param": "model", "code": "model_not_found" } } ``` **Anthropic envelope (/v1/messages)** ```json { "type": "error", "error": { "type": "invalid_request_error", "message": "max_tokens is required" } } ``` **GET /v1/key** ```json { "error": { "message": "invalid API key", "code": "unauthorized" } } ``` **Plain text (502, 503)** ```http HTTP/1.1 502 Bad Gateway Content-Type: text/plain; charset=utf-8 upstream error ``` ## Pre-stream and mid-stream failures ### Before any byte A rejected request fails before a single response byte is written. Your client sees a non-2xx status and one of the bodies above, no tokens were generated, and the request is not billed. This is the case every table row above describes. ### After a 200 Once the gateway has written 200 and the first stream bytes, the HTTP status can no longer change. If the model server fails mid-generation, the stream stops early instead: it ends without a final chunk and without the data: [DONE] sentinel, or an error object arrives inside the stream. A generation that fails this way is not billed. A truncated stream looks like a short answer, so check for it: - Treat a stream that ends without data: [DONE] as failed, even though the status was 200. - Check that the last chunk carries a finish_reason. A stream that stops without one did not complete. - Set stream_options.include_usage to true and treat a missing usage chunk as a failure. - Log the X-Neocloud-Generation-Id response header so a failed request can be traced afterwards. For 429 handling and retry strategy, see [Rate limits](https://kiln.bricksum.com/docs/en/rate-limits).