You can call Web Search API from any backend with the REST API, or from a Worker with the AI binding. Both methods route requests through an AI Gateway.
- A Cloudflare account. If you do not have one, sign up ↗︎.
- An AI Gateway. Every account has a gateway named
default, or you can create a gateway. - AI Gateway credits loaded on your account, or a provider API key stored on your gateway.
Send a POST request to the /ai/websearch/ endpoint:
POST https://api.cloudflare.com/client/v4/accounts/{account_id}/ai/websearch/Authenticate with a Cloudflare API token that has both of the following permissions:
- Account > Workers AI > Read
- Account > AI Gateway > Read
curl https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/ai/websearch/ \
--request POST \
--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"query": "What are some fun things to do in Salt Lake City as fall approaches?",
"provider": "ceramic",
"limit": 5,
"options": {
"gateway": { "id": "default" }
}
}'Call Web Search API from a Worker with the websearch() method on the AI binding.
-
Add an AI binding to your Wrangler configuration file:
{ "$schema": "./node_modules/wrangler/config-schema.json", "name": "web-search-worker", "main": "src/index.ts", // Set this to today's date "compatibility_date": "2026-10-02", "ai": { "binding": "AI" } }name = "web-search-worker" main = "src/index.ts" # Set this to today's date compatibility_date = "2026-10-02" [ai] binding = "AI" -
Call
env.AI.websearch()with your gateway ID and query:src/index.tsts export default { async fetch(request, env): Promise<Response> { const response = await env.AI.websearch({ gatewayId: "default", query: "What are some fun things to do in Salt Lake City as fall approaches?", provider: "exa", limit: 5, }); const results = await response.json(); return Response.json(results); }, } satisfies ExportedHandler<Env>;
websearch() returns a standard Response object. Call response.json() to read the results.
Set provider to choose a search provider. If you do not set a provider, Web Search API uses Ceramic.ai as default.
{
"query": "What are some fun things to do in Salt Lake City as fall approaches?",
"provider": "ceramic",
"limit": 5,
"options": {
"gateway": { "id": "default" }
}
}stringrequiredminLength: 1maxLength: 1024The search query.stringdefault: ceramicenum: ceramic, exa, linkupThe search provider to use.integerdefault: 10minimum: 1maximum: 10The maximum number of search results to return.stringpattern: ^[A-Za-z0-9_-]{1,64}$The alias of a provider API key stored on your gateway. If set, the request fails instead of falling back to AI Gateway credits when the key is not configured.objectrequiredRequest options.const response = await env.AI.websearch({
gatewayId: "default",
query: "What are some fun things to do in Salt Lake City as fall approaches?",
provider: "ceramic",
limit: 5,
});stringrequiredThe ID of the AI Gateway to route the request through.stringrequiredminLength: 1maxLength: 1024The search query.stringdefault: ceramicenum: ceramic, exa, linkupThe search provider to use.integerdefault: 10minimum: 1maximum: 10The maximum number of search results to return.stringpattern: ^[A-Za-z0-9_-]{1,64}$The alias of a provider API key stored on your gateway. If set, the request fails instead of falling back to AI Gateway credits when the key is not configured.{
"items": [
{
"url": "https://example.com/salt-lake-city-fall-guide",
"title": "Fall in Salt Lake City: A Local's Guide",
"description": "From scenic drives up Big Cottonwood Canyon to pumpkin patches..."
}
],
"metadata": {
"query": "What are some fun things to do in Salt Lake City as fall approaches?",
"requestId": "<REQUEST_ID>",
"latencyMs": 612
}
}arrayThe search results.objectInformation about the request.Optional fields are only included when the provider returns them.
If you have an existing account with a search provider, you can use your own API key instead of AI Gateway credits. The provider bills you directly.
-
In the Cloudflare dashboard, go to the AI Gateway page.
Go to AI Gateway ↗ -
Select your gateway, then select Provider Keys.
-
Add an API key for Ceramic.ai, Exa, or Linkup, and assign it an alias — for example,
default. If the provider is not listed, select Configure custom providers to add it. -
Pass the provider and alias in your request.
curl https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/ai/websearch/ \
--request POST \
--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"query": "What is Cloudflare Workers?",
"provider": "exa",
"byokAlias": "default",
"options": {
"gateway": { "id": "default" }
}
}'const response = await env.AI.websearch({
gatewayId: "default",
query: "What is Cloudflare Workers?",
provider: "exa",
byokAlias: "default",
});Your API key is never sent in the request. AI Gateway retrieves the key stored on the gateway for the provider and alias, and encrypts stored keys with Secrets Store.
AI Gateway selects credentials as follows:
- You set
byokAlias— AI Gateway uses the stored key with that alias. If the provider or alias is not configured on the gateway, the request fails with a400error instead of falling back to your credits. - You omit
byokAlias— If the gateway has a stored key with thedefaultalias for the provider, AI Gateway uses that key. Otherwise, the search is billed to your AI Gateway credits.
For more information, refer to Bring your own keys.
You can give a model access to web search by defining a web_search tool. When the model calls the tool, run the search and pass the results back to the model.
const MODEL = "@cf/google/gemma-4-26b-a4b-it";
export default {
async fetch(request, env): Promise<Response> {
const prompt = "What happened during the last Cloudflare Birthday Week?";
const messages = [{ role: "user", content: prompt }];
const completion = await env.AI.run(
MODEL,
{
messages,
tools: [
{
type: "function",
function: {
name: "web_search",
description: "Search the web for current information.",
parameters: {
type: "object",
properties: { query: { type: "string" } },
required: ["query"],
},
},
},
],
},
{ gateway: { id: "default" } },
);
const toolCall = completion.tool_calls?.[0];
if (toolCall?.name !== "web_search") {
return Response.json(completion);
}
const searchResponse = await env.AI.websearch({
gatewayId: "default",
query: toolCall.arguments.query,
limit: 5,
});
const searchResults = await searchResponse.json();
const finalResponse = await env.AI.run(
MODEL,
{
messages: [
...messages,
{
role: "tool",
name: "web_search",
content: JSON.stringify(searchResults),
},
],
},
{ gateway: { id: "default" } },
);
return Response.json(finalResponse);
},
} satisfies ExportedHandler<Env>;| Limit | Value |
|---|---|
| Query length | 1,024 characters |
| Results per request | 10 |