> ## Documentation Index
> Fetch the complete documentation index at: https://docs.playapi.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Web search and citations

> Use native Responses search tools, preserve citations, and check channel boundaries.

## Three different search integrations

| Integration | Executes the search | Entry point |
| - | - | - |
| Native Responses built-in search | Upstream model service supporting the tool | This guide and [Responses API](/en/api-reference/responses) |
| Client function tool | Your application calls a search service and returns results | [Tool calling](/en/guides/tool-calling) |
| Alpha Search | Deployment-specific search backend | [Alpha Search](/en/guides/alpha-search) |

Responses represents native `tools` / `tool_choice`. The upstream must support the selected tool and model; the gateway does not implement search for arbitrary models. Chat's conditional `web_search_options` uses a different structure. OpenRouter's `:online` suffix, `plugins` search fallback, and server-tool catalog are not gateway-wide contracts here.

## Tool selection and results

The example requires explicit `API_SEARCH_TOOL`. The command uses `web_search_preview`, for which the project has dedicated handling. Select current upstream `web_search` only after confirming deployment support. Their parameters and capabilities differ; the script does not downgrade or retry. Native forwarding alone does not establish complete call accounting or billing semantics for a new tool name across channels; the deployment must verify those. This guide makes no price or free-quota promises.

`tool_choice: required` requests tool use. The example additionally requires at least one completed `web_search_call` and URL citation. This is an example acceptance condition, not a claim that all uncited Responses results violate the protocol. Prefer a verified native Responses path; conversion does not guarantee preservation of built-in search or citations.

Each message's `output_text.annotations` can contain `url_citation` with `url`, `title`, `start_index`, and `end_index`. The script retains offsets beside their original text block instead of applying them after merging text. Interfaces should preserve clickable sources and follow the upstream's Unicode indexing convention rather than assume JavaScript slicing. The script does not visit links; escape externally supplied URLs and titles when rendering.

The script accepts HTTP(S) citations without credentials and checks basic index types and ordering. It does not verify that cited pages support the answer, or print private reasoning. Output is answer-and-citation JSON and may contain public webpage text. One request, no automatic retry; local fixtures check parsing, not live search quality.

Implementation sources: `relaykit/dto/openai_request.go`, `relay/channel/openai/adaptor.go`, and `relay/common/relay_info.go`. See [upstream search documentation](https://developers.openai.com/api/docs/guides/tools-web-search).

## Run the example

Run from the documentation directory with approved inputs and a capable model; first read the [REST runtime](/en/guides/http-runtime). Keep the script and `http-client.mjs` together.

```sh theme={null}
export API_ROOT_URL='https://gateway.example.com'
export API_KEY='YOUR_API_KEY'
export API_MODEL='YOUR_SEARCH_MODEL_ID'
export API_SEARCH_TOOL='web_search_preview'
node examples/web-search.mjs
```

```javascript theme={null}
import { requestJSON } from './http-client.mjs';

try {
  const { API_MODEL, API_SEARCH_TOOL } = process.env;
  if (!API_MODEL?.trim() || !['web_search_preview', 'web_search'].includes(API_SEARCH_TOOL)) {
    throw new Error('Select a model and a supported native search tool.');
  }
  const result = await requestJSON('/v1/responses', {
    model: API_MODEL, store: false,
    input: 'Search the official Node.js website for its release schedule and summarize it with source citations.',
    tools: [{ type: API_SEARCH_TOOL }], tool_choice: 'required',
  });
  if (result.status !== 'completed' || !Array.isArray(result.output)) throw new Error('Incomplete response.');
  let calls = 0;
  const blocks = [];
  for (const item of result.output) {
    if (item?.type === 'reasoning') continue;
    if (item?.type === 'web_search_call') {
      if (item.status !== 'completed') throw new Error('Incomplete search.');
      calls++;
      continue;
    }
    if (item?.type !== 'message' || item.role !== 'assistant' || item.status !== 'completed'
      || !Array.isArray(item.content)) throw new Error('Unexpected output.');
    for (const part of item.content) {
      if (part?.type !== 'output_text' || typeof part.text !== 'string' || !Array.isArray(part.annotations)) {
        throw new Error('Missing text or annotations.');
      }
      const citations = [];
      for (const annotation of part.annotations) {
        if (annotation?.type !== 'url_citation') throw new Error('Unsupported annotation.');
        const { url, title, start_index, end_index } = annotation;
        if (typeof url !== 'string' || typeof title !== 'string' || !title.trim()
          || !Number.isSafeInteger(start_index) || !Number.isSafeInteger(end_index)
          || start_index < 0 || end_index <= start_index) throw new Error('Invalid citation.');
        const link = new URL(url);
        if (!['http:', 'https:'].includes(link.protocol) || link.username || link.password) throw new Error('Invalid citation URL.');
        // Keep provider offsets attached to the original text block; do not fetch links.
        citations.push({ url, title, start_index, end_index });
      }
      blocks.push({ text: part.text, citations });
    }
  }
  if (!calls || !blocks.some((block) => block.text.trim()) || !blocks.some((block) => block.citations.length)) {
    throw new Error('This example requires a completed search and cited text.');
  }
  console.log(JSON.stringify({ search_calls: calls, blocks }));
} catch {
  console.error('Search example failed. Check native tool support, completed search, and citations. No automatic retry.');
  process.exitCode = 1;
}
```

[阅读中文](/zh/guides/web-search)

## Native Gemini search and URL context

Gemini uses `tools[].googleSearch` and candidate grounding metadata, rather than Responses web\_search events. To read a supplied URL use the separate urlContext tool. See the [native built-in guide and runnable modes](/en/guides/gemini-builtins).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.