Skip to content

Reference

Errors

Failures use the OpenAI error envelope, so SDK error handling works unchanged. Branch on error.code: it is stable, while messages may be reworded.

Error envelope (permalink)

Every error the gateway itself produces has this shape.

JSON402 response body
{
  "error": {
    "message": "Token budget exhausted. Contact your provider to renew.",
    "type": "insufficient_quota",
    "code": "token_budget_exhausted",
    "param": null
  }
}
  • message is human-readable and safe to log or show an operator. It never contains credentials.
  • type is the broad family: invalid_request_error, permission_error, insufficient_quota, rate_limit_error or api_error.
  • code is the specific reason. This is the field your code should switch on.
  • param is reserved for field-level errors and is null on everything the gateway raises.

Gateway errors (permalink)

These twelve are the complete set the gateway raises on its own. Anything else you see came from the model provider and passed straight through.

Complete gateway error reference
StatusCodeTypeMeaningWhat to do
401missing_api_keyinvalid_request_errorThe request carried no credential at all.Send Authorization: Bearer with your key, or the x-api-key header.
401invalid_api_keyinvalid_request_errorUnknown key, or a valid key sent to the other billing surface.Check the key was copied whole, then confirm you are on the right base URL.
403key_suspendedpermission_errorThe key exists but has been paused by whoever issued it.Ask them to resume it. The key itself stays valid and does not need replacing.
403key_expiredpermission_errorThe key is past its expiry date.Request an extension or a new key. It will not recover on its own.
403model_not_allowedpermission_errorThe key is valid but is not scoped to the model in the request body.Use a model on your allowlist, or ask for the allowlist to be widened.
403model_not_availablepermission_errorThe model id is not one the gateway can serve: unknown, or switched off centrally.Check the id against the model catalogue. If it is listed and still refused, it has been disabled.
400model_requiredinvalid_request_errorAn inference call whose model could not be read, usually a malformed or empty JSON body.Send a valid JSON body with a top-level "model" string.
402insufficient_creditinsufficient_quotaA credit key whose USD balance has reached zero.Top the key up on the billing page, or ask whoever issued it. Confirm the balance in the usage checker first.
402token_budget_exhaustedinsufficient_quotaA token key that has consumed its whole prepaid budget.Ask for the budget to be renewed.
429rate_limit_exceededrate_limit_errorThe key went over its requests-per-minute limit. Nothing was forwarded or billed.Wait the number of seconds in the retry-after header, then retry. Ask for a higher limit if you need one.
404unknown_endpointinvalid_request_errorNothing followed the base URL, or the path did not resolve to a real endpoint.Append a real path such as /chat/completions. Watch for a doubled or missing /v1.
502upstream_unavailableapi_errorThe gateway could not reach the model provider at all. Nothing was billed.Retry with exponential backoff. Repeated 502s mean an outage, not a bad request.

Order of checks (permalink)

Knowing the order tells you what a status rules out. The gateway stops at the first failing check and never reaches the ones below it.

  1. Path. A request with nothing after the base URL, or a path that does not resolve, gets 404 unknown_endpoint. This happens before authentication, so a 404 says nothing about your key.
  2. Credential present. No Authorization: Bearer and no x-api-key gives 401 missing_api_key.
  3. Key known. An unrecognised key gives 401 invalid_api_key.
  4. Key usable. Suspended gives 403 key_suspended, past its expiry gives 403 key_expired.
  5. Right surface. A credit key on the token base URL, or the reverse, gives 401 invalid_api_key again.
  6. Funds. An empty balance gives 402 insufficient_credit, a spent budget gives 402 token_budget_exhausted.
  7. Rate. A key over its requests-per-minute limit gives 429 rate_limit_exceeded, with a retry-after header holding the seconds left in the current window. This happens before the request body is read, so a throttled call costs nothing.
  8. Model readable. Only now is the request body read. An inference call whose model field cannot be read, because the body is empty or is not valid JSON, gives 400 model_required rather than being forwarded unpriced.
  9. Model allowed. A model outside the key’s allowlist gives 403 model_not_allowed.
  10. Model servable. A model the gateway cannot serve, because the id is unknown or the model has been switched off centrally, gives 403 model_not_available. Both cases return the same body on purpose.
  11. Forwarded. If the provider cannot be reached at all, 502 upstream_unavailable. Otherwise the provider’s own status is what you get.

