Built-in Web Search
Set web_search: true and the model grounds its answer with live results.
POST https://api.shannon-ai.com/v1/chat/completions
from openai import OpenAI
client = OpenAI(api_key="YOUR_API_KEY", base_url="https://api.shannon-ai.com/v1")
response = client.chat.completions.create(
model="shannon-3",
messages=[{"role": "user", "content": "What is the weather forecast for Lisbon this weekend?"}],
extra_body={"web_search": True}, # a Shannon field, so it goes in extra_body
)
print(response.choices[0].message.content) import OpenAI from "openai";
const client = new OpenAI({ apiKey: "YOUR_API_KEY", baseURL: "https://api.shannon-ai.com/v1" });
const response = await client.chat.completions.create({
model: "shannon-3",
messages: [{ role: "user", content: "What is the weather forecast for Lisbon this weekend?" }],
web_search: true,
});
console.log(response.choices[0].message.content); curl https://api.shannon-ai.com/v1/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "shannon-3",
"messages": [
{"role": "user", "content": "What is the weather forecast for Lisbon this weekend?"}
],
"web_search": true
}' The reply is an ordinary chat completion with two additions. The answer points to the search results with numbers in square brackets, sources says what each number stands for, and annotations marks where each one is cited:
{
"id": "chatcmpl-3e5a7c9b1d2f4a6c8e0b2d4f6a8c1e3b",
"object": "chat.completion",
"created": 1791590400,
"model": "shannon-3",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Saturday in Lisbon is forecast to be sunny with a high near 24 °C [1]. Sunday turns cloudy, with a chance of light rain in the afternoon [2].",
"reasoning_content": null,
"annotations": [
{
"type": "url_citation",
"url_citation": {
"start_index": 66,
"end_index": 69,
"url": "https://weather.example/lisbon/weekend",
"title": "Lisbon weather: weekend outlook"
}
},
{
"type": "url_citation",
"url_citation": {
"start_index": 137,
"end_index": 140,
"url": "https://forecast.example/pt/lisbon/sunday",
"title": "Sunday forecast for Lisbon"
}
}
]
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 2874,
"completion_tokens": 41,
"total_tokens": 2915
},
"sources": [
{
"index": 1,
"title": "Lisbon weather: weekend outlook",
"url": "https://weather.example/lisbon/weekend"
},
{
"index": 2,
"title": "Sunday forecast for Lisbon",
"url": "https://forecast.example/pt/lisbon/sunday"
},
{
"index": 3,
"title": "Portugal: the week ahead",
"url": "https://news.example/portugal/weather-week"
}
]
} Switching search on
Search is a field of the request, off by default. Nothing is searched unless a request sends it.
| Field | Type | Default | Description | Applied by |
|---|---|---|---|---|
web_search | boolean | false | true runs a web search for this request and gives the results to the model before it answers. | Every Shannon model except shannon-coder-1 |
| Endpoint | Web search | How |
|---|---|---|
/v1/chat/completions | Yes | "web_search": true in the request body. |
/v1/messages | Yes | "web_search": true in the request body. |
/v1/responses | — | This endpoint answers from the model alone. Use one of the two above for a searched answer. |
On /v1/messages the field sits at the top level of the body, next to model and messages:
{
"model": "shannon-3",
"web_search": true,
"messages": [
{
"role": "user",
"content": "What is the weather forecast for Lisbon this weekend?"
}
]
} What a searched request does
- The API searches the web for the last user message. Earlier turns of the conversation give the search its context.
- The search runs on every request that sends
web_search: true. The model does not choose whether to search. - The results are put in front of the model as numbered sources, and the model writes its answer from them.
- The reply starts when the search is done. A streamed request sends nothing, neither headers nor events, until then, so allow for a longer wait before the first chunk.
What the reply contains
- The reply has the usual shape of its endpoint. The answer is in
choices[0].message.content(atextblock on/v1/messages). - The text can carry markers such as
[1]and[2]after the statements they support. Each number stands for one of the results the model read. sourcesnames those results: one entry for each result the model was given, withindex,titleandurl. The marker[1]is the entry withindex1. Results the answer does not cite are listed too.- On
/v1/chat/completionsthe message also hasannotations, in the OpenAI shape: oneurl_citationfor each source a marker names, withurl,title, andstart_indexandend_index, the position of the marker incontentcounted in characters (the end is not included). - In a stream
sourcescomes with the last chunk, and on/v1/messageswith themessage_deltaevent. The annotations come in one chunk just before the last one, asdelta.annotations. sourcesis there only when the search of this request found something. A reply without it was written without search results.
How searches are counted
Every plan includes a number of searches per day:
| Plan | Searches per day |
|---|---|
| Free | 3 |
| Plus | 30 |
| Standard | 50 |
| Pro | 60 |
- A request whose search found something uses one search of the day's allowance. A search that found nothing uses none.
- Chat and API share the allowance of the account that owns the key.
- The count starts again every day at 00:00 UTC.
- The search results are input tokens of the request and are billed with the rest of the prompt.
When the day's searches are used up
A request that sends web_search: true after the last search of the day is not refused. It is answered without a search: the status is 200 and the reply has no sources.
Search as a tool of your own
web_search: true is the only switch of the search described here. A function that you define in tools and name web_search is one of your own tools: the model returns a call, and your code runs the search and sends the result back.
Search tool types of other platforms, such as web_search_20250305 on /v1/messages or {"type": "web_search"} on /v1/responses, do not start a search here.
Errors
Web search adds no status code and no error type. A search that finds nothing, or a day without searches left, gives a normal answer without search results. Every other error is the same as for a request without search. Error Handling