Skip to contentKumoDocs
Sections
On this page
The gateway

Errors

The envelope a refusal arrives in, the status codes the gateway answers with, and the two behaviors every client must handle.

View as Markdown

Quick

The whole decision table. The detail behind each row is in the sections below.

StatusWhyWhat to do
400The request is not valid — a malformed body, or a member this surface does not take.Fix the body. There is nothing to retry: the same request is refused the same way.
401The request is not authenticated.Check the header and the key itself. Do not retry.
402Nothing to pay with: the wallet is empty, or the package the key spends from is exhausted.Top up the wallet or the package, or switch the key to the balance. Do not retry.
403The key may not call this model, or the organization is frozen.Check the key's scope and the organization's state in the console. Do not retry.
404No such model.Check the name against the published catalog. Do not retry.
429The call went over a rate or spending ceiling.Back off and retry later, lengthening the wait with every attempt and capping their number.
500The request could not be completed.Back off and retry, not at once. Not blindly in a loop: the outcome upstream may have stayed unknown.
503No provider can currently serve this request.Back off and retry later. Retrying at once does not help: nothing upstream has changed.

No refusal is fixed by retrying immediately. A client that treats every error as "try again now" spends its whole allowance on being refused.

The refusal envelope

A refused call answers with one JSON object carrying a single error member. It is this protocol's own envelope and not a translation of another's: the same shape refuses a Chat Completions call, a Responses call and a Messages call, and the Messages surface wraps it with the top-level "type": "error" its own clients look for.

Two members are always present — type and message — and two more appear when there is something to say.

typeThe class of failure, in this protocol's own closed vocabulary: invalid_request_error, not_found_error, authentication_error, permission_error, rate_limit_error, api_error, and on the Messages surface overloaded_error as well.
messageWhat went wrong, in a fixed safe sentence. It is never built from anything the request carried, so it can never quote a prompt, a header or a key back at a log.
codeThe machine-readable reason. One reason has one spelling across every surface of this platform, which is what makes it the thing a client branches on.
paramThe request member at fault, when one member is at fault.

Branch on type and on code, never on the wording of message: the wording is chosen to be safe to print, not to be parsed.

{
  "error": {
    "type": "authentication_error",
    "message": "The request is not authenticated."
  }
}

Status codes

400The request is not valid — a malformed body, or a member this surface does not take.
401The request is not authenticated.
402Nothing to pay with: the wallet is empty (`insufficient_wallet_balance`) or the key's package is exhausted (`insufficient_package_balance`). Refused before any supplier is asked.
403The key may not call this model, or the organization is frozen.
404No such model.
429A rate or spending limit refused the request.
500The request could not be completed.
503No provider can currently serve this request.

Two of them are worth telling apart before you write a retry. A 429 is a ceiling you are over, and time is what clears it; a 503 means nothing upstream can serve the call at this moment. Neither is fixed by retrying at once, and a client that treats every refusal as "try again immediately" spends its whole allowance on being refused.

One 401, whatever is wrong with the key

A missing key, a malformed key, an unknown key, a revoked key and an expired key are answered alike — the same 401, the same sentence — and that is a decision rather than an omission. Telling them apart would turn the gateway into an oracle over key material: whoever can see the difference between "there is no such key" and "that key was revoked" is reading the platform's key table one guess at a time.

So a 401 says exactly one thing: this call was not admitted. Check that the header is there and spelled as a bearer token, that the key is the whole secret you copied when you minted it, and that the key is still live. The key check answers all three at once, from a browser, without a line of code.

Check your key → How the header is written →

A stream that stops early

A refusal decided before the first event is an ordinary JSON error with a status, exactly as a unary call would be — the envelope above, and nothing streamed. A failure after the stream has started cannot be that: the status line has already gone out. Such a stream simply ends, without its terminal [DONE] frame.

That absence is the whole signal, so read for it. A client that treats "the connection closed" as "the answer finished" will hand a truncated answer to whatever comes next, and nothing in the bytes it received says otherwise. Treat a stream that ends before [DONE] as a failed call rather than a short one.

The other two surfaces say the same thing in their own events. On the Messages surface the failure arrives as this protocol's error event and the stream then ends without message_stop; on the Responses surface the terminal event is response.failed, carrying this envelope in place of the finished body.

Failover

A model is usually reachable through more than one channel. When the one serving a call fails before any of the answer has reached you, the call carries on over the next channel for the same model rather than coming back to you.

Three kinds of failure end the call instead of moving it: a failure after the first bytes have reached you, because you cannot be handed a second answer halfway through one you are already reading; a call you canceled, because you are no longer waiting for it; and a call whose outcome upstream is not known, because retrying that one could bill you twice for work that in fact succeeded.

So a 503 is not a promise that every route was tried. It also arrives when no channel is eligible for what the call asked for, before anything upstream is contacted at all, and the number of channels a single call will try is bounded rather than exhaustive.

What bounds your throughput →