> Markdown mirror of https://router.one/blog/claude-web-search-code-execution-api for AI assistants and crawlers. Router One is an OpenAI-compatible LLM API gateway.
> Published: 2026-09-02 · Author: Router One Team

# Claude Web Search and Code Execution via /v1/messages

_Use Claude's web_search and code_execution server tools via Router One's /v1/messages: request shape, response blocks, rejections, billing._

Claude's server-side tools work through Router One. On the Anthropic-compatible `POST /v1/messages` endpoint, the `web_search` server tool and the `code_execution` tool (with its `bash_code_execution` and `text_editor_code_execution` sub-tools and container reuse across turns) are accepted exactly as Anthropic's API defines them.

Declare the typed tool in `tools`, send the request to `https://api.router.one/v1/messages` with a Router One key and a Claude catalog id, and the search or the sandboxed run happens on the model side while Router One forwards the request, meters it at the model's standard token rate, and writes the request trace. Two things do not pass: the `web_fetch` server tool and MCP (an `mcp_toolset` tool or the `mcp_servers` field) are rejected with HTTP 400 `invalid_request_error` before any model is called.

## Client-side tools vs. server-side tools

With ordinary [tool calling](https://router.one/llm-tool-calling), the tool runs in your code: you declare a function with an `input_schema`, the model answers with a `tool_use` block, you execute it and send a `tool_result` back. That loop works on every tool-capable model in the [catalog](https://router.one/models) and on the OpenAI-compatible endpoint.

Server-side tools invert the loop. You declare a tool by a dated `type` (`web_search_20250305`, `code_execution_20250825`) with no schema, and the model vendor's API runs the search or the sandboxed code inside the request — possibly several rounds — before returning the final text with citations or execution output. Nothing comes back to your code mid-request.

Router One's part is deliberately narrow: it authenticates the key, forwards the request to the Claude model, records the trace, and settles the tokens the model reports. Router One does not execute tools itself — the trust boundary is spelled out on the [API compatibility fact sheet](https://router.one/facts/api-compatibility.md).

## Sending web_search through Router One

The request is the Anthropic Messages request you already know, with the host swapped:

```bash
curl https://api.router.one/v1/messages \
  -H "x-api-key: sk-your-api-key" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "model": "anthropic/claude-sonnet-4.6",
    "max_tokens": 1024,
    "messages": [
      {"role": "user", "content": "What changed in the most recent Node.js LTS release? Cite sources."}
    ],
    "tools": [{"type": "web_search_20250305", "name": "web_search", "max_uses": 3}]
  }'
```

Three details worth knowing. The key goes in `x-api-key` or in `Authorization: Bearer` — both are accepted, and Bearer is what Claude Code sends through `ANTHROPIC_AUTH_TOKEN`. The `model` field takes the catalog id (`anthropic/claude-sonnet-4.6`); most official bare names such as `claude-sonnet-4.6` are accepted as aliases, but the catalog id is the safe form. And `max_uses`, `allowed_domains` / `blocked_domains` (one or the other, not both) and `user_location` are forwarded as sent, as are the newer tool versions `web_search_20260209` (dynamic filtering) and `web_search_20260318` (`response_inclusion`).

With the official Python SDK, set `base_url` to the host — the SDK appends `/v1/messages` itself:

```python
import anthropic

client = anthropic.Anthropic(
    api_key="sk-your-api-key",
    base_url="https://api.router.one",  # host only; the SDK adds /v1/messages
)

response = client.messages.create(
    model="anthropic/claude-sonnet-4.6",
    max_tokens=1024,
    messages=[{"role": "user", "content": "What changed in the most recent Node.js LTS release? Cite sources."}],
    tools=[{"type": "web_search_20250305", "name": "web_search", "max_uses": 3}],
)

for block in response.content:
    if block.type == "text":
        print(block.text)
print(response.usage.server_tool_use)  # web_search_requests=N
```

The response `content` array comes back in the order the turn happened: a `text` block where Claude decides to search, a `server_tool_use` block (`name: "web_search"`, the query in `input`), a `web_search_tool_result` block whose `content` is a list of `web_search_result` entries (`url`, `title`, `page_age` and `encrypted_content`), and finally `text` blocks carrying `citations` of type `web_search_result_location`. `usage.server_tool_use.web_search_requests` counts the searches. To continue the conversation, send the assistant's blocks back exactly as received, `encrypted_content` included — Router One accepts the replayed `server_tool_use` and `web_search_tool_result` blocks on the next turn and meters them as input like any other content. With `stream: true` the same blocks arrive as `content_block_start` events with a pause while the search runs; the [streaming guide](https://router.one/llm-streaming) covers the event stream.

## Sending code_execution

Same shape, different tool type:

```bash
curl https://api.router.one/v1/messages \
  -H "x-api-key: sk-your-api-key" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "model": "anthropic/claude-sonnet-4.6",
    "max_tokens": 4096,
    "messages": [
      {"role": "user", "content": "Draw 1000 samples from a normal distribution, then report the mean, median and standard deviation."}
    ],
    "tools": [{"type": "code_execution_20250825", "name": "code_execution"}]
  }'
```

Claude answers with `server_tool_use` blocks named `bash_code_execution` (shell commands) or `text_editor_code_execution` (view, create and edit files), each followed by a `bash_code_execution_tool_result` block with `stdout`, `stderr` and `return_code`, or a `text_editor_code_execution_tool_result` block carrying the view, create or edit result, and then the final text. The response also carries a top-level `container` object with an `id` and `expires_at`; pass that id back as the top-level `container` request parameter on the next turn to keep the files Claude created — Router One accepts the container id across turns. `code_execution_20260120` and `code_execution_20260521` are forwarded the same way if you need REPL state persistence or programmatic tool calling.

No `anthropic-beta` header is required: none of the three current code execution versions needs one, and the legacy beta headers remain optional opt-ins. If your SDK or an older integration still sends `anthropic-beta`, Router One forwards the header unchanged.

One boundary to plan around: Router One serves no Files API. Keep code execution to work whose result comes back in the response body — stdout, computed values, text — rather than workflows that upload input files by `file_id` or download generated files afterwards.

## What Router One rejects, and why that is useful

Three things are refused before any model is called, each with HTTP 400 and the Anthropic error envelope `{"type":"error","error":{"type":"invalid_request_error","message":...},"request_id":...}`:

- the `web_fetch` server tool, in any dated version;
- an `mcp_toolset` tool entry;
- a non-empty `mcp_servers` field (the MCP connector).

The message names the rejected feature, so the fix is obvious, and because the request never reached a model, it cost nothing. Your SDK raises the same `BadRequestError` it would raise for any Anthropic 400. The [error codes page](https://router.one/llm-api-error-codes) lists the other 400s on this endpoint.

The substitutes are straightforward. For page content, either let `web_search` bring it in (search results are loaded into the model's context), or fetch the page in your own code and pass the text as a normal content block. For MCP, run the MCP client on your side and expose the tools it discovers as client-side function tools — the loop on the [tool calling guide](https://router.one/llm-tool-calling) is exactly that.

## Billing and the request trace

Server tool activity is metered at the model's standard token rate. The search results Claude reads and the execution output it consumes are input and output tokens in the usage the model reports, settled at the posted rate on the model's page — [Claude Sonnet 4.6](https://router.one/models/claude-sonnet-4-6), for example — with no separate per-search or per-container line item, and container time is not passed through per call. Current rates and the "up to 90% off official prices" qualifier for select models are on the [pricing page](https://router.one/pricing).

Every request lands in Dashboard → Logs with model, input and output tokens, cost, latency and status, so a search-heavy turn shows up as a bigger token bill in the same trace as any other call; for your own accounting, `usage.server_tool_use` in the response body counts the searches and executions. If an agent can trigger open-ended searching, put the key behind a hard spend cap (`maxSpend`) and a rate limit — the same per-key controls described in [Claude Code token costs explained](https://router.one/blog/claude-code-token-costs-explained).

## Which models and which endpoint

Server tools are a Claude-family feature on `/v1/messages`. Per-tool model support is Anthropic's call and changes over time; the Claude ids in the [catalog](https://router.one/models) — `anthropic/claude-opus-5`, `anthropic/claude-sonnet-4.6`, `anthropic/claude-haiku-4.5` and their siblings — each list `POST /v1/messages` on their model page. DeepSeek V4 also answers on `/v1/messages`, but with client-side function tools only: a server tool has no executor there. And the OpenAI-compatible `/v1/chat/completions` request shape has no slot for Anthropic's typed server tools, so on that endpoint tool calling is client-side for every model.

Claude Code depends on this path: its built-in WebSearch relies on the endpoint accepting the `web_search` server tool — which is why it breaks on relays that reject server tools — so pointing Claude Code at Router One with `ANTHROPIC_BASE_URL` and `ANTHROPIC_AUTH_TOKEN` (the [Claude Code setup](https://router.one/claude-code-china) shows both) keeps WebSearch working. Claude Code's WebFetch fetches pages itself rather than through the `web_fetch` server tool, so the rejection above does not touch it. The dated acceptance entries are in the [changelog](https://router.one/blog/changelog).

## FAQ

**Do I need an anthropic-beta header to use code_execution through Router One?**
No. None of the current `code_execution_20250825`, `code_execution_20260120` and `code_execution_20260521` tool versions requires an `anthropic-beta` header. If your client sends one anyway, Router One forwards it unchanged.

**Does web_search cost extra per search through Router One?**
Server tool activity is metered at the model's standard token rate: the search content the model reads is billed as input tokens in the usage the model reports, and there is no separate per-search line item. The per-request trace shows the total, and `usage.server_tool_use.web_search_requests` in the response counts the searches.

**Can I use web_fetch or MCP servers through Router One?**
No. The `web_fetch` server tool, `mcp_toolset` tools and the `mcp_servers` field are rejected with HTTP 400 `invalid_request_error` before any model is called, so the request costs nothing. Fetch pages in your own code, or run the MCP client on your side and expose its tools as client-side function tools.

**Does Claude Code's WebSearch work through Router One?**
Yes. Claude Code's WebSearch depends on the endpoint accepting the `web_search` server tool this post describes, and that tool is accepted on `/v1/messages`. Configure `ANTHROPIC_BASE_URL=https://api.router.one` and `ANTHROPIC_AUTH_TOKEN` with your Router One key as on the Claude Code setup page.

**Can I send server tools to DeepSeek V4 or on /v1/chat/completions?**
No. Server tools run on the Claude API side and only do something for Claude-family models on `/v1/messages`; DeepSeek V4 there, and every model on the OpenAI-compatible `/v1/chat/completions` endpoint, use client-side function tools.

## Next steps

- Read the [tool calling guide](https://router.one/llm-tool-calling) for the client-side loop that works on every model.
- Point Claude Code at the gateway with the [Claude Code setup](https://router.one/claude-code-china) — WebSearch included.
- The [Messages endpoint reference](https://router.one/docs/chat/createMessage) and the [API docs](https://router.one/docs) document the request and error shapes.
- Pick a Claude model and check its endpoints and live rate on the [model catalog](https://router.one/models); the discount qualifier is on the [pricing page](https://router.one/pricing).

## See also

- Canonical page: https://router.one/blog/claude-web-search-code-execution-api
- LLM API Gateway and Routing: https://router.one/llm-api-gateway
- All blog posts: https://router.one/blog
- Models and per-model token rates: https://router.one/models (markdown: https://router.one/models.md)
- Pricing: https://router.one/pricing
- API docs (markdown): https://router.one/docs.md
- Company facts: https://router.one/facts/company.md
