Errors

Onlist returns errors in the OpenAI- and OpenRouter-compatible envelope. If your client already handles OpenAI errors, it handles Onlist errors. The one rule worth internalizing: branch on the string code, not on the HTTP status, and never on a numeric value inside code.

The error envelope

Every error response is a JSON object with a single top-level error field:

{
  "error": {
    "message": "insufficient credits for estimated request cost",
    "type": "onlist_error",
    "code": "insufficient_balance",
    "param": "",
    "metadata": null
  }
}
FieldTypeNotes
messagestringHuman-readable. May carry a (request id: <id>) suffix for tracing. On a relayed upstream 4xx it starts with Provider <name> returned: . Do not parse it.
typestringError family label: a string, not a status. See type families.
codestringMachine-readable slug. Branch on this.
paramstring | nullOpenAI-compat. Usually empty on Onlist pre-relay errors.
metadataobject | nullPresent only on upstream-relayed provider errors. provider_name names the fulfilling provider; on an upstream 5xx, raw carries the upstream envelope verbatim.
Caution

code is always a machine-readable string such as insufficient_balance or rate_limited, never the numeric HTTP status (402, 429). The HTTP status and the code slug are two separate signals. Switch on error.code in your client; treat the HTTP status as a coarse category.

type families

type tells you where the error came from, not what went wrong:

  • onlist_error: an Onlist pre-relay error. The request never reached a provider: it was rejected during validation, authentication, billing, or routing. All the slugs in the reference table below carry this type.
  • the upstream type, echoed: the request reached a provider's upstream provider and the provider returned an error. Onlist relays that error with its fields intact (only message changes: prefixed on a 4xx, replaced on a 5xx, see below), so type is whatever the upstream sent (e.g. invalid_request_error), defaulting to upstream_error when the upstream gives nothing usable. See upstream passthrough.

param is an OpenAI-compatibility field. It is usually empty for Onlist pre-relay errors and may be populated on a relayed upstream error. metadata appears only on upstream errors and carries provider_name, plus raw on a 5xx.

Error code reference

The following slugs are produced by Onlist before relaying (all type: onlist_error). The HTTP status is the coarse category; the code is the precise reason.

HTTPcodeMeaning
400invalid_requestMalformed request, or a missing/invalid model or messages.
400provider_sub_slug_removedA / appeared in a provider slug. Sub-slug addressing is removed: use a bare slug.
401(unauthorized)Missing/invalid bearer token, or a stale platform test token.
402insufficient_balanceRemaining credits are below the estimated request cost.
402quota_exhaustedThe per-token quota is exhausted.
403model_not_allowedThe API key only allows selected models and this model is not one of them. The message lists the allowed models; change them under “Only allow selected models” on the key.
404provider_not_foundThe pinned provider slug does not exist.
404no_listing_for_modelNo active listing exists for the requested model.
422author_mismatchThe model's author segment disagrees with the registered author.
422no_listing_for_model_at_providerThe pinned provider has no listing for this model.
422invalid_data_collectionprovider.data_collection was neither allow nor deny.
429rate_limitedPer-token rolling-window cap hit. Carries Retry-After.
503no_provider_in_max_priceprovider.max_price filtered out the entire pool.
503no_provider_after_allow_denyallow/ignore preferences excluded every provider.
503no_zdr_provider_availableprovider.zdr: true and no eligible listing declares zero data retention.
503no_provider_without_data_collectionprovider.data_collection: "deny" and no eligible listing declares that prompts are withheld from training.
503no_provider_availableAll matching listings have disabled channels. Carries Retry-After on cooldown.
500internal_errorInternal routing, billing, or database failure.

A 401 response uses the standard envelope but does not commit to a stable code slug: treat any 401 as an authentication failure and check your token. See Authentication.

Note

These three statuses split the routing failures cleanly. 404 means the model or pinned provider does not exist at all. 422 means the request shape is valid but no listing matches (author mismatch, or a pinned provider that does not carry the model). 503 means listings exist but none is currently eligible (filtered by your price/allow/deny constraints, or every channel is disabled or cooling down).

A few examples

402 insufficient_balance:

{
  "error": {
    "message": "insufficient credits for estimated request cost",
    "type": "onlist_error",
    "code": "insufficient_balance",
    "param": ""
  }
}

400 sub-slug removed:

{
  "error": {
    "message": "provider sub-slug addressing is removed; use a bare provider slug",
    "type": "onlist_error",
    "code": "provider_sub_slug_removed",
    "param": ""
  }
}

503 no_provider_in_max_price:

{
  "error": {
    "message": "no listing within the requested provider.max_price",
    "type": "onlist_error",
    "code": "no_provider_in_max_price",
    "param": ""
  }
}

Upstream passthrough

When the request reaches a provider's upstream provider and that provider returns an error, Onlist does not wrap it. The HTTP status mirrors the upstream status; type, code and param are whatever the upstream returned; and metadata.provider_name identifies the fulfilling provider. What happens to message depends on the status class:

  • 4xx keeps the upstream's text in full, prefixed with Provider <name> returned: . It is the upstream's explanation of your request (a bad parameter, a context window overflow, a rate limit) and it is what you need to fix it. The prefix is for anyone who only sees message, such as a CLI or a chat UI: without it, a provider's own "insufficient quota" reads like a message about your Onlist credits.
  • 5xx gets message replaced by the fixed string Provider returned error, and the upstream's original envelope is placed verbatim in metadata.raw. A 5xx body is a gateway or infrastructure error page in whatever language and format that provider's stack uses; nothing in it is for your code to act on, so you get a stable message to log and the original to inspect. The status, type and code are still the upstream's.

