Web Search

The Responses API can answer questions grounded in live web content and return inline citations back to the source. You enable web access by adding a web_search tool to a request; the model decides when to search, and the answer comes back with url_citation annotations pointing at the pages it used.

Search is agentic: the model chooses whether and when to search, rather than the server always searching. A turn may search once, several times, follow up by reading a full page, or not search at all, and citations only appear when the model actually grounds its answer in retrieved results.

Two capabilities are exposed from a single web_search tool:

  • Search the web: runs a web search and returns citable results (title, URL, snippet).

  • Read a web page: fetches the full content of one public URL as markdown when a search snippet is not enough. This grounds the answer and the page itself becomes citable.

Web content is backed by LinkUp (the web search service). The LinkUp API key is server-owned: unlike file search, no per-request user authorization is forwarded. Search queries and fetched page content therefore leave the cluster; see Data Flows.


How It Works

  1. The client adds one web_search tool to tools[], optionally setting the search depth and domain filters.

  2. During generation the model may search the web or fetch a page, as many times as it needs.

  3. Each call runs server-side against LinkUp and returns numbered, citable sources to the model.

  4. The model writes inline [1], [2] markers where it uses a source.

  5. The response carries a web_search_call output item (the search + its results) and url_citation annotations on the answer text, each mapping a marker to its source page.

Searches that find nothing, or turns where the model chooses not to search, simply produce no citations, the request still succeeds.


Sending a Request

Add a single tool of type web_search to tools[]. Set stream: true for SSE.

curl -X POST "$BASE_URL/v1/responses" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen3-32b-tool",
    "input": "What are the latest developments in AI regulation?",
    "tools": [
      {
        "type": "web_search",
        "depth": "standard",
        "filters": {
          "allowed_domains": ["europa.eu", "whitehouse.gov"],
          "blocked_domains": ["reddit.com"]
        }
      }
    ]
  }'

Tool fields:

Field Type Description

type

"web_search"

Required.

depth

"fast" | "standard" | "deep"

Optional, default "standard". LinkUp search depth: fast for quick lookups, deep for thorough (slower, costlier) research.

filters

object

Optional. allowed_domains restricts the search to those domains; blocked_domains excludes domains. Both default to empty.

Notes:

  • When the model calls web_search, it provides a query string argument; when it calls fetch_url, a url string argument.

  • One tool entry enables both searching and page reading.

  • web_search can be combined with file_search in the same request, citation numbers [n] stay globally unique across web and document sources.

  • Graceful degradation: if web search is unavailable, the request still answers, the web_search_call item is persisted with status: "failed" and the model answers from its own knowledge. No error is surfaced.


The Response

A grounded answer returns two kinds of output:

  1. A web_search_call output item, the search and its results (one per search the model ran).

  2. An assistant message whose output_text carries url_citation annotations, one per [n] marker the model wrote.

web_search_call item

{
  "type": "web_search_call",
  "id": "ws_...",
  "status": "completed",
  "action": { "type": "search", "query": "latest AI regulation developments" },
  "results": [
    {
      "title": "EU AI Act enters into force",
      "url": "https://europa.eu/...",
      "snippet": "matched result text",
      "citation_index": 1
    }
  ]
}

action records how the model invoked the tool. On failure, status is "failed" and results is null.

url_citation annotation

{
  "type": "url_citation",
  "start_index": 42,
  "end_index": 45,
  "title": "EU AI Act enters into force",
  "url": "https://europa.eu/...",
  "snippet": "the cited result text, for hover/preview"
}

The marker [n] in the answer maps to source number n. start_index/end_index are the marker’s character offsets in the text. Unlike file-search citations there is no sherlock block, url is a plain public web URL. Citation numbers stay stable and de-duplicated across multiple searches within one answer, the same URL keeps the same [n].

Streaming events

For a streamed web_search turn the notable events, in order, are:

  • response.output_item.added / response.web_search_call.in_progress / .searching / .completed / response.output_item.done: the web_search_call lifecycle (repeated if the model searches more than once; .completed is omitted when the search failed).

  • response.output_text.delta …, the answer text.

  • response.output_text.annotation.added: one per [n] marker, carrying a url_citation (emitted before response.output_text.done).

  • response.completed, then [DONE].

Reading a web page is transparent: it grounds the answer and makes the page citable, but emits no output item of its own.


Multi-Turn

The web_search tool must be sent on each turn where web access should be available. Citations are computed per turn: a follow-up that doesn’t search produces no citations. Already-cited URLs keep their [n] within a single answer, and web_search_call items from earlier turns are not re-sent to the model.

On reload there is no need to replay the stream, the web_search_call results and the url_citation annotations are persisted on the stored response and returned by the conversation/response retrieval endpoints.


Configuration

Web search requires explicit deployment opt-in, coherent LinkUp configuration, and the agentic loop (which executes the hosted tools).

Variable Default Description

WEB_SEARCH_ENABLED

false

Must be true to expose hosted web_search and fetch_url.

WEB_SEARCH_API_KEY (alias LINKUP_API_KEY)

-

LinkUp API key required when hosted web search is enabled.

WEB_SEARCH_BASE_URL

https://api.linkup.so

LinkUp API base URL.

WEB_SEARCH_MAX_RESULTS

8

Maximum search hits returned per search and exposed as citable sources.

WEB_SEARCH_RENDER_JS

false

Whether page reads render JavaScript before extracting content.

WEB_SEARCH_MAX_FETCH_CHARS

120000

Safety cap on fetched page text fed to the model.

WEB_SEARCH_TIMEOUT

60.0

Per-call timeout for LinkUp requests (seconds).

AGENTIC_LOOP_ENABLED

false

Must be true: web search executes inside the agentic loop.

Web search is active only when WEB_SEARCH_ENABLED=true, the API key and a non-blank base URL are configured, and the agentic loop is on. Otherwise a web_search tool on a request is ignored and the turn proceeds as a normal completion.