How routing works — and how to see it
When you request class/standard or
iig/auto, the router picks the concrete model for you — from
a candidate ladder, at your account's posture and preferences.
GET /v1/route-preview shows that decision before you
send anything: the resolved ladder, which candidate would serve
right now, and a machine-readable reason for every ordering and every
exclusion. The console's Routing page and
the playground's route badge both render from it.
1 · Postures: how a class trades quality for cost
Every account carries a cost sensitivity (set on the
console's Settings page, or POST /v1/account/preferences).
It orders the candidates a class/<name> id admits:
- quality — the ladder's best-first order stands: the strongest candidate serves, the rest are fallbacks.
- balanced (the default) — the head is skipped to spread load: the second real candidate serves first and the skipped head retries last. With only one real candidate admitted it stays first — the built-in echo backstop never outranks a live provider.
- low-cost — real candidates re-order by ascending effective price; echo backstops always come last.
The echo caveat that motivated this page: on a router
with no real provider available for a class, the deterministic
echo backstop answers — placeholder text at HTTP 200, not a live
model. The preview makes that visible before a single call: the
would-serve row names an iig/… echo and the reason chain
carries echo_backstop, with excluded[] naming
why each real candidate is out (usually
no_provider_key).
2 · Floors: privacy and jurisdiction constraints
Two request-side constraints filter candidates before any provider contact, and both fail closed — the router refuses rather than quietly serving below a floor:
provider.min_privacy— a retention floor (no-store,zdr,confidential): only routes with a cited tier at or above it may serve. The privacy meta idsiig/zdrandiig/confidentialpin their floor intrinsically.provider.jurisdiction—euor a country code: only providers with a sourced headquarters claim matching it qualify; unsourced never does.
Preview either with the matching query parameters:
/v1/route-preview?model=class/standard&min_privacy=zdr
— excluded candidates come back with reason privacy_floor
or jurisdiction.
3 · Preferences: your stored routing defaults
The console Settings page (or
POST /v1/account/provider-prefs) stores a default routing
block applied to every request that carries none of its own:
order (preferred provider try order),
only/ignore (allow/deny lists — deny wins),
sort (price or throughput), and
allow_fallbacks:false (pin the head; never roll). The
preview applies the same stored block, so what it shows is what a
plain request would do — its reason chain names each preference in
force (preference_only, preference_order,
sort, no_fallbacks, …).
4 · Reading the preview
GET /v1/route-preview?model=class/standard
[&posture=quality|balanced|low-cost] preview a posture BEFORE saving it
[&min_privacy=…] [&jurisdiction=…] preview request-side floors
Authenticated (your API key, or the console session), read-only: no reservation, no upstream call, nothing billed. The answer:
wouldServe— the candidate that answers a call sent right now (null when nothing can serve — seerefusal).candidates[]— the full try order. Each row:position(0 serves; the rest is the rollover order on retryable upstream failures), the provider and model, the effective price pair, the resolvedprivacyTier, andhealthy— the live per-route health state (an unhealthy route is demoted at equal sort rank, never gated: it still serves when it is all there is).reasons[]— machine-readable{code, detail}pairs explaining the order: the posture rung applied, floors and preference blocks in force, sort, health demotions,echo_backstop, and the rollover depth.excluded[]— every ladder candidate that cannot serve, each with the first failing filter named:no_provider_key,privacy_floor,jurisdiction,provider_excluded,retired,not_in_catalog— or the pipeline cutsrollover_limitandno_fallbacks.
The preview and the serving doors run the same
resolver — one code path, verified by tests that pin the preview to the
dispatch decision — so what you see is what a call gets, including the
?posture= switcher's what-if view (marked
postureSource: "preview", nothing saved).