Skip to content

How to use Web Search API

Last updated View as MarkdownAgent setup

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.

Prerequisites

REST API

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" }
    }
  }'

Workers binding

Call Web Search API from a Worker with the websearch() method on the AI binding.

  1. 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"
  2. 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.

Request parameters

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" }
	}
}
query
stringrequiredminLength: 1maxLength: 1024The search query.
provider
stringdefault: ceramicenum: ceramic, exa, linkupThe search provider to use.
limit
integerdefault: 10minimum: 1maximum: 10The maximum number of search results to return.
byokAlias
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.
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,
});
gatewayId
stringrequiredThe ID of the AI Gateway to route the request through.
query
stringrequiredminLength: 1maxLength: 1024The search query.
provider
stringdefault: ceramicenum: ceramic, exa, linkupThe search provider to use.
limit
integerdefault: 10minimum: 1maximum: 10The maximum number of search results to return.
byokAlias
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.

Response format

{
	"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
	}
}

Optional fields are only included when the provider returns them.

Bring your own key (BYOK)

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.

  1. In the Cloudflare dashboard, go to the AI Gateway page.

    Go to AI Gateway ↗
  2. Select your gateway, then select Provider Keys.

  3. 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.

  4. 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 a 400 error instead of falling back to your credits.
  • You omit byokAlias — If the gateway has a stored key with the default alias 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.

Use web search as a tool

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.

src/index.tsts
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>;

Limits

Limit Value
Query length 1,024 characters
Results per request 10

Was this helpful?