Provider Routing

Onlist routes every request to a platform provider that has an active listing for the model you asked for. By default Onlist picks for you. To take control, add an optional provider object to the JSON request body. It is the one Onlist-specific extension to the OpenAI/OpenRouter wire format: everything else in your request is unchanged.

curl https://onlist.io/v1/chat/completions \
  -H "Authorization: Bearer YOUR_ONLIST_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-4o",
    "messages": [{"role": "user", "content": "Hello from Onlist"}],
    "provider": { "sort": "price" }
  }'

The provider field

A bare string is shorthand for pinning a single provider:

"provider": "alice-shop"

is exactly equivalent to:

"provider": { "only": ["alice-shop"] }

Provider slugs are bare and must match ^[a-z0-9_-]{1,64}$. There is no sub-slug addressing: a slug containing a / returns 400 provider_sub_slug_removed.

Note

The provider field is read only on JSON endpoints (/v1/chat/completions, /v1/completions, /v1/embeddings, /v1/messages, /v1/responses, /v1/rerank, /v1/audio/speech, /v1/images/generations, /v1/videos). It is not read on multipart endpoints (/v1/images/edits, audio transcription/translation) or on the Gemini /v1beta path: only model is read there. This mirrors OpenRouter.

Field reference

Onlist honors a strict subset of OpenRouter's provider controls. Unsupported fields are accepted and silently ignored (never a 400) and reported in the X-Onlist-Warnings response header so you can detect them.

FieldTypeStatusBehavior
onlystring[]HonoredSingle pin: only only[0] is used; the rest are dropped and reported as ignored: provider.only[1:N]. For multiple providers, use allow.
sort"price" | "throughput" | "latency"Honoredprice = cheapest first, throughput = fastest first. latency is accepted as an alias of throughput (same ordering, no warning). A :floor/:nitro model suffix overrides this and the field is reported as ignored: provider.sort due to model variant slug. Any other value is ignored + warned.
orderstring[]HonoredProvider try-order priority for failover. Suppresses sticky-channel affinity when set. Non-string elements are skipped + warned.
allowstring[]HonoredRequest-level allowlist, merged with token/account preferences. Draining the pool returns 503 no_provider_after_allow_deny.
ignorestring[]HonoredRequest-level denylist. Deny wins over allow.
allow_fallbacksbooleanHonoredDefault true. false disables cross-provider failover (and the top-level models[] fallback list).
max_priceobjectPartialprompt/completion enforced fail-closed (drained pool → 503 no_provider_in_max_price); image/request ignored + warned.
zdrbooleanHonoredtrue routes only to listings whose channel declares zero data retention on the full upstream path (seller-declared, not verified). Drained pool → 503 no_zdr_provider_available. Also skips platform-side retention for the request.
data_collection"allow" | "deny"Honoreddeny routes only to listings whose channel declares prompts are not used for training. Drained pool → 503 no_provider_without_data_collection; any other value → 422 invalid_data_collection.
require_parametersbooleanNot implementedSilently ignored + warned.
quantizationsstring[]Not implementedSilently ignored + warned.
variantstringNot implementedDeprecated. Any value is ignored + warned.
preferred_max_latencynumberNot implementedSilently ignored + warned.
preferred_min_throughputnumberNot implementedSilently ignored + warned.

Honored fields

only: pin one provider

only pins the request to a single provider. Only the first element (only[0]) is honored. Any additional slugs are dropped and reported in X-Onlist-Warnings.

curl https://onlist.io/v1/chat/completions \
  -H "Authorization: Bearer YOUR_ONLIST_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-4o",
    "messages": [{"role": "user", "content": "Hi"}],
    "provider": { "only": ["alice-shop"] }
  }'
Caution

Unlike OpenRouter, only is not a multi-provider allowlist: Onlist honors only[0] and drops the rest. If you want to permit several providers, use allow instead. A pinned slug that does not exist returns 404 provider_not_found; a pinned slug that exists but has no listing for the model returns 422 no_listing_for_model_at_provider.

sort: price or throughput

sort chooses the ordering variant within the eligible pool.

"provider": { "sort": "price" }        // cheapest provider first
"provider": { "sort": "throughput" }   // fastest provider first

A model-slug suffix overrides this field: gpt-4o:floor forces price-first and gpt-4o:nitro forces throughput-first, regardless of any sort value you send. When you send both, the dropped field is reported as ignored: provider.sort due to model variant slug. See Model variant suffixes below.

OpenRouter's latency is accepted as an alias of throughput: both map to the same throughput ordering, and no warning is emitted. Any other sort value is ignored and reported in X-Onlist-Warnings.

Session stickiness

Onlist keeps a healthy conversation on the provider that is already serving it, so the provider's prompt cache keeps hitting. The session key is read from the request body:

EndpointSession key
/v1/chat/completions, /v1/responsesprompt_cache_key
/v1/messagesmetadata.user_id (what Claude Code sends)

Requests without a session key still stick, at the granularity of your API key, the model and the effective sort: everything that key sends for a model stays with one provider until the traffic pauses for ten minutes, which is about how long upstream prompt caches live anyway. Session-keyed stickiness lasts one hour after the last successful call. It follows the provider that actually delivered after a failover, and a stream that fails mid-way is not remembered. Hard constraints always win: allow, ignore, max_price, zdr and data_collection are re-checked on every request, and order or only bypass stickiness entirely. Changing sort starts a new session. When the sticky provider cannot be reused (cooling down, paused, or failing a constraint) the response carries X-Onlist-Cache-Continuity: broken and the warning prompt_cache_continuity_lost.

order: try-order priority

