Web Search

Beta

Give any model access to real-time web information

Beta

Server tools are currently in beta. The API and behavior may change.

The openrouter:web_search server tool gives any model on OpenRouter access to real-time web information. When the model determines it needs current information, it calls the tool with a search query. OpenRouter executes the search and returns results that the model uses to formulate a grounded, cited response.

How It Works

  1. You include { "type": "openrouter:web_search" } in your tools array.
  2. Based on the user’s prompt, the model decides whether a web search is needed and generates a search query.
  3. OpenRouter executes the search using the configured engine (defaults to auto, which uses native provider search when available or falls back to Exa).
  4. The search results (URLs, titles, and content snippets) are returned to the model.
  5. The model synthesizes the results into its response. It may search multiple times in a single request if needed.

Quick Start

1const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
2 method: 'POST',
3 headers: {
4 Authorization: 'Bearer {{API_KEY_REF}}',
5 'Content-Type': 'application/json',
6 },
7 body: JSON.stringify({
8 model: '{{MODEL}}',
9 messages: [
10 {
11 role: 'user',
12 content: 'What were the major AI announcements this week?'
13 }
14 ],
15 tools: [
16 { type: 'openrouter:web_search' }
17 ]
18 }),
19});
20
21const data = await response.json();
22console.log(data.choices[0].message.content);

Configuration

The web search tool accepts optional parameters to customize search behavior:

1{
2 "type": "openrouter:web_search",
3 "parameters": {
4 "engine": "exa",
5 "max_results": 5,
6 "max_total_results": 20,
7 "search_context_size": "medium",
8 "allowed_domains": ["example.com"],
9 "excluded_domains": ["reddit.com"]
10 }
11}
ParameterTypeDefaultDescription
enginestringautoSearch engine to use: auto, native, exa, firecrawl, parallel, or perplexity
max_resultsinteger5Maximum results per search call (1–25). Applies to Exa, Firecrawl, Parallel, and Perplexity engines; ignored with native provider search
max_total_resultsintegerMaximum total results across all search calls in a single request. Useful for controlling cost and context size in agentic loops
search_context_sizestringHow much context to retrieve: low, medium, or high. For Exa, pins a fixed per-result character cap (5K/15K/30K); when omitted, Exa picks adaptively (~2-4K per result). For Parallel, controls total characters across all results (defaults to medium). For Perplexity, maps directly to the Search API’s native search_context_size parameter. Ignored with native provider search and Firecrawl. Overridden by max_characters when both are set
max_charactersintegerExact maximum characters of content per result (1–100,000). Applies to Exa, Parallel, and Perplexity engines; ignored with native provider search and Firecrawl. For Exa, caps highlight content per result. For Parallel, caps excerpt content per result (default 1,500 when omitted). For Perplexity, converted to a token budget via max_tokens_per_page and trimmed to the exact character cap. When both max_characters and search_context_size are set, max_characters takes precedence
user_locationobjectApproximate user location for location-biased results. Currently only supported by native provider search; ignored with Exa, Firecrawl, Parallel, and Perplexity (see below)
allowed_domainsstring[]Limit results to these domains. Supported by Exa, Firecrawl, Parallel, Perplexity, and most native providers (see domain filtering)
excluded_domainsstring[]Exclude results from these domains. Supported by Exa, Firecrawl, Parallel, Perplexity, and some native providers (see domain filtering)

User Location

Pass an approximate user location to bias search results geographically:

1{
2 "type": "openrouter:web_search",
3 "parameters": {
4 "user_location": {
5 "type": "approximate",
6 "city": "San Francisco",
7 "region": "California",
8 "country": "US",
9 "timezone": "America/Los_Angeles"
10 }
11 }
12}

All fields within user_location are optional.

Native Search Providers

When engine is "auto" (the default) or "native", OpenRouter uses the provider’s built-in search for supported models. The following providers have native web search:

  • OpenAI — most recent GPT and o-series models
  • Anthropic — Claude models with tool use support
  • Google — Gemini models via Google Search grounding
  • xAI — Grok models (includes both web search and X search)
  • Perplexity — all Perplexity models (search is core to their API)

You can check whether a specific model supports native search on its model page — look for the “Web Search” capability badge. For models without native search, set engine to one of the other supported options (Exa, Firecrawl, Parallel, or Perplexity) — or leave it as "auto" to default to Exa.

