# Keyword Search Volume & Ideas: Google Ads Data, Pay per Keyword (`stubborn_otter/keyword-search-volume`) Actor

Google Ads search volume, CPC, competition and 12-month trend for up to 10,000 keywords, plus keyword ideas from seeds or a URL, for any country and language. $0.002 per keyword with data + $0.12 per 1,000-keyword batch; empty results are free.

- **URL**: https://apify.com/stubborn_otter/keyword-search-volume.md
- **Developed by:** [Stubborn Otter Software](https://apify.com/stubborn_otter) (community)
- **Categories:** SEO tools, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 keyword with data

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## Keyword Search Volume & Ideas: Google Ads Data, Pay per Keyword

Get **Google Ads Keyword Planner metrics** for up to 10,000 keywords per run, for any country and language: average monthly searches, CPC, competition (LOW / MEDIUM / HIGH and 0–100), top-of-page bid range, the 12-month trend and the month-by-month breakdown. Or expand a few seed keywords, or a website, into related **keyword ideas** with the same metrics.

You don't need a Google Ads account, a developer token or OAuth. **You pay only for keywords that come back with data.**

### What it costs

| Event | Price | When |
|---|---|---|
| Keyword with data | **$0.002** | each keyword that comes back with a search volume |
| Batch | **$0.12** | each batch of up to 1,000 keywords sent to the data provider, and only when the batch returns at least one keyword with data |
| Actor start | $0.0003 | once per run |

Worked examples (metrics mode, every keyword has data):

| Keywords | Batches | Total |
|---|---|---|
| 5 | 1 | **$0.13** |
| 50 | 1 | **$0.22** |
| 1,000 | 1 | **$2.12** |
| 2,500 | 3 | **$5.36** |

**Free of charge:**

- keywords Google reports no volume for;
- keywords rejected before sending (too long, or containing emoji);
- duplicates;
- batches that failed;
- keywords skipped because of your cost limit.

**Rerunning a list:** a keyword you checked in the last 7 days comes from your cache. It costs $0.002 and needs no batch fee.

**Your cost limit:** set a **maximum cost per run** and the Actor plans the run to fit it before sending anything. With a $0.25 limit, for example, it checks the first 64 keywords and lists the rest as `skipped_budget`. Below about $0.13 nothing fits, because one batch plus one keyword costs $0.1223.

**With your own DataForSEO login,** the batch fee disappears: DataForSEO bills you directly, about $0.09 per batch. You pay this Actor only $0.002 per keyword with data.

### Modes

- **Metrics for my keywords** (`metrics`): exactly the keywords you list, in your order. Up to 10,000 per run, sent in batches of 1,000.
- **Keyword ideas from seed keywords** (`ideas`): up to 20 seeds, expanded into related keywords. *Max ideas* (default 100) and *Sort ideas by* control which ideas you keep, and you pay only for those.
- **Keyword ideas from a website or page** (`ideasFromUrl`): the keywords Google Ads associates with a domain (`site`) or with one URL (`page`).

### Input example

```json
{
  "mode": "metrics",
  "keywords": ["web scraping", "google sheets api", "keyword research tool"],
  "location": "United States",
  "language": "English"
}
```

**Location** accepts any of these, and a typo lists the closest matches:

- a country name or ISO code (`Germany`, `DE`, `DEU`);
- a DataForSEO location code for a region or city (`1023191` = New York);
- `worldwide`.

**Language** accepts a name or code (`German`, `de`), or `any`.

### Output

One row per keyword (table view *Keywords*; full JSON in the dataset):

```json
{
  "keyword": "web scraping", "status": "ok", "search_volume": 33100, "cpc": 3.12,
  "competition": "LOW", "competition_index": 24, "low_top_of_page_bid": 1.04, "high_top_of_page_bid": 5.9,
  "trend_12m_pct": 18.4,
  "monthly_searches": [{"year": 2026, "month": 9, "search_volume": 40500}, "… 12 months, newest first"],
  "spell": null, "location": "United States", "location_code": 2840, "language": "English",
  "language_code": "en", "search_partners": false, "mode": "metrics", "seed": null,
  "source": "dataforseo_google_ads", "cached_at": null, "charged": true, "reason": null
}
```

- `status`:
  - `ok`: has data, charged.
  - `no_data`: Google reports no volume; free.
  - `rejected`: not sent, with the reason; free.
  - `skipped_budget`: over your cost limit; free.
  - `skipped_cap`: over the run's batch limit; free.
  - `failed`: the data provider failed after retries, with the reason; free.
- `trend_12m_pct`: the change from the average of the oldest 3 months to the average of the newest 3 months in the 12-month window.
- `spell`: Google's spelling correction, when it measured a corrected form of your keyword.
- `cached_at`: set when the row came from your 7-day cache.
- `reason`: says why a row has no data or wasn't charged.

The run summary is in the `OUTPUT` record of the run's key-value store:

- keywords in, valid, rejected (with reasons), and duplicates removed;
- rows with data, and rows from cache;
- batches sent, and charged events;
- the location and language as resolved.

To also add the summary as the last dataset row, turn on *Add a summary row*.

### Keyword rules

Google Ads accepts at most **80 characters** and **10 words** per keyword, with no emoji and no symbols such as `! @ % * ? , | ; ~ < > = ( ) { } [ ]`. The Actor checks every keyword before sending. A keyword that breaks a rule gets a `rejected` row with the reason, and the rest of the run continues. Case and extra spaces don't matter: `SEO  Tools` and `seo tools` count as one keyword. Match-type wrappers (`"…"` and `[…]`) are removed.

### Data source and freshness

- **Source:** Google Ads keyword data from the [DataForSEO](https://dataforseo.com) Keywords Data API (Google Ads endpoints). This is the same data Google Ads Keyword Planner shows.
- **Freshness:** Google publishes search volumes once a month, around mid-month. Each row covers the latest 12 months Google has published; the current month is never included. A 7-day cache therefore doesn't make results stale.
- **How Google reports volumes:**
  - Volumes are averages, and Google rounds them into buckets (10, 20, 30, 40, 50, 70, 90, 110, 140, 170, 210, 260, 320, 390, 480, 590, 720, 880, 1,000, 1,300, …).
  - Google combines close variants into one figure, so `web scraping` and `webscraping` may show the same number.
  - Google gives no data for very rare keywords, or for restricted topics (weapons, tobacco, drugs and similar). Those rows are `no_data` and free.

### Free Apify plans

Free Apify plans need their own DataForSEO API login and password, entered under *Your own DataForSEO account*. New DataForSEO accounts get a small trial credit. On free plans each batch is paid by the user's own DataForSEO balance; without your own login, the run stops before anything is sent. Paid Apify plans can use either. The example input (the three prefilled keywords) runs on any plan, so you can see the output first.

### Errors are plain English

Every DataForSEO status code is translated into one sentence that says what to do. For example:

- "The DataForSEO balance is too low for this request. Top up at app.dataforseo.com."
- "Contains '🙂', which Google Ads doesn't accept in keywords."

Temporary errors (timeouts, rate limits, server errors) are retried up to 4 times. If a whole run returns no data at all, it **fails** with the reason, instead of succeeding with an empty table, and nothing is charged.

### Limits

| | |
|---|---|
| Keywords per run (metrics) | 10,000 (10 batches) |
| Seed keywords (ideas) | 20 |
| Ideas kept per run | 1–10,000 (*Max ideas*) |
| Speed | about 6–10 seconds per batch. The data provider allows 12 requests per minute per account, so a run with 10 batches takes 1–2 minutes |
| Memory | 256 MB is enough for 10,000 keywords |

### Use it from code, Make, n8n or an AI agent

Call it through the Apify API like any Actor, for example `POST https://api.apify.com/v2/acts/stubborn_otter~keyword-search-volume/run-sync-get-dataset-items?token=…` with the input above. Through Apify's MCP server, AI agents can call it as a tool ("get search volumes for these keywords").

### Privacy

- **Where your keywords go:** only to DataForSEO, which fetches the Google Ads data.
- **The cache:** it is a key-value store named `keyword-metrics-cache` in **your own** Apify account, so other users never see your keywords or results. Delete that store to clear it.
- **Your DataForSEO login:** it is an encrypted secret input. It is used only for that run's requests.

***

Made by Stubborn Otter Software. Questions or a keyword that looks wrong? Open an issue on the Actor's *Issues* tab, with the run link.

# Actor input Schema

## `mode` (type: `string`):

Metrics: search volume, CPC, competition and 12-month trend for exactly the keywords you list. Ideas: related keywords (with the same metrics) for up to 20 seed keywords. Ideas from a URL: keywords Google Ads associates with a site or page.

## `keywords` (type: `array`):

One keyword per line. Metrics mode: up to 10,000 keywords (duplicates are removed; case doesn't matter). Ideas mode: up to 20 seed keywords. Each keyword: at most 80 characters and 10 words, no emoji or symbols such as ! @ % \* ? , | — rejected keywords are listed with the reason and never charged. Pricing: $0.002 per keyword that has Google data, plus $0.12 per batch of up to 1,000 keywords sent to DataForSEO. Keywords Google reports no data for are free. 5 keywords ≈ $0.13, 50 ≈ $0.22, 1,000 ≈ $2.12.

## `targetUrl` (type: `string`):

Only for 'Ideas from a URL': a domain such as example.com, or a page URL such as https://example.com/blog/post.

## `targetType` (type: `string`):

Only for 'Ideas from a URL': 'site' finds keywords for the whole domain; 'page' for that one URL.

## `maxIdeas` (type: `integer`):

Ideas modes: keep at most this many ideas (1–10,000), chosen by 'Sort ideas by'. You're charged only for kept ideas with data, so 100 ideas cost at most $0.12 + $0.20.

## `sortBy` (type: `string`):

Ideas modes: which ideas to keep when cutting to 'Max ideas', and their order. Metrics mode always keeps your order.

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

Country name or ISO code (United States, US, Germany, DE, …), a DataForSEO location code for a region or city (e.g. 1023191 for New York), or 'worldwide'. A typo lists the closest matches.

## `language` (type: `string`):

Language name or code (English, en, German, de, …), or 'any' for all languages.

## `searchPartners` (type: `boolean`):

Off: Google Search only (what Keyword Planner shows by default). On: Google Search plus search partner sites.

## `includeAdultKeywords` (type: `boolean`):

Ideas modes: also return adult keyword ideas. Off by default, as in Keyword Planner.

## `useCache` (type: `boolean`):

Metrics mode: keywords you checked in the last 7 days (same location, language and network) come from a cache in your own Apify account, with 'cached_at' set. They cost $0.002 each and need no batch fee. Google publishes volumes monthly, so a week-old result is current. Off: always ask DataForSEO.

## `includeSummaryRow` (type: `boolean`):

Also push the run summary as the last dataset item (type 'summary'). Off by default so the dataset stays a clean table. The summary is always in the OUTPUT record.

## `dataforseoLogin` (type: `string`):

The API login from app.dataforseo.com/api-access (usually your email address).

## `dataforseoPassword` (type: `string`):

The API password from app.dataforseo.com/api-access. It is generated by DataForSEO and differs from your account password.

## `maxVendorSpendUsd` (type: `number`):

Only with your own login: stop sending batches once this run has spent this much of your DataForSEO balance (one batch is about $0.09). Keywords beyond it are listed as skipped and not charged.

## Actor input object example

```json
{
  "mode": "metrics",
  "keywords": [
    "web scraping",
    "google sheets api",
    "keyword research tool"
  ],
  "targetType": "site",
  "maxIdeas": 100,
  "sortBy": "relevance",
  "location": "United States",
  "language": "English",
  "searchPartners": false,
  "includeAdultKeywords": false,
  "useCache": true,
  "includeSummaryRow": false,
  "maxVendorSpendUsd": 1
}
```

# Actor output Schema

## `keywords` (type: `string`):

One row per keyword: search volume, CPC, competition, top-of-page bids, 12-month trend, monthly searches, status and whether it was charged.

## `summary` (type: `string`):

Keywords in, valid, rejected (with reasons), duplicates removed, rows with data, rows from cache, vendor batches, charged events and the resolved location and language.

# 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 = {
    "keywords": [
        "web scraping",
        "google sheets api",
        "keyword research tool"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("stubborn_otter/keyword-search-volume").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 = { "keywords": [
        "web scraping",
        "google sheets api",
        "keyword research tool",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("stubborn_otter/keyword-search-volume").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 '{
  "keywords": [
    "web scraping",
    "google sheets api",
    "keyword research tool"
  ]
}' |
apify call stubborn_otter/keyword-search-volume --silent --output-dataset

```

## MCP server setup

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

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/UOcGF03eQVY2M6if3/builds/9nDXGYIA6E1an9K4X/openapi.json
