⚡ New — Kimi K3 is live: bring your own Moonshot key →
Documentation

Routing & data residency

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.

Request extensions

FieldValuesWhat it does
optimizeprice (default) · latency · uptime · throughput · quality · autoRoute-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_weightsobject, 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").
routebestQuality-prediction routing (opt-in) — instead of picking a route for a model you chose, let BharatRouter pick the model predicted to give the best answer for this prompt. It classifies the prompt into a capability (code · math · multilingual/Indic · long-context · reasoning · general) and routes to the best-ranked model your account can actually reach. You can also request it by setting model to auto or best. See Quality-prediction routing below. v1 is a heuristic, not a trained router — be aware of the honest limits documented there.
providera provider id, e.g. krutrim, sarvam, bharatrouterPin one provider and skip dynamic routing entirely. Provider ids are listed at GET /v1/providers.
data_policyindia_onlyOnly India-resident routes are eligible. If none exists for the model, the request fails with no_route — it never silently leaves India.
excludearray 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_keyyour provider API keyPer-request BYOK: the call runs on your key and your provider billing. Never stored or logged. See BYOK.
image_optimizeauto (default) · offVision requests only. With auto, an inline image (a data:image/…;base64,… in image_url) whose longest edge exceeds the target model's own input ceiling is downscaled to that ceiling and re-encoded before we forward it. This is quality-neutral — the model provider downscales anything larger to about the same size anyway — so you send the identical pixels the model sees, minus the wasted base64 bytes. The win is a much smaller request body, so heavy OCR / multi-page document calls stop hitting the upstream timeout (and cost fewer image tokens on tile/patch-billed models). The cap is per-model (Claude sonnet-5/opus-4.8 & GPT/Gemini vision = 2576px long edge; older models less). We never touch images inside a tool result (computer-use / bounding-box coordinates stay aligned), never upscale, and never re-encode an already-in-range image. Set off to forward images byte-for-byte. Optionally add image_max_edge (a number) to cap tighter than the model ceiling. Reported in the x-br-image-optimized response header when it fires. In-memory only (DPDP — image bytes are never logged).

The route actually used is reported in the x-br-provider response header on every reply, streamed or not.

Example: cheapest India-resident route

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"},
)

Example: pin a provider

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"}]
  }'

Example: exclude a provider

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"}]
  }'

Auto presets (auto:*) — pick a use case, not a model

Every auto:* id below is a virtual model: it appears inthe catalog and GET /v1/models like any other id, you send it as model, and BharatRouter resolves it to a concrete model for that request — filtered by capability and context size, restricted to routes that are healthy and eligible foryour account (BYOK, residency, egress), then ordered by the preset's objective. Built for non-developers and agent configs: an agent pinned to auto:agent keeps working when a model dies or a better one ships — no config change.

Model idPicks
auto · auto:balancedCheapest model capable enough for the prompt (complexity-classified). The default smart choice.
auto:cheapestLowest-price model you can reach right now.
auto:fastestLowest observed latency (live per-route EWMA) among reachable models.
auto:bestBest predicted answer — alias of route: "best" below.
auto:agentTool-calling agents. Tool-capable models only, ranked by measured tool-call reliability; routes known to corrupt tool calls are excluded outright.
auto:codeCoding — the curated code ranking, tool-capable.
auto:reasoningHard analysis/math — thinking-capable models only.
auto:visionRequests with images — vision-capable models only.
auto:long-contextModels whose context window fits this request (with head-room), largest window first.
auto:sovereignIndia-resident routes only (DPDP posture) — cheapest compliant model. Unique to BharatRouter.

Fail-loud guarantee: a preset that can't be satisfied for your account returns404 no_route — it never silently substitutes. auto:sovereign never leaves India; auto:agent never hands an agent a model that can't call tools.

Observability: the resolution is reported in response headers —x-br-auto-preset (which preset ran), x-br-auto-model (the concrete model chosen) and x-br-auto-reason. Dry-run without inference viaPOST /me/route/preview with "model": "auto:agent".

curl https://api.bharatrouter.com/v1/chat/completions \
  -H "Authorization: Bearer br-..." -H "Content-Type: application/json" \
  -d '{
    "model": "auto:agent",
    "tools": [{"type": "function", "function": {"name": "send_message", "parameters": {"type": "object", "properties": {"text": {"type": "string"}}}}}],
    "messages": [{"role": "user", "content": "message the channel that the deploy is done"}]
  }'

Quality-prediction routing (route: "best")

Every other mode picks the best route for a model you chose. Quality routing goes one step earlier: you don't name a model, you ask for the best answer, and BharatRouter predicts which model that is for your specific prompt.