Engine Selection

The web search server tool supports multiple search engines:

  • auto (default): Uses native search if the provider supports it, otherwise falls back to Exa
  • native: Forces the provider’s built-in web search (falls back to Exa with a warning if the provider doesn’t support it)
  • exa: Uses Exa’s search API, which combines keyword and embeddings-based search. Returns Exa highlights — excerpts drawn from each page that are most relevant to the search query — rather than truncated page text. See the Exa section below.
  • firecrawl: Uses Firecrawl’s search API (BYOK — bring your own key)
  • parallel: Uses Parallel’s search API
  • perplexity: Uses the Perplexity Search API for ranked web results with domain filtering, context size control, and max_characters support

Engine Capabilities

FeatureExaFirecrawlParallelPerplexityNative
Domain filteringYesYesYesYes***Varies by provider
Context size controlYes*NoYes**YesNo
API keyServer-sideBYOK (your key)Server-sideServer-sideProvider-handled

* Exa: limit applies per result

** Parallel: limit applies as a total across all results

*** Perplexity: allowed_domains and excluded_domains are mutually exclusive — when both are provided, allowed_domains takes precedence

Exa

OpenRouter requests Exa highlights for each result rather than the text content option. Highlights are extractive excerpts drawn directly from the page that Exa selects as most relevant to the search query, typically yielding higher-quality context per token than truncated page text for agentic web tooling.

By default, Exa selects an adaptive highlight size per query and document — typically ~2,000–4,000 characters per result. You can control the per-result character budget in two ways:

Coarse presets via search_context_size — maps to Exa’s contents.highlights.maxCharacters:

  • low — 5,000 characters per result
  • medium — 15,000 characters per result
  • high — 30,000 characters per result

Exact value via max_characters — pass any integer (1–100,000) to set a precise per-result content budget. Supported by Exa, Parallel, and Perplexity. When both max_characters and search_context_size are set, max_characters takes precedence.

1{
2 "type": "openrouter:web_search",
3 "parameters": {
4 "engine": "exa",
5 "max_characters": 2000
6 }
7}

When neither max_characters nor search_context_size is set, OpenRouter lets Exa pick the highlight size adaptively and Parallel uses its default of 1,500 characters per result. The selected excerpts are returned to the model on each result and surfaced to API callers via url_citation annotations. Within a single result, excerpts that come from different parts of the page are separated by Exa’s [...] markers, so the content field of a url_citation annotation may look like:

First excerpt drawn from the page.
[...]
Second excerpt drawn from elsewhere in the same page.
[...]
Third excerpt.

Firecrawl (BYOK)

Firecrawl uses your own API key. To set it up:

  1. Go to your OpenRouter plugin settings and select Firecrawl as the web search engine
  2. Accept the Firecrawl Terms of Service — this creates a Firecrawl account linked to your email
  3. Your account starts with 10,000 free credits (credits expire after 3 months)

Firecrawl searches use your Firecrawl credits directly — no additional charge from OpenRouter. Firecrawl supports domain filtering (allowed_domains / excluded_domains), but they are mutually exclusive — you cannot use both in the same request.

Parallel

Parallel supports domain filtering and context size control (search_context_size), and uses OpenRouter credits at $0.005 per request. Includes up to 10 results in a request, then $0.001 per additional result.

Perplexity

Perplexity returns ranked web results (titles, URLs, snippets) without LLM synthesis. It supports domain filtering (allowed_domains / excluded_domains, mutually exclusive), search_context_size, and max_characters. Uses OpenRouter credits at $0.005 per request.

Domain Filtering

Restrict which domains appear in search results using allowed_domains and excluded_domains:

1{
2 "type": "openrouter:web_search",
3 "parameters": {
4 "allowed_domains": ["arxiv.org", "nature.com"],
5 "excluded_domains": ["reddit.com"]
6 }
7}
Engineallowed_domainsexcluded_domainsNotes
ExaYesYesBoth can be used simultaneously
ParallelYesYesMutually exclusive
FirecrawlYesYesMutually exclusive
PerplexityYesYesMutually exclusive (when both provided, allowed_domains wins)
Native (Anthropic)YesYesMutually exclusive
Native (OpenAI)YesNoexcluded_domains silently ignored
Native (Google)NoNoNot supported. With engine: "auto", falls back to Exa when filters are set. With engine: "native", returns a 400 error
Native (xAI)YesYesMutually exclusive

