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.
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
{ "error": { "code": 402, "message": "insufficient credits", "metadata": { "error_type": "insufficient_credits" } }}Before streaming starts, the HTTP status equals
error.code. After it starts, HTTP stays200; read the terminal chunk'serror.code.error.metadata.error_typeis the stable category. Branch on the status anderror_type, never onmessage, which can change.A
400 invalid_requeston a chat completion names the field and the rule it broke, for examplen must be 1; multiple choices are not supportedorn_predict must use the admitted chat messages or output limit fields. A request too long for every endpoint tried returnsrequest 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 asmin_p: "low"returns 400 before dispatch, as does a reasoningeffortthat 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 namedmax_tokensormax_completion_tokensfield.500messages are alwaysinternal server error.The request ID is in the
X-Request-Idresponse header, not in the body. Error bodies never includeopenrouter_metadata.
Status codes and error types
| Status | error_type | Meaning | What to do |
|---|---|---|---|
400 | invalid_request | The 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. |
400 | interception_limit_exceeded | The request is too large for your account's request policy to process. | Shorten the conversation, or ask an owner to simplify the policy. |
401 | invalid_credentials | The key was refused. | Check the key and the route's key kind. See Authentication. |
402 | insufficient_credits | The request's cost can't be covered by your balance or spending limits. | See 402: insufficient credits. |
403 | forbidden | Key management only: the management key's creator may not act on that key. | Use a key whose creator has the required organization role. |
403 | input_blocked | Your 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. |
403 | cors_forbidden | A browser sent the request from a disallowed origin. | Call the API from your server. |
404 | not_found | The model (endpoints list), key or generation doesn't exist for your account. | Check the ID. |
408 | timeout | The 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. |
408 | cancelled | The caller cancelled the request or response delivery failed. | Respect cancellation; start a new request only if you still need the result. |
413 | payload_too_large | The body exceeds the route's body limit. | Shrink the body. |
415 | unsupported_media_type | A JSON route received a body without Content-Type: application/json. | Set the header. |
429 | rate_limited | A rate limit was reached. | See 429: rate limits. |
500 | server | Unexpected error. | Retry with backoff. If it persists, contact support with the request ID. |
500 | interception_failed | Your account's request policy couldn't process this request or its response. | Contact support with the request ID. |
502 | provider_unavailable | Providers 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. |
503 | provider_unavailable | No endpoint can serve this request, or server execution was interrupted while you remained connected. | See 503: unavailable. |
503 | provider_overloaded | Matching endpoints have no free capacity. | Retry with backoff, or relax provider filters. |
503 | bitcoin_reference_price_unavailable | A BTC price couldn't be confirmed. The request wasn't sent or charged. | Retry with backoff. |
503 | routing_unavailable, service_unavailable, catalogue_unavailable, authentication_unavailable, rate_limit_unavailable, interception_unavailable | The 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:
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-After | Message | Cause |
|---|---|---|
| Yes | rate limit reached | Your key, account or key creator reached a rate limit. |
| Yes | too many authentication attempts | Too many failed authentications. |
| No | rate limit reached | Every 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_controlon messages or text parts doesn't cause this unless you setprovider.require_parameters: true.provider.require_parametersistrueand no endpoint declares every sampler you sent.Your
providerfilters (only,ignore,quantizations,regions,zdr,data_collection,max_price) exclude every endpoint.Your
max_completion_tokensexceeds 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-Idon every request, or omit it. IDs must be unique in your account, including for requests that failed: reusing one returns400 invalid_request.
Debugging checklist
Read the status and
error_type. They narrow the cause to one row above. For a400, the message names the field to fix.Check the body against Parameters. Most
400s come from reserved fields the API doesn't forward, such asnother than1,plugins,storeor native output-limit spellings such asn_predict. Other common causes: amodelsarray with more than one entry, a:freeor:nitrosuffix, andmax_tokensof 0 or above the ceiling. Ordinary generation settings the API doesn't recognize are forwarded; the upstream decides whether it supports them.Confirm the model is available to your key.
GET /modelslists only models with an endpoint priced in your key's currency.GET /models/{author}/{slug}/endpointsshows each endpoint'scontext_length,max_completion_tokensandsupported_parameters.Check
require_parameters. If you set it, compare every sampler you send with the endpoints'supported_parameters.Check prompt and output size. Trim the prompt or lower
max_completion_tokensto fit the endpoints' limits.For a
402, check credits and spending limits, including holds for other running requests.Relax routing filters one at a time to find the one that excludes every endpoint.
Look up the generation. If output started,
GET /generationshows 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:
| Retry | Don't retry |
|---|---|
429, 500, 502, 503; 408 timeout once | 400, 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 uuidimport requestsRETRYABLE = {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_iddef 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.