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": "wallet balance insufficient 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. 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 (e.g. { "provider_name": "alice-shop" }).
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 verbatim, 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 passthrough fields like provider_name.

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_balanceWallet balance is below the estimated request cost.
402quota_exhaustedThe per-token quota is exhausted.
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.
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_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": "wallet balance insufficient 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 or rewrite it. The error is relayed verbatim:

  • the HTTP status mirrors the upstream status;
  • message, type, code, and param are whatever the upstream returned;
  • metadata carries passthrough fields, and metadata.provider_name identifies the fulfilling provider.

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": "This model's maximum context length is 128000 tokens.",
    "type": "invalid_request_error",
    "code": "context_length_exceeded",
    "param": "messages",
    "metadata": { "provider_name": "alice-shop" }
  }
}
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.

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, param, or metadata:

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

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 needs a wallet top-up, 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).