Skip to content
Numes

API guides

Errors and debugging

The error envelope, every status code and error type, which errors to retry, and how to track down a failing request.

On this page

A request that fails before any output returns a non-2xx status and a JSON error envelope. A stream that fails after it has started reports the error inside the stream.

Error envelope

JSON
{  "error": {    "code": 402,    "message": "insufficient credits",    "metadata": { "error_type": "insufficient_credits" }  }}
  • Before streaming starts, the HTTP status equals error.code. After it starts, HTTP stays 200; read the terminal chunk's error.code.

  • error.metadata.error_type is the stable category. Branch on the status and error_type, never on message, which can change.

  • A 400 invalid_request on a chat completion names the field and the rule it broke, for example n must be 1; multiple choices are not supported or n_predict must use the admitted chat messages or output limit fields. A request too long for every endpoint tried returns request exceeds the model's context length; shorten the input or lower the output limit. Messages never repeat values you sent.

  • An endpoint that cannot accept your tool, conversation or embedding input can fall back to another eligible endpoint. If none accepts it, you get 400 invalid_request. A nonnumeric penalty or sampler such as min_p: "low" returns 400 before dispatch, as does a reasoning effort that no endpoint of the model accepts (Reasoning). A malformed encrypted envelope also returns 400. If an encrypted endpoint refuses your public output limit and it exceeds the approved limit, lower the named max_tokens or max_completion_tokens field.

  • 500 messages are always internal server error.

  • The request ID is in the X-Request-Id response header, not in the body. Error bodies never include openrouter_metadata.

Status codes and error types

Statuserror_typeMeaningWhat to do
400invalid_requestThe body, a parameter or a header is invalid; or an unknown field, a reused X-Request-Id, media that can't be fetched or decoded, or a prompt too long for every endpoint tried.Fix the request. Don't retry unchanged.
400interception_limit_exceededThe request is too large for your account's request policy to process.Shorten the conversation, or ask an owner to simplify the policy.
401invalid_credentialsThe key was refused.Check the key and the route's key kind. See Authentication.
402insufficient_creditsThe request's cost can't be covered by your balance or spending limits.See 402: insufficient credits.
403forbiddenKey management only: the management key's creator may not act on that key.Use a key whose creator has the required organization role.
403input_blockedYour account's request policy or the selected source's content policy blocked the input. The message tells you which. A source block can try another eligible endpoint if fallback is enabled. Nothing was charged.Change the input. Don't retry unchanged.
403cors_forbiddenA browser sent the request from a disallowed origin.Call the API from your server.
404not_foundThe model (endpoints list), key or generation doesn't exist for your account.Check the ID.
408timeoutThe request reached its time limit, including after partial output.Retry once, with a shorter prompt or a smaller output limit. Handle any partial output and completed tool effects before retrying.
408cancelledThe caller cancelled the request or response delivery failed.Respect cancellation; start a new request only if you still need the result.
413payload_too_largeThe body exceeds the route's body limit.Shrink the body.
415unsupported_media_typeA JSON route received a body without Content-Type: application/json.Set the header.
429rate_limitedA rate limit was reached.See 429: rate limits.
500serverUnexpected error.Retry with backoff. If it persists, contact support with the request ID.
500interception_failedYour account's request policy couldn't process this request or its response.Contact support with the request ID.
502provider_unavailableProviders returned invalid responses or rejected the request upstream, and no fallback succeeded.Retry with backoff. If it persists, check the request against the model's supported_parameters or set provider.require_parameters: true.
503provider_unavailableNo endpoint can serve this request, or server execution was interrupted while you remained connected.See 503: unavailable.
503provider_overloadedMatching endpoints have no free capacity.Retry with backoff, or relax provider filters.
503bitcoin_reference_price_unavailableA BTC price couldn't be confirmed. The request wasn't sent or charged.Retry with backoff.
503routing_unavailable, service_unavailable, catalogue_unavailable, authentication_unavailable, rate_limit_unavailable, interception_unavailableThe service, or the part named, is temporarily unavailable.Retry with backoff.

An unknown path returns a plain-text 404, and a known path with the wrong method returns 405 with an Allow header; neither uses the envelope. Check the path and method against the endpoint list.

402: insufficient credits

Before a request runs, an upper bound of its cost is held against your balance and spending limits, and unused funds are released when it completes. For token-priced models the hold covers the endpoint's full context and output limits, so lowering max_completion_tokens doesn't shrink it. A 402 means the hold didn't fit one of these:

LimitHow to check
Available balance: your balance minus holds for other running requestsBilling
The key's spending limit for the current periodGET /key → limit_remaining
The account's spending limit for this currencyBilling

Add credits, raise the limit, wait for running requests to finish, or choose a model with a lower worst-case cost. See Limits.

429: rate limits

Retry-AfterMessageCause
Yesrate limit reachedYour key, account or key creator reached a rate limit.
Yestoo many authentication attemptsToo many failed authentications.
Norate limit reachedEvery endpoint tried was rate-limited by its provider before any output.

Mid-stream, a provider rate limit arrives as an in-band rate_limit_exceeded error instead.

503: unavailable

provider_unavailable is the most common routing failure: no endpoint could serve this exact request, or every endpoint tried failed before producing output. Causes, most common first:

  • The model ID is unknown, not published, not priced in your key's currency, or shared with an organization you can't access. These cases all return the same response, so it doesn't reveal whether an inaccessible model exists.

  • No endpoint supports a feature you asked for: tools, JSON output, reasoning controls, a cache directive that must be honored, or a media input. cache_control on messages or text parts doesn't cause this unless you set provider.require_parameters: true.

  • provider.require_parameters is true and no endpoint declares every sampler you sent.

  • Your provider filters (only, ignore, quantizations, regions, zdr, data_collection, max_price) exclude every endpoint.

  • Your max_completion_tokens exceeds every endpoint's maximum output.

