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

Web search — :online

By default a model answers only from its training data — it can't see today's web. Add :online to the model id (or send the web plugin) and BharatRouter makes the request live: it searches the web, feeds the model cited results, and the model answers from them. It works for every model we route — Krutrim, Sarvam, GLM, GPT, Claude — so India-first models get to be online too.

OpenRouter-compatible. The :online suffix, the web plugin, and the url_citation annotation shape all match OpenRouter's web plugin — so migrating is a base-URL swap, not a rewrite.

Enable it

Method A — the :online suffix

Append :online to any model id. Simplest possible switch:

curl https://api.bharatrouter.com/v1/chat/completions \
  -H "Authorization: Bearer br-..." -H "Content-Type: application/json" \
  -d '{
    "model": "glm-4.6:online",
    "messages": [{"role": "user", "content": "Who won the F1 race last weekend?"}]
  }'

Method B — the web plugin (tunable)

For control over the engine, result count and domain filters, send a web plugin instead of the suffix:

{
  "model": "glm-4.6",
  "plugins": [{
    "id": "web",
    "engine": "exa",
    "max_results": 5,
    "search_prompt": "Some relevant web results:",
    "include_domains": ["reuters.com"],
    "exclude_domains": ["reddit.com"]
  }],
  "messages": [{"role": "user", "content": "..."}]
}
FieldDefaultMeaning
engineexaSearch engine — exa, brave, or tavily (BYOK; see below).
max_results5How many results to fetch and inject (1–20).
search_prompt"Some relevant web results:"The preamble prepended to the injected context block.
include_domainsRestrict results to these domains.
exclude_domainsDrop results from these domains.

Two ways it searches

BharatRouter is native-first: if the model you routed to runs web search itself and you've brought a key for it, we let the provider search server-side; otherwise we run our own search layer and inject the results. You don't choose — the router picks the best available path per model.

1. BharatRouter search layer (default, every model)

We call a hosted search engine, fetch the top results with their text, and inject them as a cited context block ahead of your messages — then route to whatever model you asked for. This is how a model with no browsing of its own (GLM, Krutrim, Sarvam…) goes live.

The default engine is Exa (the same engine OpenRouter's plugin uses, so answers behave the same on migration). Brave (independent index) and Tavily are also available. All three are BYOK — save your engine key once under its id (exa/brave/tavily) on the dashboard BYOK tab or PUT /me/byok/exa, and :online uses it. The engine bills your own account.

2. Provider-native search (native-first, where supported)

Some providers run web search inside their own API, tuned to their model. Where the routed model supports it and you've saved that provider's key, BharatRouter forwards the provider's native search instead of our layer — you get the provider's first-party browsing. This is BYOK-only: we hold no serving key for these providers, so native search runs on your key and the provider bills you directly (a BYOK request is ₹0 on our side).

ProviderModelsSurface
Anthropic claude-opus-4.8, claude-sonnet-5, claude-haiku-4.5 Native Messages API (/v1/messages). Claude runs its web_search tool server-side. On the OpenAI-compatible chat surface, Claude uses the search layer instead (the compat API can't accept the native tool).
OpenAI gpt-4o-search-preview, gpt-4o-mini-search-preview Chat completions + the Responses API. Sent as web_search_options. Regular gpt-4o/gpt-5 don't search natively — use them with the search layer.

Example — Claude with native search via the Messages API (your saved Anthropic key, or a forwarded Claude session):

POST https://api.bharatrouter.com/v1/messages
{ "model": "claude-sonnet-5:online",
  "max_tokens": 1024,
  "messages": [{"role": "user", "content": "Latest on the RBI repo rate?"}] }

Citations

Results come back as url_citation annotations on the assistant message — the same shape as OpenRouter, so existing citation-parsing keeps working:

"annotations": [
  { "type": "url_citation",
    "url_citation": { "url": "https://...", "title": "...", "content": "..." } }
]

On streaming responses the annotations arrive in a leading chunk; provider-native search emits citations in the provider's own shape.

Response headers

Every :online response is tagged so you can see what happened:

HeaderExampleMeaning
x-br-web-searchexa:5Search layer ran on exa and injected 5 results.
x-br-web-searchanthropic:nativeClaude ran its own native search server-side.
x-br-web-searchopenai:nativeOpenAI ran native search via web_search_options.
x-br-web-searchexa:no-key:online asked for, but no engine key saved — answered without browsing (fail-open).

Residency & privacy

Web search sends your query to a search provider, all of which are hosted outside India, so an :online request is tagged residency: global — enabling search means the query text leaves India. A request pinned data_policy: india_only should not use :online. Inference itself stays zero-retention by default; the injected search context follows the same rule.

Fail-open

Search never breaks your completion. If the engine errors, returns nothing, or no key is configured, the model answers without browsing and the x-br-web-search header tells you why (:error, :0, :no-key). You always get an answer.