order is an ordered list of provider slugs. Failover advances through them in the order you give. Setting order also suppresses sticky-channel affinity, so each request re-evaluates from the top of your list.

"provider": { "order": ["alice-shop", "bob-relay", "carol-ai"] }

allow and ignore: allowlist and denylist

allow is a request-level allowlist (merged with any token/account preferences). ignore is a request-level denylist. When a slug appears in both, deny wins.

"provider": {
  "allow": ["alice-shop", "bob-relay"],
  "ignore": ["carol-ai"]
}

If your allow/ignore rules exclude every viable provider, the request returns 503 no_provider_after_allow_deny.

allow_fallbacks: control failover

Failover across providers is on by default. Set allow_fallbacks: false to require the first selected provider and disable cross-provider failover. This also disables the top-level models[] fallback list (see below).

"provider": { "only": ["alice-shop"], "allow_fallbacks": false }

max_price: USD-per-token caps

max_price filters the pool to providers priced at or below your caps, in OpenRouter's native USD-per-token format (for example 0.0000025 = $2.50 per million tokens).

"provider": {
  "max_price": { "prompt": 0.0000025, "completion": 0.00001 }
}
  • prompt and completion are enforced fail-closed: if no provider fits, the request returns 503 no_provider_in_max_price rather than silently overspending.
  • image and request are not implemented (the scope is chat-only): they are ignored and reported in X-Onlist-Warnings.
Caution

Because prompt/completion fail closed, an aggressive cap can drain the pool and return a 503. If you would rather get a result than a hard failure, widen the caps or omit them.

zdr and data_collection: privacy constraints

Both fields filter the pool on the seller's own data policy declaration for that listing, which covers the full path to that seller's upstream and is not verified by Onlist.

"provider": {
  "zdr": true,
  "data_collection": "deny"
}
  • zdr: true keeps only listings whose channel is declared zero data retention. Nothing eligible left returns 503 no_zdr_provider_available. The request is also excluded from Onlist's own temporary retention, which means it leaves no platform-side record to use as dispute evidence.
  • data_collection: "deny" keeps only listings whose channel is declared to withhold prompts from training. A channel that retains prompts but does not train on them is still eligible, matching OpenRouter. Nothing eligible left returns 503 no_provider_without_data_collection.
  • zdr: false and data_collection: "allow" are the same as omitting the field. Any other data_collection value returns 422 invalid_data_collection.
  • The filter runs after max_price and before allow/ignore, so a 503 here means nothing survived your other constraints too, not necessarily that the market has no such seller.

Read Privacy & data before relying on either field: it spells out what the declaration covers, who stands behind it, and the evidence trade-off of zdr: true.

Model variant suffixes

OpenRouter splits a model:suffix id into two kinds, and Onlist reads them the same way. Only the last : segment is inspected.

Routing shortcuts do not change which model you get. They are removed from the id before routing and only affect how the eligible providers are ordered:

SuffixEffect
:floorCheapest provider first (same as provider.sort: "price").
:nitroFastest provider first (same as provider.sort: "throughput").
:exactoHonored as the default ordering, which is already a composite quality score. No warning.
:onlineNot available on Onlist (it needs OpenRouter's web-search plugin). Dropped, and reported as ignored: model variant slug ":online" (not available on Onlist).

Static variants are separate models, not options. Every other : suffix, including :free, :batch, :thinking and anything Onlist does not recognize, stays in the model id and is routed as its own listing. deepseek/deepseek-v4:thinking and deepseek/deepseek-v4 are two different products, priced and sold separately.

The two kinds compose: in llama-3.3-70b:free:floor the :floor shortcut is stripped for ordering and llama-3.3-70b:free is the model that gets routed.

Caution

If no provider sells that exact id, the request returns 404 no_listing_for_model. Onlist does not quietly drop the suffix and serve you the base model, so a :thinking id can never come back as the non-thinking model. Call GET /v1/models for the ids that actually exist. Free variants (:free) cannot be published yet, so those ids normally have nothing behind them.

The models[] fallback list

Independently of the provider object, you can send a top-level models array: the OpenRouter-compatible fallback list. Onlist tries each model in order, rewriting the body model for each attempt, until one succeeds.

{
  "model": "openai/gpt-4o",
  "models": ["qwen/qwen3.8-27b", "deepseek/deepseek-v4-flash"],
  "messages": [{ "role": "user", "content": "Hi" }]
}

This list is gated by allow_fallbacks: with provider.allow_fallbacks: false, only the primary model is attempted and models[] is not used.

The X-Onlist-Warnings header

Whenever Onlist ignores a field or fires a routing notice, it reports it in the X-Onlist-Warnings response header. The value is a de-duplicated list joined by a comma and a space (, ). Parse it by splitting on , .

X-Onlist-Warnings: ignored: provider.sort, ignored: provider.quantizations, ignored: provider.only[1:3]

Entries take the form ignored: <field> (a dropped field) or a notice such as cool_pool_exhausted_fallback_used. The header is present only when something was ignored or a notice fired: a clean request returns no warnings header. Always check it in development to confirm Onlist honored what you intended. The notices are a closed set, listed in Limits & headers.

Next steps

  • Privacy & data handling: What the zdr and data_collection declarations actually mean (seller-declared, not platform-verified) and what they do to platform-side retention.
  • Errors: The 503 routing codes: no_provider_in_max_price, no_provider_after_allow_deny.
  • Limits & headers: X-Onlist-Warnings, X-Onlist-Route-Id, Retry-After, and why there are no X-RateLimit-* headers.
  • Models: Model id forms, onlist/auto, and a short version of the routing shortcut vs static variant suffix rules.