An execution interruption while your connection remains open also returns 503 provider_unavailable, with message service temporarily unavailable. This can happen mid-stream, where the error is in the final event and HTTP remains 200. Your own cancellation remains 408 cancelled.

When a deadline expires and cleanup also cancels response delivery, the terminal error remains 408 timeout. The cleanup cancellation does not replace the original timeout with 408 cancelled.

provider_overloaded is narrower: endpoints match, but none has free capacity. Retry after a short backoff.

An endpoint can also be connected but fail to open its separate request connection before the attachment deadline. This returns 503 provider_unavailable if no eligible fallback succeeds. A request that wasn't sent to the provider isn't charged. Contact support with the request ID if this repeats.

Retry-After

Retry-After is set only on 429 rate_limited responses caused by your rate limits or by repeated failed authentications. The value is whole seconds, at least 1. Wait at least that long before the next request with the same key. For other retryable errors, use exponential backoff.

Errors in streams

Once a stream has started, its status is 200. A later failure arrives as a final chunk with a top-level error object, finish_reason: "error" and then data: [DONE], with no usage chunk. Streaming lists the in-band error types.

A client can retry a transient stream failure as a new request, but must handle the partial answer explicitly. Don't append a restarted answer to the partial output or replay tool effects blindly. The failed generation may have a charge; the retry is a separate generation with its own usage and cost.

Request and generation IDs

Every response carries X-Request-Id. Inference responses also carry X-Generation-Id once output starts; an error before any output has none.

  • For support, quote the X-Request-Id. Never send the key or the prompt.

  • For usage and cost, pass the generation ID to GET /generation. It's the authoritative record after a dropped connection or a mid-stream error.

  • Use a fresh X-Request-Id on every request, or omit it. IDs must be unique in your account, including for requests that failed: reusing one returns 400 invalid_request.

Debugging checklist

  1. Read the status and error_type. They narrow the cause to one row above. For a 400, the message names the field to fix.

  2. Check the body against Parameters. Most 400s come from reserved fields the API doesn't forward, such as n other than 1, plugins, store or native output-limit spellings such as n_predict. Other common causes: a models array with more than one entry, a :free or :nitro suffix, and max_tokens of 0 or above the ceiling. Ordinary generation settings the API doesn't recognize are forwarded; the upstream decides whether it supports them.

  3. Confirm the model is available to your key. GET /models lists only models with an endpoint priced in your key's currency. GET /models/{author}/{slug}/endpoints shows each endpoint's context_length, max_completion_tokens and supported_parameters.

  4. Check require_parameters. If you set it, compare every sampler you send with the endpoints' supported_parameters.

  5. Check prompt and output size. Trim the prompt or lower max_completion_tokens to fit the endpoints' limits.

  6. For a 402, check credits and spending limits, including holds for other running requests.

  7. Relax routing filters one at a time to find the one that excludes every endpoint.

  8. Look up the generation. If output started, GET /generation shows the outcome of each endpoint that was tried.

Retry strategy

For errors before streaming starts, retry only those that can succeed on a second try:

RetryDon't retry
429, 500, 502, 503; 408 timeout once400, 401, 402, 403, 404, 413, 415; 408 cancelled

Use exponential backoff with jitter, honor Retry-After, cap the number of tries, and send a new X-Request-Id on every try, or none.

import osimport randomimport timeimport uuid​import requests​RETRYABLE = {429, 500, 502, 503}​​class APIError(Exception):    def __init__(self, status, error_type, request_id):        super().__init__(f"{status} {error_type} (request {request_id})")        self.status, self.error_type, self.request_id = status, error_type, request_id​​def create_completion(body, max_attempts=5, base_delay=0.5, max_delay=30.0):    for attempt in range(max_attempts):        response = requests.post(            "https://api.numes.ai/api/v1/chat/completions",            headers={                "Authorization": f"Bearer {os.environ['NUMES_API_KEY']}",                # A fresh correlation ID per try, including retries.                "X-Request-Id": f"summary-{uuid.uuid4().hex}",            },            json=body,            timeout=630,        )        if response.ok:            return response.json()​        try:            error_type = response.json()["error"]["metadata"]["error_type"]        except (ValueError, KeyError):            error_type = "unknown"        retryable = response.status_code in RETRYABLE or (            response.status_code == 408 and error_type == "timeout" and attempt == 0        )        if not retryable or attempt == max_attempts - 1:            raise APIError(response.status_code, error_type, response.headers.get("X-Request-Id"))​        retry_after = response.headers.get("Retry-After")        if retry_after is not None:            delay = float(retry_after)        else:            # Full jitter: a random delay up to the exponential ceiling.            delay = random.uniform(0, min(max_delay, base_delay * 2**attempt))        time.sleep(delay)

The OpenAI SDKs retry some of these statuses themselves and resend the same headers each time. If you set X-Request-Id through the SDK's default headers, turn off SDK retries (max_retries=0 in Python, maxRetries: 0 in TypeScript) and use a loop like the one above, or leave X-Request-Id unset.

Next steps

Dashboard forwarding failures

Dashboard requests can return 504 upstream_timeout when the application service does not confirm a response before the forwarding deadline, or 503 upstream_unavailable for another forwarding failure. These are dashboard errors, separate from inference-provider availability. A failed mutation response does not prove rollback: retry the original reviewed input with its saved receipt. Include the request ID when reporting a persistent failure.