Skip to main content

Routing preferences

By default the proxy picks an operator the same way the chat app does — favoring a fast, cheap, available operator for the model you asked for. Most tools never need to touch anything beyond the model id.

If you want finer control — from your own code, or a tool that exposes it — add an optional top-level provider object to the request body (OpenRouter-style). It's a proxy-only hint: the proxy reads it to pick an operator, then strips it before the request ever leaves your machine, so it never reaches the operator or the model. Any field that's missing or malformed is just ignored.

{
"model": "qwen3-coder",
"messages": [/* … */],
"provider": {
"only": ["1234", "5678:2"], // allowlist of operator refs
"ignore": ["9999:1"], // denylist (deny wins over allow)
"order": ["5678:2", "1234"], // explicit preference order
"allow_fallbacks": false, // false ⇒ pin to exactly `order`/`only`
"max_price": { "input": 2.0, "output": 6.0 }, // USD per 1M tokens, ceilings
"require_tools": true, // only route to operators with built-in tools
"sort": "latency", // price | throughput | latency
"relay": "off" // per-request transport-privacy override
}
}

A ref is either "<operatorId>" (any node run by that operator) or "<operatorId>:<nodeId>" (one exact node). You can find operator/node ids from GET /v1/zs/operators, or from the chat app's model picker.

Fields

FieldWhat it does
onlyRestrict routing to these operator refs.
ignoreExclude these operator refs — wins over only if both match the same ref.
orderTry these operators in this order before falling back to the rest.
allow_fallbacksfalse pins the request to exactly only/order — no fallback to anything else if they're all unavailable.
max_priceUSD-per-1M-token ceilings (input/output); operators above either are excluded.
require_toolsOnly route to operators that advertise the built-in tools the request needs.
sortRe-weight ordering among the remaining candidates — see below.
relayOverride transport privacy for this one request — see below.

If you have an active multi-turn session (a previous_response_id continuation), that pin always wins over provider — it has to, since the conversation lives in that specific operator's account. If your provider preferences would exclude it, that's a conflict, not a silent re-route (see the table below).

When preferences can't be satisfied

SituationWhat you get back
only/order with allow_fallbacks: false matched no available operator503 no_pinned_operator
A previous_response_id continuation is excluded by only/ignore/order400 pin_conflicts_with_continuation
max_price excluded every operator400 price_ceiling_exceeded
require_tools and no operator advertises the requested tools503 no_tool_capacity

sort — what to optimize for

By default the proxy bands candidates by a combined expected-response-time score (network latency + time-to-first-token + decode speed) and picks the cheapest within the fastest band. sort changes that:

ValueOptimizes for
latencyTime to first token only (ignores decode speed).
throughputDecode speed only (ignores time to first token).
priceCheapest available operator, full stop.
(absent)The combined default.

relay — transport privacy, per request

The proxy normally routes every request through another operator acting as a relay, so the operator actually running your model never sees your IP — see Privacy & security. relay overrides that for one request:

ValueEffect
"off"Send directly to the target — lower latency, but it sees your IP. Only honored if zs.allow_privacy_override is enabled (the default).
"required"Force a relay hop, failing with 503 no_relay_available rather than send directly.
"auto" / absentUse the configured default (zs.privacy).

You can also set this per request with the X-Zs-Relay header instead of the request body; the body wins if both are present.

What's next