Esc to close · ⌘K / Ctrl-K opens search anywhere
Most models in the catalog have more than one provider route. On every request the gateway picks among the healthy, eligible routes — by price unless you say otherwise — then fails over automatically if the chosen provider errors. You steer this with four optional request fields, accepted on both/v1/chat/completions and /v1/embeddings. They are BharatRouter extensions: the router consumes them and strips them before anything is forwarded upstream, so providers never see them.
| Field | Values | What it does |
|---|---|---|
optimize | price (default) · latency · uptime · throughput · quality · auto | Route-selection preference among eligible providers. price ranks by input + output ₹/Mtok summed (not input alone). latency uses a moving average of observed latency per route; uptime sorts by observed failure rate; throughput favours routes with rate-limit headroom plus low latency — it steers traffic off providers nearing their limits, paying a little more for sustained speed under load; quality sorts by a curated per-route quality rank (unranked routes fall behind ranked ones, then by latency so an unhealthy high-quality route still yields to a working one); auto blends all of them (reliability first, then a latency/price trade-off). Note: whatever the mode, routes whose circuit is open are always tried last — so the catalog's cheapest route can be skipped when it's currently unhealthy, which is why a price request may not land on the single cheapest provider. |
optimize_weights | object, e.g. {"latency":1,"throughput":1,"price":0.2} | Explicit dials — weight the four axes (price, latency, uptime, throughput) directly instead of picking a named mode. Overrides optimize when present. Each weight is a non-negative number; a missing or zero axis simply drops out of the blend, and a malformed/negative value is treated as 0 — so a bad dial safely falls back to the named mode rather than flattening the ranking. Use it when one named mode doesn't capture the trade-off you want (e.g. "fast and unsaturated, with a mild cost tiebreak"). |
provider | a provider id, e.g. krutrim, sarvam, bharatrouter | Pin one provider and skip dynamic routing entirely. Provider ids are listed at GET /v1/providers. |
data_policy | india_only | Only India-resident routes are eligible. If none exists for the model, the request fails with no_route — it never silently leaves India. |
exclude | array of provider ids, e.g. ["openai"] | Drop these providers from routing. An entry of the form provider/model (e.g. "openai/gpt-5") excludes that provider only for that one model — useful inside a fallback chain. A hard filter like data_policy: if it empties the pool the request fails with no_route. |
upstream_key | your provider API key | Per-request BYOK: the call runs on your key and your provider billing. Never stored or logged. See BYOK. |
The route actually used is reported in the x-br-provider response header on every reply, streamed or not.
from openai import OpenAI
client = OpenAI(base_url="https://api.bharatrouter.com/v1", api_key="br-...")
r = client.chat.completions.create(
model="qwen3-32b",
messages=[{"role": "user", "content": "Summarise this complaint in Hindi: ..."}],
extra_body={"optimize": "price", "data_policy": "india_only"},
)curl https://api.bharatrouter.com/v1/chat/completions \
-H "Authorization: Bearer br-..." -H "Content-Type: application/json" \
-d '{
"model": "gemma-4-e4b-it",
"provider": "krutrim",
"messages": [{"role": "user", "content": "namaste"}]
}'curl https://api.bharatrouter.com/v1/chat/completions \
-H "Authorization: Bearer br-..." -H "Content-Type: application/json" \
-d '{
"model": "qwen3-32b",
"exclude": ["openai"],
"messages": [{"role": "user", "content": "namaste"}]
}'Every route in the catalog carries a residency tag. data_policy: "india_only"filters the candidate set to India-resident routes before selection and failover, so the guarantee holds even mid-failover: a request can fail withno_route (HTTP 400) but it cannot be served from outside India. This is the enforcement point for DPDP-sensitive workloads — put it in the request, not in a policy document.
If the selected provider fails, the gateway retries the remaining eligible routes in preference order before giving up with all_routes_failed (HTTP 502). Health is tracked per route:
400 malformed request, 404, 422) is your request's problem, not the route's — it is returned to you as-is, unchanged, and the chain stops. A bad request won't be silently retried against a different provider (which would just fail the same way and cost you latency).optimize: latency and optimize: uptime modes.These are distinct on purpose — they tell you where the request stopped:
no_route (HTTP 400) — pre-flight: no eligible route could even be resolved, so nothing was dialed. Causes: a model with no configured provider, data_policy: india_only with no India route, a pinned provider that doesn't serve the model, or a chain whose every step is unresolvable.all_routes_failed (HTTP 502) — runtime: routes existed and were tried, but every one failed (per the failure rules above).model_not_found (HTTP 404) — the model id isn't in the catalog and isn't a discoverable provider/model BYOK id.max_tokensReasoning models (tagged reasoning in the catalog — e.g.gpt-oss-120b, the qwen3 family, gpt-5) spend part of the completion budget on hidden reasoning before the visible answer. A very small max_tokenscan therefore be consumed entirely by reasoning, leaving content empty. To avoid that footgun, the gateway raises a too-small max_tokens to a floor of512 for reasoning models only, and reports it in thex-br-reasoning-min-tokens response header. An unset max_tokens is left alone (the provider default is already reasoning-aware). The thinking trace, when a provider returns one, arrives in a separate reasoning_content field, not incontent.
Live circuit state is public at GET /health, and per-model 7-day stats atGET /v1/models/:id/stats.
Beyond per-request routing you can save a fallback chain for a model — an ordered list of steps that replaces that model's default routing for your whole org. A step is { model, provider? } (a bare string is shorthand for{ model }), and chains are cross-model first-class: "my own GPU → Krutrim → OpenRouter" is a valid chain. The same JSON shape is used everywhere — REST, MCP, and the dashboard.
model is a catalog id or aprovider/model-id BYOK id.provider is a catalog provider id (see GET /v1/providers) or abyoe:<slug> custom endpoint.PUT /me/routing/llama-3.1-8b-instruct
{ "steps": [
{ "model": "llama-3.1-8b-instruct", "provider": "bharatrouter" },
{ "model": "llama-3.1-8b-instruct", "provider": "krutrim" },
{ "model": "mistral/mistral-large-latest" }
] }
→ { "ok": true, "model": "llama-3.1-8b-instruct", "steps": [ ... ] } // effective within a minute| Endpoint | What it does |
|---|---|
GET /me/routing | List your org's saved chains. |
GET /me/routing/:model | Get the chain for one model. |
PUT /me/routing/:model | Save or replace the chain (owner/admin). |
DELETE /me/routing/:model | Remove it — routing returns to default (owner/admin). |
A per-request fallbacks array (same step shape, on the chat/embeddings body) overrides the saved chain for that single call. Chains can be shared and reused ascollections, and steps can point at your ownregistered endpoints. Agents manage chains overMCP with get_fallback_chains,set_fallback_chain and clear_fallback_chain.
For streamed requests the gateway injects stream_options.include_usageon providers that support it, and parses the usage block from the final SSE chunk — so streamed and non-streamed requests are metered identically, and yourcredit debits always reflect real token counts.