Routing Controls

By default the marketplace routes for you: it picks the cheapest healthy offer for the model you asked for and fails over transparently. See Health & Routing for that default path.

This page is about overriding it, and about seeing what it did. Every control here is optional — send none of them and behaviour is exactly the default.

Text only. These controls apply to /v1/chat/completions, /v1/completions, /v1/responses and /anthropic/v1/messages. Media routing (image, video, music) is always cheapest-first and ignores every field on this page.

Where the controls go

Which providers may serve a request are top-level body fields, beside the existing provider pin:

fieldmeaning
providerallow-list — only these providers are eligible (string or array)
providers_blockeddeny-list — these providers are refused
provider_orderpreferred attempt order

How to choose among the eligible ones lives in a si_route object: objective, price_tolerance_pct, max_latency_ms, cache.

Only objective (X-SI-Route-Objective) and price_tolerance_pct (X-SI-Route-Tolerance-Pct) have header forms, for clients that cannot add a field to an OpenAI-shaped body. Everything else is body-only — there is no header, and sending one has no effect, including no error, because a field that was never received cannot be reported as malformed.

json
POST /v1/chat/completions
{
  "model": "gpt-5.6-luna",
  "messages": [{ "role": "user", "content": "hi" }],
  "providers_blocked": ["openrouter"],
  "provider_order": ["venice", "openai"],
  "si_route": {
    "objective": "latency",
    "price_tolerance_pct": 15,
    "max_latency_ms": 4000
  }
}

None of these reach the provider: provider, providers_blocked, provider_order and si_route are all stripped before the upstream call.

objective — what to optimise for

ValueBehaviour
price (default)Cheapest estimated total cost first.
latencyFastest observed p50 first, within a price band.
reliabilityHighest observed success rate first, within the same band.
price_without_cacheCheapest first with cache-affinity routing disabled.

Header form: X-SI-Route-Objective: latency.

The price band is what a non-price objective ranks within. Offers whose estimated cost is inside price_tolerance_pct of the cheapest eligible offer are ranked by the objective; offers outside the band are not dropped — they are placed behind the band, cheapest-first, and remain reachable on failover. The default band is per-objective; override it with si_route.price_tolerance_pct or X-SI-Route-Tolerance-Pct, and it is clamped server-side.

A tolerance is therefore an ordering preference, not a spend cap. price_tolerance_pct: 0 does not bound what a request can cost.

Choosing a non-price objective changes what is reserved, and on some rails what is charged. Because the router may now select an offer that is not the cheapest, funding reserves the most expensive offer it could select and caps the request at that amount. Two consequences:

  • On credit and on-chain balances the reservation is released on completion and you are charged for what you used — but a buyer close to their balance may see 402 on a request that objective: "price" would have served.
  • On x402 and MPP the payment is captured up front at the challenged amount, which is computed from that same maximum. The surplus is returned as marketplace credit, not as a refund to your wallet.

provider_order — try these providers first

A top-level field. An ordered list of provider families ("venice") or hosts ("api.venice.ai"). Named providers are attempted first, in the order given; everything else follows in the objective's own order.

json
"provider_order": ["venice", "openai"]

Ordering is not eligibility. It reorders offers that already passed every filter — price ceilings, minimum discount, spend caps, health, wire availability — and can never resurrect an excluded offer, nor exclude one you did not name. A provider you do not list is still attempted, just later.

