> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mka1.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Web search

> Let a model search the web and read pages, and see which provider handles those requests and what data it receives.

Add a web search tool to a [Responses](https://docs.mka1.com/docs/responses.md) request when the model needs current information from the public web. The MKA1 API sends the model's searches and page requests to [Serper](https://serper.dev), a third-party search API that returns Google search results.

This page explains how to enable web search, what each outbound request contains, and how to limit web search to specific domains.

## Enable web search

Add a tool with type `web_search` to `tools`. The API also accepts `web_search_2025_08_26`, `web_search_preview`, and `web_search_preview_2025_03_11`, which give the model the same tools.

A web search tool gives the model three tools. The model decides when to call each one:

| Tool | What it does | How the call appears in the response output |
| - | - | - |
| `web_search` | Runs a search query and returns titles, links, and snippets | `web_search_call` item with `action.type` set to `search` and the query |
| `scrape` | Reads the text of a page at a URL | `web_search_call` item with `action.type` set to `open_page` and the URL |
| `web_find` | Finds lines that match a word or pattern on a page | `web_search_call` item with `action.type` set to `find`, the URL, and the pattern |

Inspect the `web_search_call` items in the response output to see each query and URL the model requested.

This request asks a question that needs current information:

```ts TypeScript SDK theme={null}
import { SDK } from '@meetkai/mka1';

const sdk = new SDK({ bearerAuth: 'Bearer <mka1-api-key>' });

const response = await sdk.llm.responses.create({
  xOnBehalfOf: '<end-user-id>', // optional — attribute the request to one of your end users
  responsesCreateRequest: {
    model: 'auto',
    input: 'What is the latest stable release of Python? Cite your sources.',
    tools: [{ type: 'web_search', searchContextSize: 'low', userLocation: { country: 'US' } }],
  },
});
console.log(response);
```

```ts OpenAI SDK theme={null}
import OpenAI from 'openai';

const openai = new OpenAI({
  apiKey: '<mka1-api-key>',
  baseURL: 'https://apigw.mka1.com/api/v1/llm/',
  defaultHeaders: { 'X-On-Behalf-Of': '<end-user-id>' },
});

const response = await openai.responses.create({
  model: 'auto',
  input: 'What is the latest stable release of Python? Cite your sources.',
  tools: [{ type: 'web_search', search_context_size: 'low', user_location: { type: 'approximate', country: 'US' } }],
});
console.log(response);
```

```python Python SDK theme={null}
from meetkai_mka1 import SDK

sdk = SDK(bearer_auth="Bearer <mka1-api-key>")

response = sdk.llm.responses.create(
    x_on_behalf_of="<end-user-id>",  # optional — attribute the request to one of your end users
    model="auto",
    input="What is the latest stable release of Python? Cite your sources.",
    tools=[{"type": "web_search", "search_context_size": "low", "user_location": {"type": "approximate", "country": "US"}}],
)
print(response)
```

```csharp C# SDK theme={null}
using System.Collections.Generic;
using MeetKai.MKA1;
using MeetKai.MKA1.Types.Components;

var sdk = new SDK(bearerAuth: "Bearer <mka1-api-key>");

var response = await sdk.Llm.Responses.CreateAsync(
    new ResponsesCreateRequest()
    {
        Model = "auto",
        Input = ResponsesCreateRequestInput.CreateStr("What is the latest stable release of Python? Cite your sources."),
        Tools = new List<ResponsesCreateRequestTool>
        {
            ResponsesCreateRequestTool.CreateWebSearchToolDefinition(new WebSearchToolDefinition()
            {
                Type = WebSearchToolDefinitionType.WebSearch,
                SearchContextSize = WebSearchToolDefinitionSearchContextSize.Low,
                UserLocation = new UserLocation() { Country = "US" },
            }),
        },
    },
    xOnBehalfOf: "<end-user-id>" // optional — attribute the request to one of your end users
);
System.Console.WriteLine(response);
```

```bash CLI theme={null}
mka1 llm responses create \
  -H 'X-On-Behalf-Of: <end-user-id>' \
  --model auto \
  --input '"What is the latest stable release of Python? Cite your sources."' \
  --tools '[{"type": "web_search", "search_context_size": "low", "user_location": {"country": "US"}}]'
```

```bash Bash theme={null}
curl https://apigw.mka1.com/api/v1/llm/responses \
  --request POST \
  --header 'Authorization: Bearer <mka1-api-key>' \
  --header 'X-On-Behalf-Of: <end-user-id>' \
  --header 'Content-Type: application/json' \
  --data '{
  "model": "auto",
  "input": "What is the latest stable release of Python? Cite your sources.",
  "tools": [
    {
      "type": "web_search",
      "search_context_size": "low",
      "user_location": { "country": "US" }
    }
  ]
}'
```

### Tool options

| Field | Effect |
| - | - |
| `search_context_size` | Number of results each search requests: `low` requests 5, `medium` requests 10, and `high` requests 20. On `web_search`, the default is `medium`. On `web_search_preview`, omitting it requests 20 results. |
| `user_location` | Localizes results. The API uses `city`, `region`, and `country`. It doesn't send `timezone` to the provider. |
| `filters.allowed_domains` | Restricts search results, page reads, and citations to the listed domains. See [Restrict web search to specific domains](#restrict-web-search-to-specific-domains). Available on `web_search` and `web_search_2025_08_26` only. |

For the full schema, see [Create a response](https://docs.mka1.com/api-reference/responses/create-an-agent-powered-response-with-tool-support.md).

## What the provider receives

All web search traffic goes from the MKA1 platform to the provider over HTTPS. Your end users' devices don't contact the provider, so the provider doesn't see their IP addresses.

### Searches

Each `web_search` call sends one request to `google.serper.dev` with these fields:

| Field | Value |
| - | - |
| `q` | The search query the model wrote. With `filters.allowed_domains` set, the API appends `site:` operators for as many listed domains as fit within Google's query limits; results are filtered by domain regardless. |
| `num` | The number of results, set by `search_context_size`. |
| `gl` | A two-letter country code. The model can set it. Otherwise, the API derives it from `user_location.country` and falls back to `us`. |
| `hl` | A language code. The model can set it. The default is `en`. |
| `location` | A location string. The model can set it. Otherwise, the API joins `user_location.city`, `region`, and `country` with commas. The field is omitted when neither source provides a value. |
| `safe` | Always `active`, which turns on SafeSearch. |

The model writes the query from the conversation. A query can therefore include text from the input or instructions, such as a name, a product, or a place the user mentioned.

### Page reads

Each `scrape` or `web_find` call sends the URL the model chose to `scrape.serper.dev`. Serper fetches the page and returns its text. For `web_find`, the MKA1 platform matches the pattern against that text itself; the pattern isn't sent to the provider.

### Country lookups

If the model sets a country that isn't a two-letter code or a country name the API recognizes, the API sends that country text to the public SerpApi Locations API (`serpapi.com/locations.json`) to find its country code. The lookup request contains only the country text and a result limit of 1. If the lookup fails or finds nothing, the search uses `us`.

### What isn't sent

The platform doesn't forward your MKA1 authentication headers or identity headers such as `X-On-Behalf-Of` to these providers. It doesn't add organization, team, or user identifiers from the request context to provider payloads. It authenticates to Serper with the cluster's own Serper key, and country lookups carry no key.

The platform doesn't send the conversation, instructions, or files as separate payloads. However, the model can copy their contents into a query, URL, location, language, or country value. Those values are sent without redacting credentials or identifiers they contain. For example, a page URL can contain an access token or user ID in its query string, even when its domain is allowed. URLs that carry credentials before the host (`user:password@`) are refused when a domain list is set.

Separately from the provider, the MKA1 platform records each web search tool call on the cluster. The call's arguments (query, URL, pattern, location, language, and country) and its output (search results, page text, or matching lines) are written to the platform audit log and, in shortened form, to request traces. The `web_search_call` items in the response output carry the query, URL, and pattern, and are stored with the response when the response is stored. Serper sees the cluster's outbound IP address, not your end users'.

## Caching

Search results aren't cached. Each `web_search` call sends a new request to the provider.

Page text from `scrape` and `web_find` is cached on the platform for 5 minutes. The cache key is the URL alone, so the cache is shared by all callers on the cluster. A repeat read of the same URL within that window doesn't send a new request to the provider. The [domain list](#restrict-web-search-to-specific-domains) and [network policy](#outbound-network-policies) checks still run before the cache is read.

## Usage and billing

Each `web_search` call counts as one search. Each `scrape` or `web_find` call that isn't served from the cache also counts as one search. Searches are priced under the `serper` price entry, and their cost counts toward [budgets](https://docs.mka1.com/docs/budgets.md). Country lookups aren't billed.

## Restrict web search to specific domains

Set `filters.allowed_domains` to limit web search to a list of domains. The `filters` field is available on `web_search` and `web_search_2025_08_26`. A request that sets `filters` on a `web_search_preview` type fails with HTTP 400.

Write each entry as a hostname, such as `python.org`, and omit the scheme. If an entry starts with `http://` or `https://` or ends with `/`, the API removes that part. Each entry also covers its subdomains: `python.org` covers `docs.python.org` but not `notpython.org`. Matching ignores case.

A list can have up to 100 entries. A request with an entry that contains a path, a query string, a port, credentials, an IP address, or a wildcard, or whose hostname has no dot (such as `localhost`), fails with HTTP 400. A `filters` object with an unknown key, such as `allowedDomains`, also fails with HTTP 400. An empty or omitted list leaves web search unrestricted.

With a domain list set:

* The API removes search results that aren't on the listed domains, so the model can receive fewer results than `search_context_size` requests.
* `scrape` and `web_find` refuse URLs outside the listed domains before sending anything to the provider, and the model receives an error for that call.
* The API removes URL citations (`url_citation` annotations) that point outside the listed domains from the output.
* Each search runs at most 4 provider searches, each counted as a search. A long list is split across those searches in the order you give it. If the list doesn't fit, the API runs 3 searches restricted to the first domains plus one unrestricted search filtered to the whole list, so later domains can still appear but less reliably. Put the most important domains first.

```ts TypeScript SDK theme={null}
const response = await sdk.llm.responses.create({
  xOnBehalfOf: '<end-user-id>',
  responsesCreateRequest: {
    model: 'auto',
    input: 'Summarize what is new in the latest Python release.',
    tools: [{ type: 'web_search', filters: { allowedDomains: ['python.org'] } }],
  },
});
console.log(response);
```

```ts OpenAI SDK theme={null}
const response = await openai.responses.create({
  model: 'auto',
  input: 'Summarize what is new in the latest Python release.',
  tools: [{ type: 'web_search', filters: { allowed_domains: ['python.org'] } }],
});
console.log(response);
```

```python Python SDK theme={null}
response = sdk.llm.responses.create(
    x_on_behalf_of="<end-user-id>",
    model="auto",
    input="Summarize what is new in the latest Python release.",
    tools=[{"type": "web_search", "filters": {"allowed_domains": ["python.org"]}}],
)
print(response)
```

```csharp C# SDK theme={null}
var response = await sdk.Llm.Responses.CreateAsync(
    new ResponsesCreateRequest()
    {
        Model = "auto",
        Input = ResponsesCreateRequestInput.CreateStr("Summarize what is new in the latest Python release."),
        Tools = new List<ResponsesCreateRequestTool>
        {
            ResponsesCreateRequestTool.CreateWebSearchToolDefinition(new WebSearchToolDefinition()
            {
                Type = WebSearchToolDefinitionType.WebSearch,
                Filters = new WebSearchFilters() { AllowedDomains = new List<string> { "python.org" } },
            }),
        },
    },
    xOnBehalfOf: "<end-user-id>"
);
System.Console.WriteLine(response);
```

```bash CLI theme={null}
mka1 llm responses create \
  --model auto \
  --input '"Summarize what is new in the latest Python release."' \
  --tools '[{"type": "web_search", "filters": {"allowed_domains": ["python.org"]}}]'
```

```bash Bash theme={null}
curl https://apigw.mka1.com/api/v1/llm/responses \
  --request POST \
  --header 'Authorization: Bearer <mka1-api-key>' \
  --header 'X-On-Behalf-Of: <end-user-id>' \
  --header 'Content-Type: application/json' \
  --data '{
  "model": "auto",
  "input": "Summarize what is new in the latest Python release.",
  "tools": [
    {
      "type": "web_search",
      "filters": { "allowed_domains": ["python.org"] }
    }
  ]
}'
```

A domain list doesn't stop data from going to the provider: searches and allowed page reads still go to Serper as described in [What the provider receives](#what-the-provider-receives).

The domain list is checked against the URL the model requests. The platform doesn't see redirects that happen while the provider fetches the page, so if a page on an allowed domain redirects to another domain, the returned content can come from that other domain.

The domain list applies to `web_search`, `scrape`, and `web_find` only. Other tools in the same request, such as `shell`, `code_interpreter`, `mcp`, and OpenAPI tools, can still reach other hosts. Use [outbound network policies](#outbound-network-policies) to restrict where a request can connect.

## Outbound network policies

If the cluster enforces outbound network policies and your organization or team has a policy that restricts destinations, web search traffic must pass that policy:

* Allow `google.serper.dev` for searches and `scrape.serper.dev` for page reads. Allow `serpapi.com` if the model may need country lookups.
* Each URL that the model asks `scrape` or `web_find` to read must also be allowed. A URL outside the policy fails and the model receives an error for that call. With a policy in force, URLs with a port other than 80 or 443, or whose host is an IP address, are refused as well.
* If the policy can't be verified, outbound requests are refused.

To manage policies, use [Set tenant network policy](https://docs.mka1.com/api-reference/sessions/set-tenant-network-policy.md).

## Availability on a cluster

Web search is available only when the cluster operator has configured a Serper API key (`SERPER_API_KEY`). On a cluster without one, a Responses request that includes any web search tool fails with HTTP 400 and the error code `feature_unavailable`. The error's `service` field is `web_search` and its `param` field names the tool, for example `tools[0].type`. Requests without a web search tool aren't affected.

In a [batch](https://docs.mka1.com/docs/batch-processing.md), the affected item fails with `code: feature_unavailable` instead.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.