One exception: on a streaming request, once heartbeat comments (: ONLIST PROCESSING) have started, the 200 status line is already committed and cannot change. A terminal upstream failure after that point is delivered as an in-stream error frame instead of an HTTP error status, and no Retry-After header is possible. The frame shapes are documented in Streaming.

The presence of metadata.provider_name is your signal that an error came from upstream rather than from Onlist's own pre-relay checks.

{
  "error": {
    "message": "Provider alice-shop returned: This model's maximum context length is 128000 tokens.",
    "type": "invalid_request_error",
    "code": "context_length_exceeded",
    "param": "messages",
    "metadata": { "provider_name": "alice-shop" }
  }
}

The same provider returning a 502 from its gateway arrives with the generic message and the original body under raw:

{
  "error": {
    "message": "Provider returned error",
    "type": "upstream_error",
    "code": "bad_response",
    "param": "",
    "metadata": {
      "provider_name": "alice-shop",
      "raw": "{\"error\":{\"message\":\"upstream gateway timeout\",\"type\":\"upstream_error\",\"code\":\"bad_response\"}}"
    }
  }
}
Tip

If type is onlist_error, the request was rejected by Onlist before relay: match on the slugs in the table above. Otherwise the error came from upstream: metadata.provider_name is set and code/type follow the upstream provider's own conventions, which Onlist does not normalize (only message changes: prefixed on a 4xx, replaced on a 5xx).

Which provider rejected it

Onlist changes two things at most: it adds metadata.provider_name under error.metadata, and it marks message: on a 4xx by putting Provider <name> returned: in front of the upstream text, on a 5xx by swapping it for the generic string while keeping the original body in metadata.raw. The upstream status and every other error field are left alone. Onlist is a multi-provider marketplace, so a 400 may come from the platform's pre-relay checks, from a provider's own gateway, or from the upstream behind that provider, and your next move depends on which: retry elsewhere, or exclude this provider.

The attribution sits at error.metadata.provider_name on all three protocol families (OpenAI, Anthropic, Gemini), whatever else their error objects contain. Non-streaming responses also carry X-Onlist-Provider and X-Onlist-Attempts saying the same thing; see Limits & headers.

The field, and with it the 4xx message prefix, is absent when:

  • the upstream body is not a standard JSON error envelope — an HTML error page from a reverse proxy, plain text, a JSON array. Those are relayed verbatim with no structural rewriting;
  • the upstream already set metadata.provider_name itself. Upstream attribution wins. metadata.raw follows the same two rules: it is only added on a standard envelope, and an upstream that already set it wins;
  • the error is Onlist's own (type: "onlist_error"): authentication, insufficient credits, a constraint that emptied the candidate pool. There is no provider to attribute;
  • it is the terminal failure frame of a /v1/responses stream after heartbeats began. That protocol's failure event is shaped as response.error, which has no matching metadata slot; a non-standard key there breaks strict clients' event parsing.

Streaming responses never carry X-Onlist-Provider in their HTTP headers — those are committed before Onlist knows which provider finishes — so on a stream, read the attribution from the error envelope.

Empty upstream responses

A provider can return a well-formed 200 and then produce nothing at all: no content, no usage. Onlist treats that as a failure rather than a successful empty answer. The call is not charged, the provider goes on cooldown, and you get a terminal error carrying code: "empty_response". On a streaming request it arrives as an in-stream error frame (see Streaming):

{
  "error": {
    "message": "upstream returned an empty response (no output produced); you were not charged",
    "type": "upstream_error",
    "code": "empty_response"
  }
}

The same empty_response slug shows up as other.error_code on that request's row in Usage & cost, so a call you saw fail in real time is still explainable afterwards.

The Anthropic /v1/messages variant

The Anthropic-native endpoint /v1/messages uses Anthropic's own error shape, not the OpenAI envelope. It has only type and message inside error. There is no code or param:

{
  "type": "error",
  "error": {
    "type": "overloaded_error",
    "message": "Overloaded"
  }
}

The one exception is the provider attribution described above: on a failed relay, error also carries metadata.provider_name. Anthropic SDKs ignore fields they do not recognize, so the extra key does not affect parsing.

If you point an Anthropic SDK at Onlist, branch on error.type for this endpoint. All other endpoints (/v1/chat/completions, /v1/completions, /v1/responses, /v1/embeddings, and the rest of the OpenAI surface) use the standard envelope with the string code.

Retrying

A 429 (rate_limited) and a 503 on channel cooldown both carry a Retry-After header giving the number of seconds to wait before retrying. Honor it: back off for at least that long rather than retrying immediately.

Caution

X-RateLimit-* headers Onlist does not emit X-RateLimit-* headers. Retry-After (on 429 and 503) is the only rate-limit and cooldown signal. See Limits & headers for the full header set and the rate-limit and cooldown behavior.

Other statuses are not automatically retryable. A 402 means you need to buy more credits, a 400 or 422 needs a fix to the request, and a 404 means the model or provider is not available. A 500 (internal_error) is safe to retry after a short backoff.

Next steps

  • Limits & headers: Retry-After, X-Onlist-Route-Id, X-Onlist-Warnings, and the rate-limit and cooldown behavior.
  • Authentication: Bearer tokens, the normalized x-api-key / x-goog-api-key headers, and 401 handling.
  • Provider routing: The provider object (allow, ignore, max_price) and the failures that surface as 503.
  • OpenAPI spec: Full endpoint reference with request and response schemas (YAML download).