Two costs worth knowing:

  • Overriding the default order forfeits the cache-affinity tiebreak, so prompt-cache hit rate (and therefore price) may fall.
  • If the order can actually promote a pricier offer, the request funds like a non-price objective — see the reservation note above. If it cannot (you named a provider that serves none of this model's offers, or only one provider serves it), it costs nothing.

providers_blocked — never send this request to these providers

json
"providers_blocked": ["openrouter", "api.venice.ai"]

Matched on provider family or host, the same spellings provider_order accepts. A blocked provider is excluded from every rail — the marketplace, your BYOK priority provider, and your fallback key — because a block is a statement about which providers may see the request, not about whose key pays for it.

This one does not fail open. Every other field here is a preference, and an unreadable preference is ignored. A block is a refusal, so an unreadable one is refused with 400 invalid_request rather than dropped: silently ignoring it would send the request to the provider you just ruled out. A bare string ("providers_blocked": "venice") is accepted as a one-element list. An empty array means "no block", never "block everything".

The list is capped at 32 entries; a longer one is refused rather than silently truncated, because a partially-applied block is the same failure as a dropped one.

If the block leaves no eligible offer, the request fails with 503 no_healthy_sellers.

There is also a key-scoped blocklist set on the API key itself. The two are additive — the per-request list can only narrow further — but they are not equivalent, and the difference matters for data residency:

top-level providers_blockedkey-scoped blocklist
spellingsprovider family or hosthost only (a family name is rejected at key creation with 400 invalid_scope_providers_blocked)
rails boundmarketplace, BYOK priority, buyer fallbackmarketplace only

So a key-scoped block still leaves your own BYOK provider reachable. If the requirement is "this request must never reach provider X", use the per-request field.

max_latency_ms — a hard ceiling

Offers whose observed p50 exceeds this are excluded outright (not merely deprioritised). An offer we have no latency history for is not excluded — we do not have evidence to exclude it on.

Seeing what the router did

Every text response carries routing telemetry:

HeaderMeaning
x-si-served-byWhich rail served it: marketplace, preferred_key, fallback_key.
x-si-marketplace-attemptsHow many offers were dialled.
x-si-attemptsWhich offers failed, as provider:offerId:reason, comma-separated. Empty when nothing failed.
x-si-buyer-cost-microWhat this request cost you, in integer microdollars (1 USD = 1000000).
x-si-truncated1 when the response was cut short by a deadline — the only signal that a completion is incomplete.
x-si-adapted-paramsParameters we changed or could not carry to the provider.

x-si-attempts answers the question a count cannot: four attempts can be four offers on one provider, which is a flaky seller, not a vendor outage. Reason codes are coarse and stable — unavailable, rate_limited, not_found, upstream_error, request_rejected, timeout, error — and the provider's own error text is never echoed into a header.

Two deliberate redactions. A provider we cannot classify appears as unknown rather than as a raw name. And on the unavailable class the offer id is replaced with -: that class is uniquely the auth/billing one, so pairing it with an offer id would let anyone with an API key enumerate which sellers' credentials are dead or out of credit.

The list is capped, with a +N more marker when a walk was longer than the cap. Under some operator cache configurations the attempt headers are suppressed and ship empty — they can otherwise reveal routing decisions influenced by other tenants' traffic.

Streaming responses cannot carry the cost header (headers freeze at the first byte). Use the per-request record instead:

bash
curl https://api.surplusintelligence.ai/v1/requests/$REQUEST_ID \
  -H "Authorization: Bearer $SURPLUS_API_KEY"

This endpoint needs the requestlog.read scope, which an inference key does not carry by default — use a key provisioned with it. It returns the model, the serving provider, which rail served it, the settled cost, and the latency breakdown — route_overhead_ms (our own time before opening the upstream socket), first_token_ms (time to the first byte we delivered to you, streaming only), and upstream_total_ms. GET /v1/requests lists them.

Browser clients

x-si-route-objective, x-si-route-tolerance-pct and x-min-discount are accepted on cross-origin preflight, and x-si-attempts, x-si-buyer-cost-micro and x-si-truncated are exposed so a browser can read them. The remaining x-si-* telemetry headers are readable server-side only.

Reasoning with tools

Some models reject tools and reasoning together on the ordinary chat endpoint — the provider's own error says so, and names the remedy. Left alone, the only way to serve such a request on that endpoint is to turn reasoning off, which answers the question by deleting the thinking behind it.

Where the seller's offer supports it, the marketplace serves these requests over the provider's Responses endpoint instead and translates the answer back into the chat shape you asked for. You do not opt in and the request body does not change; the response is the same chat.completion object, now with the reasoning intact.

Two consequences worth knowing:

  • It can cost more. Reasoning tokens are reported inside completion_tokens and billed at the offer's output rate, so a request that previously ran with reasoning suppressed now generates tokens it did not before. That is the point — you are getting what you asked for — but the bill reflects it.
  • It never makes a request worse. If the provider turns out not to serve that endpoint for the model, the request is re-sent on the ordinary chat wire and served exactly as it is today. The seller is not penalised for it.

What these controls are not

min_discount (the /min{N} URL segment, the X-Min-Discount header, or a key's min_discount_pct) measures the discount against the model's external list price — what you would pay going direct to a reference provider. It is a floor on how much cheaper than list an offer must be. It is not a bound on how much you might overpay relative to other offers inside the marketplace, and it does not guarantee you got the cheapest offer available. For that, use objective: "price" (the default) or read Provider Performance and pin with provider_order.

See Minimum-Discount Routing for the full semantics.