Controlling Total Results

When the model searches multiple times in a single request, use max_total_results to cap the cumulative number of results:

1{
2 "type": "openrouter:web_search",
3 "parameters": {
4 "max_results": 5,
5 "max_total_results": 15
6 }
7}

Once the limit is reached, subsequent search calls return a message telling the model the limit was hit instead of performing another search. This is useful for controlling cost and context window usage in agentic loops.

Works with the Responses API

The web search server tool also works with the Responses API:

1const response = await fetch('https://openrouter.ai/api/v1/responses', {
2 method: 'POST',
3 headers: {
4 Authorization: 'Bearer {{API_KEY_REF}}',
5 'Content-Type': 'application/json',
6 },
7 body: JSON.stringify({
8 model: '{{MODEL}}',
9 input: 'What is the current price of Bitcoin?',
10 tools: [
11 { type: 'openrouter:web_search', parameters: { max_results: 3 } }
12 ]
13 }),
14});
15
16const data = await response.json();
17console.log(data);

Usage Tracking

Web search usage is reported in the response usage object:

1{
2 "usage": {
3 "input_tokens": 105,
4 "output_tokens": 250,
5 "server_tool_use": {
6 "web_search_requests": 2
7 }
8 }
9}

The web_search_requests field counts the total number of search queries the model made during the request.

Pricing

EnginePricing
Exa$0.005 per request using OpenRouter credits. Includes up to 10 results, then $0.001 per additional result
Parallel$0.005 per request using OpenRouter credits. Includes up to 10 results in a request, then $0.001 per additional result
Perplexity$0.005 per request using OpenRouter credits
FirecrawlUses your Firecrawl credits directly — no OpenRouter charge
NativePassed through from the provider (OpenAI, Anthropic, Google, Perplexity, xAI)

All pricing is in addition to standard LLM token costs for processing the search result content.

Migrating from the Web Search Plugin

The web search plugin (plugins: [{ id: "web" }]) and the :online variant are deprecated. Use the openrouter:web_search server tool instead.

The key differences:

Web Search Plugin (deprecated)Web Search Server Tool
How to enableplugins: [{ id: "web" }]tools: [{ type: "openrouter:web_search" }]
Who decides to searchAlways searches onceModel decides when/whether to search
Call frequencyOnce per request0 to N times per request
Engine optionsNative, Exa, Firecrawl, Parallel, PerplexityAuto, Native, Exa, Firecrawl, Parallel, Perplexity
Domain filteringYes (Exa, Parallel, Perplexity, some native)Yes (Exa, Parallel, Perplexity, most native)
Context size controlVia web_search_optionsVia search_context_size parameter
Total results capNoYes (max_total_results)
PricingVaries by engineVaries by engine (same rates)

Migration example

1// Before (deprecated)
2{
3 "model": "openai/gpt-5.2",
4 "messages": [...],
5 "plugins": [{ "id": "web", "max_results": 3 }]
6}
7
8// After
9{
10 "model": "openai/gpt-5.2",
11 "messages": [...],
12 "tools": [
13 { "type": "openrouter:web_search", "parameters": { "max_results": 3 } }
14 ]
15}
1// Before (deprecated) — engine and domain filtering
2{
3 "model": "openai/gpt-5.2",
4 "messages": [...],
5 "plugins": [{
6 "id": "web",
7 "engine": "exa",
8 "max_results": 5,
9 "include_domains": ["arxiv.org"]
10 }]
11}
12
13// After
14{
15 "model": "openai/gpt-5.2",
16 "messages": [...],
17 "tools": [{
18 "type": "openrouter:web_search",
19 "parameters": {
20 "engine": "exa",
21 "max_results": 5,
22 "allowed_domains": ["arxiv.org"]
23 }
24 }]
25}
1// Before (deprecated) — :online variant
2{
3 "model": "openai/gpt-5.2:online"
4}
5
6// After
7{
8 "model": "openai/gpt-5.2",
9 "tools": [{ "type": "openrouter:web_search" }]
10}

Next Steps