Using the Web Search Tool with the Responses API
How the Web Search Tool Fits Into the Responses API
The Responses API treats web search as one of its built-in tools, alongside things like file search and code execution. Unit 9, Lesson 1 showed the minimum needed to turn it on. This lesson treats it as a first-class mechanism you need to understand structurally: what actually happens on the server when you enable it, what the returned response object looks like, and how to read the pieces of it your application will actually depend on.
Conceptually, enabling web search does not change how you call client.responses.create(). You still send a model name and an input. What changes is that you also pass a tools list containing a web search tool definition. When the model decides a request would benefit from current information, it invokes the tool itself — you do not manually trigger a search — the server performs the search, feeds the results back into the model's context, and the model produces a final answer informed by what it found. This entire loop happens inside a single call to responses.create(); you do not need to handle intermediate steps yourself, unlike some other tool-calling patterns where you must execute a function locally and send its result back.
This "the model decides" behavior is important to internalize. Adding the web search tool does not force a search on every request. If the model judges that a question does not require current information (for example, "explain what a for loop does"), it may answer directly from its own knowledge without searching at all. This is generally the right default, since it avoids unnecessary latency and cost, but it also means you cannot always assume a search happened just because you enabled the tool. Later lessons in this unit cover how to inspect the response to confirm whether a search actually occurred.
Minimal Example
from openai import OpenAI
client = OpenAI()
response = client.responses.create(
model="gpt-5.6-terra",
tools=[{"type": "web_search"}],
input="What are the current LTS versions of Node.js, and when does the current one reach end of life?",
)
print(response.output_text)
The key addition compared to a plain call is the tools parameter, a list containing one dictionary with "type": "web_search". This tells the Responses API that the web search tool is available for the model to use during this request. Nothing else about the call changes — model and input behave exactly as they do without the tool.
Note: The exact tool type string (
"web_search") and any additional configuration keys accepted alongside it are specific to the API version you are targeting. Confirm the current tool name and accepted parameters against the official OpenAI API reference before relying on this in production, since built-in tool identifiers are among the details most likely to be revised between API versions.
Running this, response.output_text gives you the final, synthesized answer — a plain string, exactly as it would be without any tool. This is deliberate: output_text is a convenience property that flattens the final assistant message regardless of how many intermediate steps (like a web search) the model took to produce it. If all you need is the answer text, you never have to touch anything else on the response object.
Inspecting What Happened Under the Hood
For anything beyond a toy example, you will want to know more than just the final text — specifically, whether a search actually happened and what was found. The Responses API exposes this through the output list on the response object, which contains an ordered sequence of "items" representing each step the model took.
from openai import OpenAI
client = OpenAI()
response = client.responses.create(
model="gpt-5.6-terra",
tools=[{"type": "web_search"}],
input="What is the current record for the fastest marathon time, and who holds it?",
)
for item in response.output:
print("item type:", item.type)
print("---")
print(response.output_text)
Iterating over response.output lets you see every distinct item type the model produced during this call. When a web search is performed, you will typically see an item representing the search call itself (often something like web_search_call) followed by the model's final message item (typically message). If the model answered without searching, you will only see the message item.
This distinction matters for debugging and for cost tracking: if you expect search-dependent behavior but never see a search item appear, that is a signal the model judged the query as not needing current information — which may be correct, or may indicate your prompt needs to be more explicit about wanting current data. You will use this same inspection pattern in Lesson 4 when extracting citation details from a search-backed response, so it is worth getting comfortable with the shape of response.output now.
Note: The exact item
typestring(s) used to represent a search step, and the fields available on that item, can change between API versions. Print the raw output structure for your SDK version and confirm the field names against current documentation before building production logic that depends on them.
Forcing Versus Allowing Search
By default, giving the model the web search tool makes it available, not mandatory. In most cases, letting the model decide is the correct behavior — it is the entire point of tool calling that the model reasons about when a tool is useful. But there are cases where you specifically want to guarantee a search happens on every call, such as a "check current price" feature where an ungrounded answer is never acceptable regardless of how the model interprets the question.
Two practical strategies handle this without needing an explicit "force" flag (which built-in tools in the Responses API generally do not expose the way custom function tools sometimes do):
- Make the current-information need explicit in the prompt. Instead of "What is Bitcoin worth?" write "Search the web for Bitcoin's current price in USD right now and report the figure and the time it was retrieved." Explicit language about wanting live data strongly biases the model toward invoking the tool.
- Verify after the fact and retry or fail closed if no search occurred. Since you can inspect
response.outputfor a search item, your application logic can check for one and, if absent, either re-issue the request with stronger wording or return an explicit "could not verify current data" message rather than silently serving an unsearched answer.
The second strategy is the more robust one for production systems, because prompt wording alone is a soft signal — it improves the odds the model searches but does not guarantee it. Lesson 3 goes further into shaping search behavior for specific application needs, including domain restrictions and result counts.
Common Mistakes
Assuming a search happened just because the tool was enabled, which causes silent staleness. Enabling tools=[{"type": "web_search"}] only makes the capability available; the model still decides per-request whether to use it. Always check response.output for a search-related item when your application's correctness depends on fresh data, rather than trusting that the tool being present means it fired.
Reading only output_text and never inspecting output, which causes you to miss what the model actually did to produce that text. This is fine for a quick demo, but for anything where you need to log, audit, or cite sources, you need the structured output list, not just the flattened string.
Treating a fresh-looking answer as always correct, which happens because a synthesized answer reads confidently regardless of whether the underlying search results were relevant or authoritative. A search having occurred is not the same as the answer being accurate — Lesson 7 covers handling conflicting or low-quality sources in depth.
Best Practices
Log the output list, not just output_text, in any application where correctness matters. Persisting the structured output (or at least whether a search item appeared) gives you an audit trail for debugging wrong answers later, without needing to reproduce the exact query against a constantly changing web.
Write prompts that state the need for current information explicitly when your feature depends on freshness, rather than relying on the model to infer that intent from an ambiguous question.
Treat the web search tool definition as a versioned dependency. Because built-in tool schemas can change between API releases, pin the SDK version you test against and re-verify tool behavior when you upgrade, rather than assuming the shape of tools=[{"type": "web_search"}] is permanently fixed.