Smart Routing
How sansa-auto picks the best model for each request, including sticky sessions and reasoning behavior
Smart routing is what powers sansa-auto. Instead of hard-coding a model, you send your request to sansa-auto and Sansa picks the best underlying model for that specific request, balancing quality and cost, then proxies the call and returns the standard response shape plus a sansa metadata object.
This page covers how to call smart routing, what signals it uses, how to keep multi-turn threads on one model with session_id, and how reasoning interacts with routing. For the broader comparison of auto-routing vs. calling a specific model directly, see the Models docs.
How to call it
Set the model field to "sansa-auto". There is no separate endpoint or parameter — smart routing runs on the same POST /v1/chat/completions request.
{
"model": "sansa-auto",
"messages": [{"role": "user", "content": "Plan a week-long trip to Kyoto."}]
}Three values all trigger smart routing — they are equivalent:
model value | Behavior |
|---|---|
"sansa-auto" | Smart route. |
null | Smart route. |
| Omitted entirely | Smart route. |
Any other value is treated as a direct model call (e.g. "openai/gpt-5.4"), and no routing runs. An unrecognized ID returns 400 invalid_model. See Models for direct calls and provider failover.
See the code panel for full TypeScript, Python, and curl examples.
What the response tells you
A smart-routed response is a normal ChatCompletion, with three things to note:
model— the model that actually served the request (e.g."anthropic/claude-sonnet-4.6"), not"sansa-auto".usage.cost— total billed cost in USD for this completion at the served model's per-token rates.sansa— routing metadata:
interface SansaCompletionExtension {
// True for smart-routed (sansa-auto / null / omitted) requests.
routed: boolean;
// The model the router selected. Only set when routed is true.
routed_model: string | null;
// Routing latency in milliseconds. Only set when routed is true.
routing_latency_ms: number | null;
}When streaming, the sansa object appears on the first chunk (routing fields) and again on the final chunk that carries usage (including usage.cost). See Streaming.
What the router considers
For text requests, the router reads the request and selects a model based on:
- Message content — the conversation itself is the primary signal for matching a model's capability profile.
- Tools — if
toolsare present, only models that support tool calling are eligible.tool_choiceis also respected. - Reasoning — on
sansa-auto, omitting reasoning defaults toeffort: "high"; explicit"none"/enabled: falseopts out. See below. - Input modalities — image or audio inputs route to a multimodal-capable model automatically (see Multimodal requests).
Among the eligible models, the router ranks by capability match distance and breaks ties by cost. You are billed at the per-token rate of whichever model actually served the request.
Sticky sessions for multi-turn
By default, each sansa-auto request is routed independently. That is fine for one-shot calls, but multi-turn agents and chat threads usually want the same model on every turn. Provider prompt caches are tied to the model that wrote the cache; if turn 2 routes to a different model than turn 1, you lose those cache hits on the shared conversation prefix.
Pass a stable session_id (max 256 characters) in the body, or set the x-session-id header. Body wins when both are present. Reuse the same value on every turn of that conversation.
{
"model": "sansa-auto",
"session_id": "agent-thread-42",
"messages": [
{"role": "system", "content": "You are a coding agent."},
{"role": "user", "content": "Open the repo and summarize the layout."}
]
}What you get:
- The first request with that id runs normal smart routing and remembers the chosen model for your organization and that session id.
- Later requests with the same id reuse that model instead of re-routing, as long as the pin is still active (about 15 minutes from the last request that used it; each hit refreshes the window).
- If a later turn needs image or audio and the pinned model cannot serve it, Sansa routes again and updates the pin to the new model.
Use a distinct session_id per conversation or agent run. Sharing one id across unrelated threads will pin them all to the same model. Direct model IDs ignore this pin (there is no routing step); session_id may still be forwarded to the provider for its own cache session behavior. See Prompt Caching for cache control fields and usage reporting.
See the code panel for curl, TypeScript, and Python examples.
Reasoning with smart routing
This is the most important behavior to understand: on sansa-auto, omitting reasoning defaults to effort: "high", and that choice changes which models are eligible.
Omitted reasoning → defaults to high
If your request to sansa-auto includes neither a reasoning object nor reasoning_effort, Sansa normalizes the request to reasoning: { "enabled": true, "effort": "high" } before routing. The router then restricts candidates to the high reasoning tier and forwards that effort to the selected model.
{
"model": "sansa-auto",
"messages": [{"role": "user", "content": "What is the capital of France?"}]
}The body above is treated the same as an explicit high-effort request for routing and for the provider call.
Opt out of reasoning → non-reasoning models only
To keep routing on non-reasoning models (and disable extended thinking), send an explicit opt-out. Either form works:
{
"model": "sansa-auto",
"messages": [{"role": "user", "content": "What is the capital of France?"}],
"reasoning_effort": "none"
}{
"model": "sansa-auto",
"messages": [{"role": "user", "content": "What is the capital of France?"}],
"reasoning": { "enabled": false }
}reasoning.effort: "none" is also an opt-out and is left unchanged. Omitting reasoning is not an opt-out on sansa-auto.
Explicit effort → effort respected
If you provide reasoning — either the reasoning object or the top-level reasoning_effort — that value is kept. The router restricts candidates to the matching tier (none / low / high), and your requested effort is forwarded to the underlying model unchanged.
{
"model": "sansa-auto",
"messages": [{"role": "user", "content": "Prove that sqrt(2) is irrational."}],
"reasoning": { "effort": "high" }
}The two formats are equivalent:
{ "reasoning": { "effort": "high" } }{ "reasoning_effort": "high" }If both are present, the reasoning object takes precedence. See the Reasoning docs for the full parameter reference, response shape, and billing.
Notes and edge cases
- Omit vs opt-out — omitting both
reasoningandreasoning_effortonsansa-autodefaults to high. Use"none"orenabled: falsewhen you want non-reasoning models. - Direct model IDs — this default applies only to smart routing (
sansa-auto/null/ omittedmodel). Direct calls leave omitted reasoning unset. - Any
reasoningobject counts as present — including{ "enabled": false },{ "effort": "none" }, or{ "max_tokens": 1000 }withouteffort. Only a fully omitted pair of fields triggers the high default.
Multimodal requests
If any message contains image (image_url) or audio (input_audio) content, the request is routed to a multimodal-capable model automatically — you don't need to do anything beyond including the content. Content-based capability matching is not run for these requests; they're dispatched directly to a model that can handle the modality. See Completions for the input formats.
Failover
Smart routing produces a ranked list of candidate models, not just one. If the selected model's provider is rate-limited, down, or returns a transient error, Sansa automatically retries the next candidate. This is transparent to your client — you receive a single successful response, and usage (including usage.cost) reflects whichever model actually answered.
Pricing
Smart-routed requests are billed at the per-token rate of the model the router selected (or the fallback model, if failover occurred). Reasoning tokens count as output tokens. Token rates are listed on the Models page. See Models and Completions for the full billing flow.