Skip to main content

Web Search

The Router can search the web and add the results to the model's context, so the answer reflects current information and carries citations. It works with any model on POST /v1/chat/completions and POST /v1/messages, because the search runs in the Router, not in the model.

There are two ways to ask for it:

Web plugin0g:web_search tool
Request field"plugins": [{"id": "web"}]{"type": "0g:web_search"} in tools
Who decides to searchThe Router, on every requestThe model, only when it needs the web
Who writes the queryThe Router, from the conversationThe model
Searches per request1Up to max_uses (default 1, at most 5)
Model turns11 without a search, 2 with one (more when 0g:web_fetch is also listed)
Model requirementAny chat modelA model that supports tool calling

Both return the same url_citation annotations and are billed the same per-search fee.

claude-sonnet-4.6 answers from its training data

claude-sonnet-4.6 rarely uses the web, and answers from its training data instead. With the tool, it almost never decides to search. With the plugin, the search runs and is charged, but the model usually ignores the results. For questions about recent events, use another model, such as claude-sonnet-5 or claude-opus-4.7.

Web plugin​

Add plugins to the request. The Router builds a search query from the conversation, runs the search, adds the results to the system prompt, and then sends the request to the model.

curl https://router-api.0g.ai/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk-YOUR_API_KEY" \
-d '{
"model": "qwen-flash",
"messages": [{"role": "user", "content": "What is the latest stable release of the Go programming language?"}],
"plugins": [{"id": "web"}]
}'

The plugins field is removed before the request reaches the provider. If the search fails or returns nothing, the request still succeeds: the model answers without search results.

0g:web_search tool​

Add {"type": "0g:web_search"} to tools. The model reads the conversation, decides whether it needs the web, and writes its own queries.

curl https://router-api.0g.ai/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk-YOUR_API_KEY" \
-d '{
"model": "qwen-flash",
"messages": [{"role": "user", "content": "What is the latest stable release of the Go programming language?"}],
"tools": [{"type": "0g:web_search", "max_uses": 2}]
}'
FieldTypeDescription
typestring"0g:web_search"
max_usesintegerOptional. How many searches the model may run. Default 1; values above 5 are treated as 5

How it runs:

  1. Deciding turn. The Router sends your request to the model with a web_search function added. If the model answers without calling it, that answer is your response: one model turn, no search.
  2. Answering turn. If the model calls it, the Router runs its queries (up to max_uses), adds the results to the system prompt, and sends the request again without the tool. The model's answer is your response.

Things to know:

  • Tool-calling models only. The tool counts as tools, so a model without tool-calling support returns 400. Check supported_parameters in the catalog.
  • Your own tools work alongside it. List your function tools in the same tools array. If the model calls one of yours in the deciding turn without searching, you get that tool call as usual. If it searches, the answering turn decides tool calls again.
  • One search round. The model cannot search again after it has seen the results.
  • With 0g:web_fetch. If the request also lists the 0g:web_fetch tool, searching runs inside the web fetch loop instead. The model can search and fetch over up to 5 rounds, with at most max_uses searches in total, so more than two model turns can run. Each round is its own usage entry, with the request ID plus -wf1, -wf2, and so on, instead of -ws.
  • Name clash. If one of your own tools is named web_search, the Router removes the 0g:web_search tool and sends the request without it.
  • Streaming shows progress, then the answer. With "stream": true, the response starts at once with 200 and text/event-stream. Until the deciding turn and the search are done, the stream carries SSE comment lines, which SSE clients and SDKs ignore: : 0g web_search query="..." for the search the model asks for, and : ping when the stream opens and every 15 seconds. On /v1/messages, that is all it carries. On /v1/chat/completions, a reasoning model's reasoning from the deciding turn may also arrive as reasoning_content deltas in data: chunks. The answer's content arrives after that. Because the status is already 200, a request that fails after the stream has opened ends with an error event in the stream instead of an error status: on /v1/chat/completions, a data: line holding the usual {"error": {...}} object, with no data: [DONE] after it; on /v1/messages, an event: error.
  • Never sent to the provider. The 0g:web_search entry is always removed before the request is forwarded. If search is unavailable for the request, the model answers without it.
  • One search source. If a request has both the plugin and the tool and the plugin's search returns results, the tool is removed.

Response​

Answers built on search results carry url_citation annotations, one per result:

{
"type": "url_citation",
"url_citation": {
"url": "https://go.dev/VERSION",
"title": "- The Go Programming Language",
"content": "go1.27.1\ntime 2026-08-28T16:20:06Z"
}
}

content is the snippet the model was given; it can be omitted. Where the annotations appear depends on the endpoint and on streaming:

EndpointNon-streamingStreaming
/v1/chat/completionschoices[0].message.annotationsA final chunk before [DONE] with choices[0].delta.annotations
/v1/messagesTop-level annotationsTop-level annotations on the message_delta event

No annotations are added when nothing was searched or the search returned no results.

Billing​

Each search that returns results costs $0.02, on top of the model's token charges.

  • Web plugin: one search per request, so at most $0.02.
  • Tool: $0.02 for each search that returned results, up to max_uses.
  • Free: a search that fails or returns nothing, a request where the model decides not to search, and any request to a free model.
  • Tokens: the search results added to the prompt are billed as input tokens. With the tool, a searching request runs two model turns (more with 0g:web_fetch) and each is billed on its own token counts. When the model does not search, the request is a single turn, but its prompt includes the tool's definition, so it bills slightly more input than the same request without the tool.

The fee shows up in x_0g_trace.billing. It is included in input_cost and total_cost and reported on its own in web_search_cost, in the same unit as the other cost fields:

"x_0g_trace": {
"billing": {
"currency": "0g",
"input_cost": "77715595615710628",
"output_cost": "100996942884488",
"total_cost": "77816592558595116",
"total_cost_usd_micro": "20057",
"web_search_cost": "77596690811523652"
}
}

For the tool, x_0g_trace.billing covers both turns, while usage in the response is the answering turn only. In your usage history, the deciding turn is its own entry, with the request ID plus -ws (-wf1, -wf2, and so on when 0g:web_fetch is also listed).

A 0G-settled account pays the fee in 0G, converted from the USD price at the Router's current 0G/USD rate and rounded up; the cost fields are then in wei. If no current rate is available, the request is served without search rather than charged.

Anthropic's own web_search tool

On /v1/messages, you can also pass Anthropic's web_search server tool (for example web_search_20250305, which Claude Code's WebSearch uses). It is forwarded to the provider unchanged. Whether it works depends on the provider; when the provider runs searches itself and reports them in usage.server_tool_use.web_search_requests, each one is billed at the same per-search fee and included in web_search_cost.

  • Chat Completions — request and response shape
  • Models — check which models support tool calling
  • Errors — response codes and error shapes