# web-search (Used in MCP tests) (`apify/web-search`) Actor

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

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## 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://securitybyobscurity.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://securitybyobscurity.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** from Google or DuckDuckGo in one API call: organic results with title, URL,
snippet, and position. Filter by date and domain. Set country and language preferences. No proxy or browser to manage.

Web Search answers HTTP requests directly. Use it as a search tool for an AI agent or LLM app, 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.
- **Control search scope and locale.** Set date and domain filters, and choose a country and language for ranking.
- **No infrastructure to run.** Apify handles hosting, proxies, and IP rotation. Call the endpoint.
- **Call it from anywhere.** Use HTTP or MCP for direct requests. For regular Actor runs, use the
  [Apify API](https://docs.apify.com/api/v2) client libraries for Python and JavaScript, or
  [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/actors/publishing/monetize/pay-per-event):
one event per successful search, including searches with no results. `maxResults` doesn't change the price per search.
Failed searches (invalid input, blocked, timed out) aren't charged a search event.

A regular Actor run also charges an Actor Start event.
See the [Pricing tab](https://apify.com/apify/web-search/pricing) for current rates.

### Getting started

1. Use your [Apify API token](https://console.apify.com/settings/integrations) in a `token` query parameter
   or an `Authorization: Bearer <token>` header. The header is safer: URLs can appear 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"]
     }'
   ```

   Replace `***` with your Apify API token. The POST example restricts results to Wikipedia and GitHub.
   Omit `allowedDomains` to search across domains.

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

For a regular Actor run, enter the same fields on the Input tab and select **Start**. Web Search searches once,
saves the result to the run's dataset, and exits. Regular runs also work with Apify integrations and scheduled searches.

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

### Input

Send input to `https://web-search.apify.actor` as `GET` query parameters or a `POST` JSON body.
For a regular Actor run, enter it on the Input tab. `query` is the only required field.

| Field                               | Description                                                                                                             |
| ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `query`                             | Search text (required). Operators pass through unchecked.                                                               |
| `provider`                          | `google` (default) or `duckduckgo`. See [Choosing Google or DuckDuckGo](#choosing-google-or-duckduckgo).                |
| `maxResults`                        | Maximum results to return. Defaults to all results on one page. Raising it won't fetch more pages.                      |
| `languageCode`                      | ISO 639-1 code with an optional region suffix, like `en` or `pt-BR`. Default: `en`.                                     |
| `countryCode`                       | ISO 3166-1 alpha-2 country code, like `US`, `DE`, or `CZ`. Default: `US`. Unsupported markets return `INVALID_COUNTRY`. |
| `location`                          | Google-only place name, most specific first, like `Paris, Ile-de-France, France`. Best-effort.                          |
| `dateFrom` / `dateTo`               | Inclusive publication-date bounds in `YYYY-MM-DD` format. Best-effort.                                                  |
| `allowedDomains` / `blockedDomains` | Up to 10 hostnames per list. Comma-separated in `GET`, a JSON array in `POST`.                                          |

When setting both date bounds, `dateFrom` must not be later than `dateTo`.
Date filters rely on the search engine's publication-date signals, which can be missing or wrong.

#### Choosing Google or DuckDuckGo

Use Google to target a location or get more result details. Try DuckDuckGo for a second ranking,
or if Google finds nothing or fails. DuckDuckGo returns one page of about 10 results.

|                                                    | Google | DuckDuckGo |
| -------------------------------------------------- | ------ | ---------- |
| `location`                                         | yes    | no         |
| `countryCode`                                      | 192    | 57         |
| `dateFrom` / `dateTo`                              | yes    | yes        |
| `allowedDomains` / `blockedDomains`                | yes    | yes        |
| `emphasizedKeywords`                               | yes    | yes        |
| `totalResults`, `correctedQuery`, `suggestedQuery` | yes    | no         |
| `date`, `siteName`, `siteLinks`                    | yes    | no         |

You can query both providers in parallel to widen coverage or cross-check results. Each search is charged as its own
event.

#### Locale fields

Use an [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes) `languageCode` like `en`, `de`, or `cs`.
A region suffix works too, as in `pt-BR`. Google uses `languageCode` to localize its interface and favor pages in that language.
It doesn't translate results or strictly filter them by language.

`countryCode` runs the search as if you were in that country. This changes result order and which local businesses appear.
Google uses the matching domain (`google.de` for `DE`). DuckDuckGo uses the matching region,
preferring the `languageCode` variant (`CA` with `fr`) over the default. Results aren't filtered by hosting country.

`location` sets the searcher's presumed location for local and "near me" results. It doesn't filter the pages themselves. DuckDuckGo has no location
parameter, so `provider: "duckduckgo"` rejects it with `INVALID_PARAMETER`.

#### 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.
When set, `allowedDomains` keeps matching domains and `blockedDomains` excludes them. Both default to empty lists.
Filters can remove every result on the page. If `results` is empty, loosen the filters before changing the query.

### Output

A successful search returns JSON with `query`, a `search` object with search details,
and a `results` array of organic results. An empty `results` array still counts as a successful search.
A regular Actor run saves this JSON as one dataset item. Download it as JSON, HTML, CSV,
or Excel from the **Output** tab.

Example response for a search without domain filters:

```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 search fails (invalid input, blocked, timed out), Web Search returns an error with `code`,
`error`, and a matching HTTP status. See [Troubleshooting](#troubleshooting) for recovery steps:

```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`                | Absolute destination URL, never a search engine redirect.                                                             |
| `results[].domain`             | Hostname of the destination URL, including subdomains.                                                                |
| `results[].title`              | Result heading.                                                                                                       |
| `results[].snippet`            | Result excerpt. `null` when the search engine shows none.                                                             |
| `results[].displayedUrl`       | Breadcrumb string, when available.                                                                                    |
| `results[].siteName`           | Publisher name, when available.                                                                                       |
| `results[].date`               | Publication date (`YYYY-MM-DD`), when available.                                                                      |
| `results[].emphasizedKeywords` | Bolded terms inside the snippet, omitted when there are none.                                                         |
| `results[].siteLinks`          | Sub-page links attached to the result, omitted when there are none.                                                   |
| `search.provider`              | Search engine used.                                                                                                   |
| `search.providerDomain`        | The engine domain queried, like `google.de` for `countryCode: "DE"`. Always `duckduckgo.com` for DuckDuckGo.          |
| `search.totalResults`          | The engine's estimate of all matching pages, not the number returned. `null` if not shown, and always for DuckDuckGo. |
| `search.correctedQuery`        | The query the search engine silently searched instead. Always `null` for DuckDuckGo.                                  |
| `search.suggestedQuery`        | The engine's "did you mean" suggestion, not applied to the search. Always `null` for DuckDuckGo.                      |
| `search.checkUrl`              | Link to the query as sent to the engine. Results may differ: engines personalize by IP, account, and time.            |

### Search via MCP

Web Search runs a [Model Context Protocol](https://modelcontextprotocol.io) server at `/mcp`.
Its only tool, `search`, accepts the same inputs as the REST API. It returns the search JSON in an MCP text content block
and marks search failures with `isError`.

For Claude Code, add the HTTP endpoint with your Apify API token:

```bash
claude mcp add web-search https://web-search.apify.actor/mcp -t http -H 'Authorization: Bearer ***'
```

Replace `***` with your Apify API token. For another MCP client, like Cursor, use the same endpoint
and an `Authorization: Bearer <token>` header.

Or open the [Apify MCP server setup page](https://mcp.apify.com/?tools=actors,docs,apify/rag-web-browser,apify/web-fetch,apify/web-search) with Web Search selected.
Copy the settings for your client.

### Troubleshooting

#### Common errors

For input validation errors, find the field named in `error` under [Input](#input).

| Error code                   | When it happens                                                                                                  | What to do                                                                           |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| `MISSING_QUERY`, `INVALID_*` | A field is missing or invalid, the JSON body is malformed, or `location` was sent with `provider: "duckduckgo"`. | Fix what `error` describes. For DuckDuckGo, remove `location` and use `countryCode`. |
| `BLOCKED_BY_SEARCH_ENGINE`   | The search engine served a consent or CAPTCHA page, or refused the request.                                      | Retry the request, or switch `provider`.                                             |
| `SEARCH_TIMEOUT`             | The search exceeded its 60-second budget.                                                                        | Retry the request.                                                                   |
| `UPSTREAM_SEARCH_ERROR`      | The search backend couldn't be reached or returned an unexpected status.                                         | Retry the request, or switch `provider`.                                             |
| `PARSE_ERROR`                | Web Search couldn't recognize the search results page, which can happen when its markup changes.                 | This is a bug on our side. Report it on the **Issues** tab.                          |
| `INTERNAL_ERROR`             | The request failed unexpectedly.                                                                                 | Retry the request. If it keeps failing, report it on the **Issues** tab.             |

#### Need support or a custom feature?

If a retry doesn't help, open an issue on the Actor's **Issues** tab. Include your input and the error,
or [chat with Apify support](https://apify.com/contact).
For different fields, another search engine, or a different result shape,
[ask Apify for a custom solution](https://apify.com/custom-solutions).

### FAQ

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

No. Only organic results are returned.

#### Are `site:` and `filetype:` operators supported?

Yes, but Web Search passes them to the search engine unchecked.
Use `allowedDomains` and `blockedDomains` for enforced domain filtering.

#### Why do Web Search and browser results differ?

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

#### 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/).

# Actor input Schema

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

Operators like site: pass through unchecked. Use allowedDomains and blockedDomains to enforce domain filters, and dateFrom and dateTo for dates.

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

Google is the default search engine, supports location, and returns the most data. DuckDuckGo has no location, covers fewer countries, and fills fewer fields. Use it for a second, independent ranking, or when Google finds nothing. Each search is charged separately.

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

Maximum number of organic results to return. Leave it empty to get every result on the page. You get fewer when the page has fewer, or when domain filters remove some.

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

ISO 639-1 code with an optional region suffix, like "en", "de", or "pt-BR". See https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes. Google uses it to localize the interface and bias ranking, without translating or strictly filtering results. DuckDuckGo picks the matching regional variant, or the country's default.

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

ISO 3166-1 alpha-2 code, like "US", "DE", or "CZ". The search runs as if you were in that country, changing result order and which local businesses appear. Google queries that country's domain (google.de for "DE"). DuckDuckGo picks the matching region, preferring the languageCode variant ("CA" with "fr") over the default. Results aren't limited to sites hosted or registered there. Web Search returns INVALID_COUNTRY if the provider has no market there.

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

A place name, most specific first, like "Paris, Ile-de-France, France". Google treats it as your location for local and "near me" results. It doesn't filter pages. It's best-effort and not validated: Google may ignore places it can't resolve. Using it with DuckDuckGo returns INVALID_PARAMETER.

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

The earliest publication date to include. Best-effort. See the README.

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

The latest publication date to include. Best-effort. See the README.

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

Return only results from these domains. You can list up to 10 domains. A domain matches itself and its subdomains, so "wikipedia.org" covers "en.wikipedia.org". A top-level domain like "gov" matches every domain under it. Examples: "wikipedia.org", "docs.python.org", "gov".

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

Exclude results from these domains. You can list up to 10 domains. Matching works like allowedDomains, including subdomains and top-level domains. 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-securitybyobscurity.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api-securitybyobscurity.apify.com/v2/actors/75mun7kovVrjpLczB/builds/5gHaJj8KPj0dYhWD1/openapi.json
