Claude Web Search Can Fail Inside HTTP 200: Parse Errors, Empty Results, and ZDR
Build a Claude web-search parser that separates results, empty searches, tool errors inside HTTP 200, request errors, and ZDR-sensitive caller modes.
Direct answer: A Claude Messages API call can return HTTP 200 even when the web-search tool failed. Your integration must inspect each web_search_tool_result: a list contains results, an empty list means the search completed with no matches, and a single web_search_tool_result_error object means the tool failed. Request validation and disabled-tool errors remain request-level failures.
This contract applies to Anthropic’s API web-search tool, not every Claude consumer search surface. It also does not make third-party web requests private: ZDR eligibility at Anthropic and data handling by fetched sites are separate questions.
Model the response as four states
Do not branch on status code alone and do not treat every array-like field as a result set. Parse the block type, verify the tool_use_id, then classify the content shape. Preserve the tool version, query, caller, error code, number of results, stop reason, and request identifier in a safe run record.
The documented tool-error codes include rate limiting, invalid input, exhausted max_uses, long queries, oversized requests, and temporary unavailability. Keep unknown codes as an explicit state so a future platform addition cannot fall through to “success.”
| State | Evidence | Application action |
|---|---|---|
| Results | Content is a non-empty result list | Render sources and retain citations |
| Empty | Content is an empty list | Show no matches; do not retry blindly |
| Tool error | Content is one structured error object | Classify the code and apply bounded retry policy |
| Request error | HTTP 4xx or validation failure | Repair configuration or user input |
Handle retries, continuations, and billing
Retry only transient conditions and cap attempts with delay and jitter. A query that is too long or a domain list that makes the request too large needs correction, not repeated traffic. A successful empty result can be the correct answer; an automatic retry would change the observation and may increase cost.
Also handle pause_turn by replaying the paused assistant message unchanged, and preserve encrypted search content exactly in multi-turn conversations. Record usage.server_tool_use.web_search_requests instead of estimating calls from visible citations because one request can search more than once.
{\n "http_status": 200,\n "search_state": "tool_error",\n "error_code": "max_uses_exceeded",\n "retryable": false\n}Choose the tool version and privacy mode
Anthropic currently documents basic web_search_20250305, dynamic-filtering web_search_20260209, and web_search_20260318 with response-inclusion control. Pin the version when reproducibility matters and save allowed_callers with the fixture.
The dynamic-filtering versions use code execution and are not ZDR-eligible by default. Setting allowed_callers to direct bypasses that internal step and restores the documented ZDR eligibility boundary, while also changing the execution path. Treat privacy configuration and retrieval behavior as two fields, not one “secure search” label.
Ship a parser fixture before production
Build fixtures for a normal result, an empty list, every documented error code, a request-level 400, pause_turn, and an unknown future error. Assert that no error block reaches the citation renderer and that no empty search is shown as an outage.
Before release, compare direct and dynamic-filtering modes on non-sensitive inputs, document the model and tool versions, and decide which failures should stop the user’s task. Monitoring should expose the state distribution without copying user queries or retrieved content into a less protected log system.
Use the search API reproducibility record for run manifests and the evidence-led publishing guide for claim boundaries.
Ask a question or join the discussion