Web search
Ground any chat model in live web results — append :online, get the answer plus its sources, one search per request, exact cost in-band.
Append :online to any chat model and every request is grounded in a
live web search: current events, fresh docs, live prices. The gateway runs
exactly one search per request, gives the results to the model with
your question, and returns the answer with its sources as
annotations. You need no search API keys and no tool-call loop in your code.
r = client.chat.completions.create(
model="anthropic/claude-sonnet-5:online", # any chat model + :online
messages=[{"role": "user", "content": "What changed in the EU AI Act this month?"}],
)
print(r.choices[0].message.content)
for a in r.choices[0].message.annotations or []:
print("-", a.url_citation.title, a.url_citation.url)
print(r.usage.web_search_count, "search ·", r.usage.cost, "USD total")
:online works on every chat model, including models without tool support,
and with the router: gopura/auto:online routes each request to the right
tier and grounds it.
Sources
Every result the search returned comes back as a url_citation, in the
same shape OpenRouter uses:
"message": {
"role": "assistant",
"content": "The Commission published … ([europa.eu](https://europa.eu/…)).",
"annotations": [
{
"type": "url_citation",
"url_citation": {
"url": "https://europa.eu/…",
"title": "AI Act: implementation timeline",
"content": "The first obligations apply from …",
"start_index": 42,
"end_index": 69
}
}
]
}
contentis the excerpt the model saw, capped at 1,000 characters.- The model sees the results numbered
[1],[2], … in the same order asannotations. An answer that cites[2]refers toannotations[1]. start_index/end_indexappear only when the answer quotes that URL. The list always contains every source, whether or not the answer links to it, so you can show sources even when you tell the model not to link (seesearch_promptbelow).- Streaming: the sources arrive as one
delta.annotationschunk after the text and before the final usage chunk.
Options
The OpenRouter-compatible plugins field configures the search. :online
alone uses the defaults.
{
"model": "openai/gpt-5.6-terra",
"plugins": [{
"id": "web",
"max_results": 5,
"search_prompt": "Background research for the request above. Do not include links in your answer.",
"include_domains": ["europa.eu", "*.gov"]
}],
"messages": [ … ]
}
| Field | Default | Meaning |
|---|---|---|
max_results | 5 | Results to retrieve, 1–10. |
search_prompt | see below | The instruction placed in front of the results. Yours replaces the default entirely. |
include_domains | none | Only search these domains (example.com, *.substack.com, path prefixes, or .gov). |
exclude_domains | none | Never return these domains. Ignored when include_domains is set. |
engine | parallel | parallel, exa, or perplexity. See the table below. |
trigger | always | always runs one search on every request. model lets the model decide (0–3 searches, no sources). |
The default search_prompt gives the date of the search, tells the model to
use the results, and asks it to cite sources as markdown links named by
domain, such as [europa.eu](https://…). If your app renders sources from
annotations and you don't want links inline, set your own search_prompt.
Malformed fields return a 400. They are never silently ignored.
Engines
| Engine | Per search | Sources | How it runs |
|---|---|---|---|
parallel (default) | $0.00125 | ✓ | The gateway searches, then calls the model |
perplexity | $0.00625 | – | Search tool inside the upstream, forced to run once |
exa | $0.00875 | – | Search tool inside the upstream, forced to run once |
Prices include margin. parallel with trigger: "model" runs as an upstream
search tool and costs $0.00625 per search. Domain filters need parallel. With exa or
perplexity, the search still runs on every request, but the upstream
returns no source list. If you bring your own function tools on those
engines, the search is left to the model, so a tool loop is never forced.
Billing
With trigger: "always" (the default), each request is billed for one
search plus tokens. The search results become part of the prompt, so they
count as input tokens. The response reports the count and the
search-inclusive total:
"usage": {
"prompt_tokens": 3541,
"completion_tokens": 452,
"web_search_count": 1,
"cost": 0.006886
}
With trigger: "model", you pay per search the model actually runs:
typically 0–3, often none. In streams, the count appears on the final
usage chunk.
Failures
If the search itself fails or times out, the request fails with a 502 (or
504) and nothing is billed. A request that asked for grounding never
quietly gets an ungrounded answer.
Notes
- Only
function-type client tools are accepted. Provider-native search tool blocks are rejected; use:onlineinstead, which is metered honestly. - The search query is your latest user message. For follow-up questions that only make sense with earlier context ("and in France?"), make that message self-contained.
- The results are appended to the end of your last user message, so your system prompt and history stay unchanged and prompt caching keeps working on grounded requests.