Upstream failures (permalink)

A request that passes every check is forwarded to the model provider, and the provider’s status and response body come back to you unchanged. So a 400 about an unsupported parameter, a 429 about rate limits, or a 529 about capacity originates there rather than here, and the message reads in the provider’s own words.

Response headers are filtered rather than mirrored. content-type and x-request-id are preserved, streamed responses also carry cache-control: no-cache, no-transform, and everything else is dropped. The one header the gateway adds itself is retry-after, on its own 429. Do not build logic on a header you have not seen documented here.

Pass-through statuses
StatusWhere it comes from
400, 404, 422The provider rejected the request body: a bad parameter, an oversized context, or a missing max_tokens on an Anthropic Messages call. A 400 whose code is model_required came from the gateway instead, and an unknown model id never reaches the provider at all.
429Either surface can produce this. A gateway 429 carries code: "rate_limit_exceeded" and a retry-after header; anything else is the provider rate limiting you. Back off with jitter in both cases.
5xx other than 502The provider failed or timed out. Retry with backoff, and report it if it persists.

A 402 can also arrive this way, when the upstream account itself is short of funds. The distinction matters: if the usage checker shows your key still has balance or budget but calls keep returning 402, the problem is upstream and only your provider can clear it.

What to retry (permalink)

Authentication, permission and billing failures are decisions, not accidents. Retrying them wastes time and makes rate limits worse. Only 429 and 5xx deserve another attempt.

JavaScriptReading the error
const response = await fetch(url, init);

if (!response.ok) {
  const body = await response.json().catch(() => null);
  const code = body?.error?.code ?? "unknown_error";

  // Only 429 and 5xx are worth trying again.
  const retryable = response.status === 429 || response.status >= 500;

  throw Object.assign(new Error(body?.error?.message ?? response.statusText), {
    status: response.status,
    code,
    retryable,
  });
}

Then retry only what is retryable, with exponential backoff and jitter.

JavaScriptBackoff wrapper
async function withRetry(send, attempts = 4) {
  for (let attempt = 0; ; attempt++) {
    try {
      return await send();
    } catch (err) {
      if (!err.retryable || attempt >= attempts - 1) throw err;
      const wait = 500 * 2 ** attempt + Math.random() * 250;  // jittered backoff
      await new Promise((resolve) => setTimeout(resolve, wait));
    }
  }
}
  • Cap the attempts. Four tries over roughly ten seconds is plenty for a transient upstream fault.
  • Add jitter. Without it, every client that failed at the same moment retries at the same moment.
  • On a 429, prefer the retry-after header over your own schedule when it is present. It is the exact number of seconds left in the current rate-limit window.
  • Stop on 402 immediately, or you will hammer a key that has nothing left to spend.

What gets billed (permalink)

Failures cost nothing, but they are not all recorded the same way.

How failures are recorded
OutcomeBilledIn the usage checker
Refused by the gatewayNoNo. A 400, 401, 402, 403, 404 or 429 stops before the ledger, so it never appears as a request.
Forwarded, provider erroredNoYes, recorded against your key as an error at zero cost.
Provider unreachable (502)NoYes, recorded as an error at zero cost.
SucceededYesYes, with model, token counts and cost.

Successful metadata calls are the one exception in the other direction. A GET /models reports no token usage, so it is neither billed nor listed. More detail on Checking usage.

Reporting a problem (permalink)

When something needs a human, include:

  • The x-request-id response header, when the provider supplied one.
  • The HTTP status and the whole error object.
  • The key prefix only, meaning the first characters such as kl_live_a1b2c3d4. Never send the whole key.
  • The model id, whether you were streaming, and roughly when it happened.