# Web Search (`apify/web-search`) Actor

Search the web and get organic results as JSON with title, URL, snippet, and position. You can filter by date, country, language, and domain. The Actor provides a real-time API.

- **URL**: https://apify.com/apify/web-search.md
- **Developed by:** [Apify](https://apify.com/apify) (Apify)
- **Stats:** 8 total users, 0 monthly users, 86.7% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 search results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

**Get web search results as JSON** with a single API call. Send a query, get back organic results with title, URL,
snippet, and position. Filter by date, country, language, and domain. No proxy to configure, no browser to run.

Web Search runs in [Standby mode](https://docs.apify.com/platform/actors/running/standby), so it answers HTTP
requests directly like a web server. Use it as the search tool for an AI agent, or call it from any script or backend.

### Why use Web Search?

- **Give an AI agent a search tool.** Results come back as JSON the model can read, in the same request. Ground
  answers in current web content without a results page to parse.
- **Filter without search operators.** Date range, country, language, and domain allow/block lists are input fields.
- **No infrastructure to run.** No proxy, no browser, no server. Call the endpoint and get a response.
- **Call it from anywhere.** Directly via HTTP, through the [Apify API](https://docs.apify.com/api/v2) client
  libraries for Python and JavaScript, as an MCP tool, or from [Apify integrations](https://docs.apify.com/integrations)
  like Make, Zapier, and n8n.

### How much does it cost?

Web Search uses [pay-per-event pricing](https://docs.apify.com/platform/actors/publishing/monetize/pay-per-event):
one event per successful search. `maxResults` does not change the price. A failed request (invalid
input, blocked, timed out) is not charged. Current prices are on the Actor's page in Apify Store.

### Getting started

1. Authenticate with your [Apify API token](https://console.apify.com/settings/integrations), either as a `token`
   query parameter or as an `Authorization: Bearer <token>` header (the header is safer, since URLs end up in logs
   and browser history).

2. Send a `GET` or `POST` request to `https://web-search.apify.actor` with your query. Both accept the same fields:

   ```bash
   curl 'https://web-search.apify.actor/?query=latest+AI+agent+payment+protocols&maxResults=5&token=***'
   ```

   ```bash
   curl -X POST 'https://web-search.apify.actor/' \
     -H 'Content-Type: application/json' \
     -H 'Authorization: Bearer ***' \
     -d '{
       "query": "latest AI agent payment protocols",
       "maxResults": 5,
       "allowedDomains": ["wikipedia.org", "github.com"]
     }'
   ```

3. Read `results` from the JSON response. No polling, no separate results endpoint.

Prefer a regular Actor run instead? Enter the same fields on the Input tab and select **Start**. Web Search performs
one search, saves the result to the run's dataset, and exits, like any other Actor. A regular run gives you Apify's
native integrations and lets you schedule recurring searches.

[Scheduling your Actor](https://www.youtube.com/watch?v=1jI7WcVQmwM)

### Input

These fields are accepted by `https://web-search.apify.actor`, either as `GET` query parameters or a `POST` JSON
body. The same fields appear on the Input tab for a regular Actor run. `query` is the only required field.

| Field                               | Description                                                                                                                                                     |
| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `query`                             | Plain search text, passed to the search engine as is (required). Operators like `site:` are not supported; use `allowedDomains` and `blockedDomains` instead.   |
| `provider`                          | Search engine to use. Currently `google`.                                                                                                                       |
| `maxResults`                        | Maximum number of results to return. When omitted, every organic result on the fetched page is returned.                                                        |
| `languageCode`                      | [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes) code such as `en`, `de`, `cs`. A region suffix like `pt-BR` works too. Default: `en`. |
| `countryCode`                       | ISO 3166-1 alpha-2 code such as `US`, `DE`, `CZ`. A country the search engine has no market for returns `INVALID_COUNTRY`. Default: `US`.                       |
| `location`                          | A place name, most specific first, e.g. `Paris, Ile-de-France, France`. Best-effort.                                                                            |
| `dateFrom` / `dateTo`               | `YYYY-MM-DD`, inclusive bounds on publication date. Best-effort.                                                                                                |
| `allowedDomains` / `blockedDomains` | Up to 10 hostnames each, e.g. `wikipedia.org`. Comma-separated in `GET`, a JSON array in `POST`.                                                                |

#### Locale fields

`languageCode` sets the language of the search itself. It biases ranking toward pages in that language and
localizes the search engine's interface. It neither translates results nor filters them strictly by language.

`countryCode` runs the search as if issued from that country. The search engine's country-specific domain is
queried (`google.de` for `DE`) and results are ranked for that market, which changes both the ordering and which
local businesses appear. It does not restrict results to sites hosted in that country.

`location` is the searcher's presumed location, so local and "near me" results match that place. It does not
filter the pages themselves.

#### Domain filters

A domain matches itself and all of its subdomains, so `wikipedia.org` also matches `en.wikipedia.org`. A bare
top-level domain works too: `gov` matches every domain under it.

### Output

Every successful response has the same three keys: the `query` you sent, a `search` object with metadata about the
search, and a `results` array of organic results. In a regular Actor run, the same object is saved as one dataset
item, and you can download it in JSON, HTML, CSV, or Excel format from the **Output** tab.

```json
{
    "query": "latest AI agent payment protocols",
    "search": {
        "provider": "google",
        "searchedAt": "2026-08-27T11:42:18.512Z",
        "durationSecs": 2.104,
        "correctedQuery": null,
        "suggestedQuery": null,
        "totalResults": 1240000,
        "countryCode": "US",
        "languageCode": "en",
        "location": null,
        "providerDomain": "google.com",
        "dateFrom": null,
        "dateTo": null,
        "checkUrl": "https://www.google.com/search?q=latest+AI+agent+payment+protocols&gl=us&hl=en"
    },
    "results": [
        {
            "position": 1,
            "url": "https://example.com/agent-payments",
            "domain": "example.com",
            "title": "Agent Payments Protocol",
            "snippet": "A protocol for authorizing payments by autonomous agents.",
            "siteLinks": [{ "title": "Specification", "url": "https://example.com/agent-payments/spec" }]
        }
    ]
}
```

If the request fails (invalid input, blocked, timed out), you get an error with `code` and a matching HTTP
status instead:

```json
{
    "code": "INVALID_DOMAINS",
    "error": "\"allowedDomains\" accepts at most 10 domains, got 12."
}
```

See the Actor's **Endpoints** tab for the full API reference.

#### Response

| Field                                          | Description                                                                                                                                                      |
| ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `results[].position`                           | 1-based rank within `results`, counted after filtering. Always dense: 1, 2, 3, ...                                                                               |
| `results[].url` / `domain`                     | Absolute destination URL, never a search engine redirect.                                                                                                        |
| `results[].title` / `snippet`                  | Result heading and excerpt. `snippet` is `null` when the search engine shows none.                                                                               |
| `results[].displayedUrl` / `siteName` / `date` | Breadcrumb string, publisher name, and shown publication date, when the search engine displays them.                                                             |
| `results[].emphasizedKeywords`                 | Bolded terms inside the snippet, omitted when `snippet` is `null`.                                                                                               |
| `results[].siteLinks`                          | Sub-page links attached to the result, omitted when there are none.                                                                                              |
| `search.provider`                              | Which search engine served the result.                                                                                                                           |
| `search.providerDomain`                        | The engine domain queried, e.g. `google.de` for `countryCode: "DE"`.                                                                                             |
| `search.totalResults`                          | The search engine's own rough estimate of all matching pages, not the number returned. `null` when the page shows none.                                          |
| `search.correctedQuery` / `suggestedQuery`     | The query the search engine silently searched instead, and the one it offered as "did you mean" without applying it.                                             |
| `search.checkUrl`                              | A link you can open to see the query as sent to the search engine. It will not reproduce the same results, because engines personalize by IP, account, and time. |

### Search via MCP

Web Search also runs a [Model Context Protocol](https://modelcontextprotocol.io) server at `/mcp`, exposing a single
`search` tool with the same parameters and JSON output as the REST API. Add it to any MCP-compatible client, like
Claude Code or Cursor, using the Actor's `/mcp` endpoint:

```bash
claude mcp add web-search https://web-search.apify.actor/mcp -t http
```

Get it through the [Apify MCP server](https://mcp.apify.com/) instead. Add `apify/web-search` to skip connecting to
the Actor's own `/mcp` endpoint directly.

### Tips for getting the best results

- An empty `results` array does not mean the query has no matches. Domain filters can drop every result on the
  page. Loosen the filters before changing the query.
- `dateFrom`, `dateTo`, and `location` are best-effort. The search engine applies them from its own signals, which
  are sometimes missing or wrong.

### FAQ

#### What's a web search tool?

A web search tool runs a query on behalf of an AI agent or LLM app and returns the results as structured JSON the
model can read, instead of a results page.

#### Do I need a proxy?

No. Web Search handles proxies and IP rotation for you.

#### Does Web Search return ads or featured snippets?

No. Only organic results are returned.

#### Does Web Search support search operators like `site:` or `filetype:`?

They are passed through to the search engine as part of the query, but not validated. For domain filtering, use
`allowedDomains` and `blockedDomains` instead, since those are guaranteed to apply.

#### Why do my results differ from what I see in a browser?

Search engines personalize by IP, account, and time. `checkUrl` shows the query as sent, not the same result set.

#### Why did I get `PARSE_ERROR`?

Search engines occasionally change their results page markup. This is a bug on our side, not yours. Please report
it in the Issues tab so it can be fixed.

#### Is scraping search results legal?

Scraping publicly available search results is generally accepted, but you are responsible for complying with the
search engine's terms of service and any laws that apply to your use case. If you're unsure, seek legal advice. Read
more in [this blog post](https://blog.apify.com/is-web-scraping-legal/).

#### Something not working?

Open an issue on the Actor's **Issues** tab with your input and the error you received, or
[chat with Apify support](https://apify.com/contact). If you need different fields, another search engine, or a
different result shape, ask through [Apify's custom solutions page](https://apify.com/custom-solutions).

# Actor input Schema

## `query` (type: `string`):

Plain search text, passed to the provider verbatim. Engine operator syntax (site:, intitle:, ...) is not supported - use allowedDomains/blockedDomains and dateFrom/dateTo instead.

## `provider` (type: `string`):

Search engine to query. "google" is the only supported value in v1.

## `maxResults` (type: `integer`):

Maximum number of organic results to return. When omitted, every organic result on the fetched results page is returned. Fewer results come back when the page holds fewer, or when the domain filters drop some.

## `languageCode` (type: `string`):

ISO 639-1 language code, for example "en", "de", "cs" - see https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes. A region suffix such as "pt-BR" is accepted too. It sets the language of the search engine interface and biases ranking toward pages written in that language. Results are neither translated nor strictly filtered by language.

## `countryCode` (type: `string`):

ISO 3166-1 alpha-2 country code, for example "US", "DE", "CZ". The search runs as if issued from that country: the matching Google domain is queried (google.de for "DE") and results are ranked for that market, which changes both the ordering and which local businesses appear. It does not restrict results to websites hosted or registered in that country. A country Google has no search domain for is rejected with INVALID_COUNTRY.

## `location` (type: `string`):

Human-readable place name, most specific first, for example "Paris, Ile-de-France, France". It is applied to the search engine only, as the searcher's presumed location, so local and "near me" results match that place. It does not filter the pages themselves. Best-effort and not validated: Google may ignore a place it cannot resolve.

## `dateFrom` (type: `string`):

Inclusive lower bound on publication date. Best-effort - see the README.

## `dateTo` (type: `string`):

Inclusive upper bound on publication date. Best-effort - see the README.

## `allowedDomains` (type: `array`):

Return only results from these domains. Max 10. A domain matches itself and all of its subdomains, so "wikipedia.org" also matches "en.wikipedia.org". A bare top-level domain works too: "gov" matches every domain under it. Examples: "wikipedia.org", "docs.python.org", "gov".

## `blockedDomains` (type: `array`):

Exclude results from these domains. Max 10. Matching works as for allowedDomains: a domain matches itself and all of its subdomains, and a bare top-level domain matches everything under it. Examples: "pinterest.com", "reddit.com", "ru".

## Actor input object example

```json
{
  "query": "latest AI agent payment protocols",
  "provider": "google",
  "languageCode": "en",
  "countryCode": "US"
}
```

# Actor output Schema

## `searchResults` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "query": "latest AI agent payment protocols",
    "languageCode": "en",
    "countryCode": "US"
};

// Run the Actor and wait for it to finish
const run = await client.actor("apify/web-search").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "query": "latest AI agent payment protocols",
    "languageCode": "en",
    "countryCode": "US",
}

# Run the Actor and wait for it to finish
run = client.actor("apify/web-search").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "query": "latest AI agent payment protocols",
  "languageCode": "en",
  "countryCode": "US"
}' |
apify call apify/web-search --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,apify/web-search"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/yD95d01y3SlKrPqQ7/builds/vopZSn1d8YRvdgakX/openapi.json
