Web Search
LLM API lets any model answer questions that need current, real-world information — recent news, live prices, today's docs, anything past the model's training cutoff. Activation is OpenAI-compatible, so a stock OpenAI SDK turns it on with no extra_body:
// POST /v1/chat/completions
{
"model": "openai/gpt-4o",
"web_search_options": {}, // presence enables search — an empty object is valid
"messages": [
/* ... */
],
}
You don't need a search-provider account, and you don't have to pick a model that ships its own search tool. When web_search_options is present, LLM API performs the searches for you, feeds the results to the model, and returns an answer grounded in what it found.
INFO
Enable on any model. Unlike OpenAI, LLM API does not gate web search behind *-search-preview
model names — the same request adds live search to a GPT, Claude, Gemini, or Bedrock model, with
one behavior and one result format.
Activation
There are two OpenAI-compatible surfaces, plus a legacy alias:
| Surface |
How to enable |
Chat Completions (/v1/chat/completions) |
Include a web_search_options object. Its presence enables search — an empty {} enables it with defaults. |
Responses API (/v1/responses) |
Include { "type": "web_search" } in the tools array. |
| Legacy alias (non-standard) |
The top-level web_search: true boolean still works and maps to web_search_options: {}. If both are present, the object wins. |
Prerequisites
- An LLM API key, passed as a
Bearer token in the Authorization header.
- Requests go to the OpenAI-compatible base URL
https://api.llmapi.ai/v1, so any OpenAI-compatible SDK or HTTP client works.
That's it — there's no separate search provider to sign up for or configure.
How It Works
Enable web search on a request and LLM API takes the best available path automatically:
- Model with built-in search (e.g.
*-search-preview models) → the model's own native search is used. These models always search, with or without the flag.
- Any other model (most OpenAI, Anthropic, Bedrock, Gemini, and open models) → LLM API runs the search for you. It performs the searches the model asks for, gives it the results, and repeats until the model is ready to answer.
Either way you send the same request and get back a standard OpenAI-compatible response. All searching happens on LLM API's side — you never have to handle tool calls yourself.
INFO
Why don't dedicated search models (*-search-preview) use LLM API's own search? For those models,
search is built into the model and runs on every request — there's no way to turn it off, and they
generally don't accept the function tools LLM API's search relies on. So we let their native
search run rather than layering a second (billed) search on top. If you want LLM API to drive the
search on every request, just pick a regular model (e.g. openai/gpt-4o) instead of a
search-preview model.
Basic Usage
Add a web_search_options object to any chat request. An empty object is enough:
curl -X POST "https://api.llmapi.ai/v1/chat/completions" \
-H "Authorization: Bearer $LLM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-4o",
"web_search_options": {},
"messages": [
{ "role": "user", "content": "What are the headline features of the latest Go release?" }
]
}'
Example Response
The response is a normal chat completion. The answer reflects the live search results, and no intermediate search tool-calls are surfaced to you:
{
"id": "chatcmpl-abc123",
"object": "chat.completion",
"created": 1234567890,
"model": "openai/gpt-4o",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "The latest Go release adds ... (summary grounded in live results, often citing the source URLs it used)."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 42,
"completion_tokens": 210,
"total_tokens": 252,
"cost_usd_input": 0.0006,
"cost_usd_output": 0.0015,
"cost_usd_web_search": 0.01
}
}
The token-cost fields (cost_usd_input, cost_usd_output) are always present; cost_usd_web_search appears only when the model actually searched. If the model decides it doesn't need to search (the answer is already in its knowledge), it simply answers and no web-search cost is incurred.
Search Options
web_search_options accepts OpenAI's standard configuration fields (all optional):
{
"model": "openai/gpt-4o",
"web_search_options": {
"search_context_size": "high", // "low" | "medium" | "high" — how broadly to search
"user_location": {
"type": "approximate",
"approximate": { "country": "US", "city": "San Francisco", "region": "CA" },
},
},
"messages": [
/* ... */
],
}
search_context_size — low, medium, or high. Controls how many results the search casts for (higher = broader/deeper, more cost). Omitted or unrecognized values fall back to the server default.
user_location — an approximate location hint used to bias results toward a locale where the backend supports it. Accepted and ignored (never an error) where it isn't supported.
Within each search, the model still chooses its own query, result count (capped server-side at 20 so a model can't request an unbounded, expensive result set), and an optional recency window (day/week/month/year).
Fetching Full Page Content (advanced)
By default, searches return short result snippets, which is fast and inexpensive. To have the model work from full page content instead of snippets, add the non-standard scrape extension inside web_search_options:
{
"model": "openai/gpt-4o",
"web_search_options": { "scrape": true }, // LLM API extension — not an OpenAI field
"messages": [
/* ... */
],
}
Fetching full pages is slower and costs more than snippets. scrape is an LLM API extension (stock OpenAI SDK clients never set it); it only takes effect where full-content fetching is available for your account, otherwise searches stay snippets-only.
Responses API
On /v1/responses, enable web search by adding the OpenAI-native web_search tool to the tools array:
curl -X POST "https://api.llmapi.ai/v1/responses" \
-H "Authorization: Bearer $LLM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-4o",
"input": "What shipped in the latest Go release?",
"tools": [ { "type": "web_search" } ]
}'
The request is routed to a web-search-capable model automatically, so you're not forced to name a *-search-preview model yourself. The response is a standard OpenAI Responses object.
Streaming
Web search works with stream: true. The search happens invisibly behind the scenes while the model decides what to look up; once it's ready, the final answer streams token-by-token. You never see intermediate search tool-calls or search-result events in the stream — only the answer, followed by the usual usage and cost summary.
curl -N -X POST "https://api.llmapi.ai/v1/chat/completions" \
-H "Authorization: Bearer $LLM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-4o",
"web_search_options": {},
"stream": true,
"messages": [
{ "role": "user", "content": "Latest news on the Mars sample return mission?" }
]
}'
You receive chat.completion.chunk token events for the answer only. The cost_usd_web_search value arrives in the final usage chunk, exactly as in the non-streaming case.
Using with SDKs
Because web_search_options is a standard OpenAI Chat Completions field, you pass it as a typed argument with your existing OpenAI-compatible SDK — no extra_body required.
Python
from openai import OpenAI
client = OpenAI(base_url="https://api.llmapi.ai/v1", api_key="your-api-key")
# Chat Completions — typed web_search_options field, no extra_body.
response = client.chat.completions.create(
model="openai/gpt-4o",
messages=[{"role": "user", "content": "What are the latest news headlines today?"}],
web_search_options={}, # or {"search_context_size": "high"}
)
print(response.choices[0].message.content)
# Responses API — the web_search tool.
resp = client.responses.create(
model="openai/gpt-4o",
input="What are the latest news headlines today?",
tools=[{"type": "web_search"}],
)
print(resp.output_text)
TypeScript
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.llmapi.ai/v1",
apiKey: "your-api-key",
});
// Chat Completions — typed web_search_options field, no extra_body.
const response = await client.chat.completions.create({
model: "openai/gpt-4o",
messages: [{ role: "user", content: "What are the latest tech news?" }],
web_search_options: {},
});
console.log(response.choices[0].message.content);
// Responses API — the web_search tool.
const resp = await client.responses.create({
model: "openai/gpt-4o",
input: "What are the latest tech news?",
tools: [{ type: "web_search" }],
});
console.log(resp.output_text);
INFO
Prefer web_search_options over the legacy web_search: true boolean: the boolean is a
non-standard extension that typed SDK clients can only send via extra_body, whereas
web_search_options is a first-class OpenAI field. Existing callers using web_search: true keep
working unchanged.
Combining with Your Own Tools
Web search works alongside your own function tools. The search capability is added to whatever tools you already send, and your tool_choice is left exactly as you set it:
{
"model": "openai/gpt-4o",
"web_search_options": {},
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather for a location",
"parameters": {
"type": "object",
"properties": { "location": { "type": "string" } },
},
},
},
],
"tool_choice": "auto",
"messages": [
{
"role": "user",
"content": "Compare today's forecast with what the news says about the storm.",
},
],
}
The model can use both your get_weather function and web search in the same turn.
Cost Tracking
Web-search cost is metered per search and reported separately from token cost in the usage object:
{
"usage": {
"prompt_tokens": 42,
"completion_tokens": 210,
"total_tokens": 252,
"cost_usd_input": 0.0006,
"cost_usd_output": 0.0015,
"cost_usd_web_search": 0.01
}
}
Each cost line is reported separately — there is no aggregate cost_usd_total field on a chat-completion response; sum the cost_usd_* lines if you need the grand total.
cost_usd_web_search is the total charge for the searches performed on the request (the per-search price times the number of searches). It is absent or zero when the model didn't search.
- The search count and cost also appear in your dashboard logs and analytics.
- Repeated identical searches are free. When the same query (and options, including
user_location) is searched again within a short period, the earlier result is reused at no extra cost or latency.
Limits & Reliability
- Per-request search cap. Each request is capped at a fixed number of searches. If the model hits the cap, it stops searching and answers with what it has — the request never fails.
- Search outages don't fail requests. If web search is temporarily unavailable, the model is told so and still produces an answer from its own knowledge. You get a normal response, not an error.
- No results. When a search returns nothing, the model is told so and answers accordingly (from its own knowledge, or by stating it couldn't find current information).
Citations
Some search-enabled models (e.g. OpenAI and Gemini) return citations in the message's annotations field; availability and format vary by provider:
{
"annotations": [
{
"type": "url_citation",
"url_citation": {
"url": "https://example.com/article",
"title": "Article Title",
"start_index": 0,
"end_index": 50
}
}
]
}
Each annotation has a type and a nested url_citation object; start_index/end_index mark the span of the answer text the citation supports. Router-executed search (via web_search_options) does not add annotations — models reached through it typically cite sources inline in the message content instead.
Best Practices
- Use
web_search_options for portability — it's a standard OpenAI field, works on every model, and needs no extra_body, so switching providers doesn't change your search code.
- Tune
search_context_size. Use low for quick fact lookups and high when the question needs broad coverage; higher sizes cost more.
- Stream for user-facing apps. The search stays invisible and the answer streams token-by-token for a responsive UX.
- Monitor cost. Watch
cost_usd_web_search in responses and your dashboard analytics; searches are billed in addition to tokens.
- Leave it on for factual assistants. The model searches only when the question needs it — questions it can answer directly incur no search cost.