How v1 works: a fast classifier reads a few signals off your prompt — length, code and math cues, multi-step-reasoning hints, and Indic script / language — and picks acapability (code · math · multilingual · long-context · reasoning · general). Each capability has a curated model ranking; BharatRouter walks it and routes to the first model your account can actually serve, honouring your BYOK keys, data_policy, and exclude. So a plain platform-key account still lands on a strong Krutrim / first-party model, while a BYOK account gets the frontier model at the top of the list.

The decision is reported back in response headers so it's fully observable:x-br-route: best, x-br-route-capability, x-br-route-model(the model chosen), and x-br-route-reason. The usual x-br-providerstill reports the route within that model.

curl https://api.bharatrouter.com/v1/chat/completions \
  -H "Authorization: Bearer br-..." -H "Content-Type: application/json" \
  -d '{
    "model": "auto",
    "route": "best",
    "messages": [{"role": "user", "content": "Refactor this Python function and explain the bug: ..."}]
  }'

Honest limits (this is a v1 heuristic, not a trained router). It does not predict quality per prompt the way a learned router (e.g. Martian, Unify) does — those train on offline (prompt → best-model) labels. Here the classifier is hand-written keyword/character heuristics, and each capability maps to a static, human-curated ranking (seeded from our model-eval bench), refreshed by hand. It's a pragmatic first cut that's cheap on the hot path and fails open: any uncertainty (no prompt text, or nothing in the ranking is reachable) falls straight back to normal routing — it never blocks a request. The upgrade path is to replace the curated rankings with orderings derived from live evals.

How india_only works

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.

Failover & circuit breakers

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:

  • What counts as a failure (triggers failover): a connection/network error, or an upstream 5xx, 429 (rate limit),401 or 403. The gateway walks to the next step.
  • What does NOT trigger failover: a client-side 4xx(e.g. 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).
  • A moving average of latency and failure rate per route feeds theoptimize: latency and optimize: uptime modes.
  • After 3 consecutive failures a route's circuit opens and it stops receiving traffic; after 30 seconds a half-open probe lets one request through, and success closes the circuit again.
  • Failures on a BYOK key are attributed to that key, not the route — your expired key doesn't mark a healthy provider as down for everyone.

Two different "it didn't work" responses

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.

Reasoning models & max_tokens

Reasoning 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.

Saved fallback chains

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.

  • 1–10 steps. 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.
  • Steps that don't resolve yet (a BYOK key not saved, a BYOE endpoint not registered) are skipped at request time rather than failing the chain.
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
EndpointWhat it does
GET /me/routingList your org's saved chains.
GET /me/routing/:modelGet the chain for one model.
PUT /me/routing/:modelSave or replace the chain (owner/admin).
DELETE /me/routing/:modelRemove 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.

Streaming & metering

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.

Residency attestation & DPDP audit exports

Enforcing india_only is one half of a regulated deployment; proving it to a risk team is the other. Two org-scoped, read-only endpoints turn your metered traffic into auditor-ready evidence. Both are gated to owner/admin or a holder of the Analyst (usage:org:view) / Finance (cost:org:view) / Auditor (audit:view) capability, and never expose request or response content — BharatRouter is zero-retention by default.

Signed residency attestation

GET /v1/org/residency/attestation?window=30d&format=json returns a cryptographically signed attestation over the period: a residency summary (share of requests served on India-resident vs global infrastructure, computed over thefull window) plus per-request evidence (model, provider, residency, timestamp, correlation id). The signature is HMAC-SHA256 with a non-secret key_id fingerprint, so a recipient can verify the document was not altered.

GET /v1/org/residency/attestation?window=30d&format=json
→ { "payload": { "org_id": 59, "window": "30d",
                 "summary": { "total": 118934, "india_requests": 118934, "india_pct": 100.0,
                              "global_requests": 0, "global_pct": 0.0 },
                 "evidence": { "count": 20000, "capped": true }, ... },
    "alg": "HMAC-SHA256", "key_id": "…", "signature": "…" }

Pass format=report instead for a human-readable Markdown report a bank's compliance team can read and file — the residency summary, any global-routed requests called out with model and provider, and the signature block with verification notes.

DPIA export (DPDP SDF pack)

GET /v1/org/dpia?window=90d produces a Data Protection Impact Assessment for the period — the evidence an SDF's independent algorithmic auditor asks for. It reports which models/providers processed the org's data, the residency mix (India vs global, never roundingunknown up to India), your true content-retention posture (zero-retention unless your org has opted into content logging, in which case the bounded retention window is stated), analgorithmic-audit log (model × provider × residency × openness over the window), and agent (NHI) processing by agent_id. Add ?format=reportfor the Markdown DPIA. No global router can produce this from a US-domiciled control plane.