# Search Volume: Google Keyword Planner & Keyword Volume Data (`alexmorain/keyword-volume-scraper`) Actor

Google Ads search volume, CPC, competition and 12 months of searches for up to 100,000 keywords per run, per country. Close variants get their own figures. Optional difficulty and intent, keyword ideas, monthly watch, webhook. From $1.02 per 1,000 keywords.

- **URL**: https://apify.com/alexmorain/keyword-volume-scraper.md
- **Developed by:** [Alexis Morain](https://apify.com/alexmorain) (community)
- **Categories:** SEO tools, Marketing
- **Features:** Limited permissions
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 keyword with volumes

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

### What does Keyword Volume Scraper do?

**Keyword Volume Scraper** returns the **Google search volume, CPC, top-of-page bids, paid competition and 12 months of searches** for up to **100,000 keywords per run**, per country and language. It reads the same Google Ads data as the **Google Keyword Planner**, through the [DataForSEO](https://dataforseo.com) Google Ads API, without a Google Ads account, an API key or a minimum ad spend. Optionally it adds the **keyword difficulty and search intent**, turns seed keywords into **keyword ideas**, and **watches volume changes** from one month to the next.

It does not scrape Google result pages and it does not estimate traffic. It gives you the numbers Google gives advertisers, in a dataset you can download as JSON, CSV or Excel, or call by API.

> **Read this first.** Google Keyword Planner merges the figures of keywords it considers the same search. Send "sale agreement format" and "format of sale agreement" in one request and only the first one comes back with data. This Actor sends such keywords in separate requests, so each one gets its own row. The next section has the measured numbers. This Actor is an independent project and is **not affiliated with, endorsed by or sponsored by Google, Ahrefs or Semrush**. Google Keyword Planner is named only to say what kind of data this is.

### Why use Keyword Volume Scraper?

- **A list does not break on one bad keyword.** One keyword with a symbol Google Ads refuses (`!`, `?`, `,`, `%`, an emoji) makes the **whole request fail**, so a list of 1,000 keywords returns nothing. We measured it on 9 November 2026: three keywords, one of them `crm software!`, came back as `40501 Invalid Field: 'keywords'. Keyword text has invalid characters or symbols`, zero rows. The Actor removes those symbols before sending, flags the row with `keywordCleaned: true`, and keeps the original text in `input`.
- **Close variants get their own figures.** Google Ads groups near-identical keywords and returns data for the first of the group only. Measured on 9 November 2026, same four keywords, United States, English:

| Keyword | All four in one request | Each one sent alone |
|---|---|---|
| sale agreement format | 880 | 880 |
| format of sale agreement | no data | 880 |
| agreement for sale format | no data | 10 |
| sale agreement formats | no data | no data (Google gives none, the row says so) |

One request returned 1 keyword out of 4. Sent separately, 3 out of 4. The Actor groups keywords that Google merges (word order, plural, small words like "of" and "for", accents) and never puts two of them in the same request. A keyword Google still merges is sent again alone, up to 4 rounds.

- **The country is in every row, and worldwide is not the default.** Without a location the API returns the figure for **all countries merged**: `crm software` is **450,000** worldwide and **201,000** in the United States, `sale agreement format` is **6,600** worldwide and **880** in the United States. The default here is `us`, and `worldwide` is an explicit choice.
- **Cheap in bulk.** From **$1.02 per 1,000 keywords** ($0.62 on the Gold plan), difficulty and ideas billed separately and only when you ask for them. Details in the cost section.
- **Rows that cost nothing are free.** A keyword rejected before sending, a keyword Google has no figure for, a refused request: delivered as a row with a `status` and a `note`, **not charged**.
- **A monthly watch.** Google updates the volumes once a month. Schedule the Actor monthly, turn on **Monitor volume changes** and get `previousVolume`, `volumeChange` and `volumeChangePercent`, or only the keywords that moved.
- **On the Apify platform:** schedule it, call it by API, send results to Google Sheets, Make, Zapier, n8n, or a **webhook** at the end of the run.

Use it to prioritise a keyword list before writing content, size a market in several countries, price a Google Ads campaign with real CPCs, feed an SEO dashboard, or alert a client when the demand for their keywords moves.

### What data can Keyword Volume Scraper extract?

Two row types, told apart by `rowType`. Every column is present in every row of a run, `null` when it does not apply.

#### Keyword rows (`rowType: keyword`)

| Field | Example | Description |
|---|---|---|
| `input` | `CRM Software!` | The keyword exactly as you gave it |
| `keyword` | `crm software` | What was sent to Google Ads: lowercased, cleaned, unique |
| `keywordCleaned` | `true` | Symbols Google Ads refuses were removed from the input |
| `status` | `ok` | `ok`, `no_data`, `invalid_input`, `error` or `close_variant`. Only `ok` rows are charged |
| `country`, `language` | `us`, `en` | Country and language of the figures. `worldwide` and `null` when you chose worldwide |
| `searchVolume` | `201000` | Average monthly searches on Google in that country |
| `cpc` | `37.13` | Average cost per click in USD |
| `lowTopOfPageBid`, `highTopOfPageBid` | `7.14`, `51.57` | Range of the bid to show an ad at the top of the page, USD |
| `competition`, `competitionIndex` | `LOW`, `4` | Paid competition level and index 0-100 (ads, not organic results) |
| `monthlySearches` | `[{"month": "2025-10", "searchVolume": 165000}, ...]` | The last 12 months, oldest first |
| `keywordDifficulty`, `searchIntent`, `secondaryIntents` | `58`, `commercial`, `["informational"]` | Only with **Add difficulty and intent** |
| `isNew`, `previousVolume`, `previousCheckedAt`, `volumeChange`, `volumeChangePercent` | `false`, `3600`, `2026-11-09`, `0`, `0.0` | Only with **Monitor volume changes** |
| `note`, `groupedWith` | | Why a row has no figure, and for `close_variant` rows the keyword whose figures Google gives to the group |
| `scrapedAt` | `2026-11-09T20:56:27Z` | When the figures were read |

#### Keyword idea rows (`rowType: idea`, mode Ideas)

`seed`, `keyword`, `searchVolume`, `cpc`, `lowTopOfPageBid`, `highTopOfPageBid`, `competition`, `competitionIndex`, `monthlySearches`, `trendMonthly`, `trendQuarterly`, `trendYearly` (volume change in percent against the previous month, quarter and year), `keywordDifficulty`, `searchIntent`, `secondaryIntents`, `country`, `language`.

#### What the statuses mean

- **`ok`**: Google Ads gave a volume. Charged.
- **`no_data`**: Google Ads gave nothing. Very low volume, a restricted category (adult, weapons, some finance and political terms) or a variant Google merged. Not charged.
- **`invalid_input`**: not sent, because nothing was left after cleaning, or the keyword has more than 80 characters or 10 words. Not charged.
- **`error`**: the data provider refused the request. Not charged.
- **`close_variant`**: Google merges the keyword with another one, even when it is sent alone. `groupedWith` names it. Not charged.

### How to get the search volume of a keyword list

1. Click **Try for free**.
2. Paste your keywords into **Keywords**, one per line.
3. Pick the **Country** you sell to. Leave the language empty to use the main language of the country, or write a code such as `fr` or `es`.
4. Optional: turn on **Add difficulty and intent**, or switch the mode to **Keyword ideas from seed keywords**.
5. Click **Start**. A list of 1,000 keywords takes about 15 seconds. 100,000 keywords take about 10 minutes, because Google Ads allows 12 requests a minute on this account.
6. Open the **Output** tab, pick the **Keywords** view, and download the dataset as JSON, CSV, Excel, XML or HTML.

### How much will it cost to get keyword volumes?

You pay for events, and every cost is visible before you start.

| Event | Price (Free and Bronze) | Gold | What it is |
|---|---|---|---|
| Actor start | $0.002 per GB | same | Once per run |
| Google Ads request | $0.12 | $0.12 | Per request of up to 1,000 keywords |
| Keyword with volume | $0.0009 | $0.0005 | Per `ok` row |
| Difficulty request | $0.02 | $0.02 | Per request of up to 700 keywords, only with difficulty on |
| Keyword difficulty and intent | $0.0004 | $0.0002 | Per keyword the provider returns data for |
| Keyword ideas request | $0.03 | $0.03 | Per page of up to 1,000 ideas of one seed |
| Keyword idea | $0.001 | $0.0005 | Per idea row |

Silver sits between Bronze and Gold: $0.0007 per keyword, $0.0003 for difficulty, $0.00075 per idea.

| Run | Cost on Free and Bronze | Cost on Gold |
|---|---|---|
| 100 keywords | $0.21 | $0.17 |
| 1,000 keywords, no close variants | $1.02 | $0.62 |
| 1,000 keywords with difficulty and intent | $1.46 | $0.86 |
| 10,000 keywords | $10.20 | $6.20 |
| 1,000 ideas for one seed | $1.03 | $0.53 |

A very small run costs more per keyword, because the Google Ads request is billed whole: one keyword is $0.12. Put your keywords in one run.

**Close variants add requests.** The Actor sends a group of near-identical keywords in as many requests as the group has members, so that each gets its own figures. A list with no variants takes one request per 1,000 keywords. A list of related long-tail keywords, with many reorderings and plurals, can take two to five requests per 1,000. The `SUMMARY` record of the run shows `googleAdsCalls` and `variantRounds`. If you prefer one request and accept merged figures, remove the variants from the list before the run.

**Set a maximum cost per run** on the Apify start screen. The Actor reads it before every request, shrinks the last request to what fits, delivers what it paid for and stops with a clear message.

### Input

See the **Input** tab for every option. The ones worth knowing:

- **Keywords**: up to 100,000, one per line. In ideas mode they are seeds, up to 200.
- **Country**: 86 countries, plus `worldwide` (all countries merged into one figure).
- **Language**: a two-letter code. Empty uses the main language of the country.
- **Add difficulty and intent**: needs a country. Ideas mode always includes both.
- **Ideas per seed keyword** and **Minimum volume of an idea**: ideas mode. The minimum volume is a filter at the source, so skipped ideas are not read and not charged.
- **Monitor volume changes** and **Only keywords that changed**: the monthly watch.
- **Webhook URL**: the run summary is sent there by POST at the end of the run.

Example input:

```json
{
  "keywords": ["crm software", "best crm for small business", "free crm"],
  "country": "us",
  "includeDifficulty": true
}
```

### Output

You can download the dataset in various formats such as JSON, HTML, CSV or Excel. Example, a volume run for France with difficulty on (real run, 9 November 2026):

```json
[
  {
    "rowType": "keyword",
    "status": "ok",
    "input": "logiciel crm",
    "keyword": "logiciel crm",
    "keywordCleaned": false,
    "country": "fr",
    "language": "fr",
    "searchVolume": 3600,
    "cpc": 16.3,
    "lowTopOfPageBid": 2.38,
    "highTopOfPageBid": 18.32,
    "competition": "MEDIUM",
    "competitionIndex": 56,
    "monthlySearches": [{"month": "2025-10", "searchVolume": 3600}, "...", {"month": "2026-09", "searchVolume": 2400}],
    "keywordDifficulty": 40,
    "searchIntent": "commercial",
    "note": null,
    "scrapedAt": "2026-11-09T20:56:27Z"
  },
  {
    "rowType": "keyword",
    "status": "invalid_input",
    "input": "x x x x x x x x x x x",
    "keyword": null,
    "searchVolume": null,
    "note": "Not sent: more than 10 words."
  }
]
```

A keyword idea row:

```json
{
  "rowType": "idea",
  "seed": "crm software",
  "keyword": "crm system",
  "country": "us",
  "searchVolume": 33100,
  "cpc": 36.54,
  "competition": "MEDIUM",
  "keywordDifficulty": 56,
  "searchIntent": "commercial",
  "trendMonthly": 0,
  "trendYearly": 0
}
```

The run also writes a `SUMMARY` record: keywords received, rejected, without data, requests sent, rounds for close variants, rows charged by event, stop reason.

### Run it from code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("alexmorain/keyword-volume-scraper").call(run_input={
    "keywords": ["crm software", "best crm", "free crm"],
    "country": "us",
})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(row["keyword"], row["searchVolume"], row["cpc"])
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('alexmorain/keyword-volume-scraper').call({
  keywords: ['crm software', 'best crm', 'free crm'],
  country: 'us',
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((r) => [r.keyword, r.searchVolume]));
```

cURL:

```bash
curl -X POST "https://api.apify.com/v2/acts/alexmorain~keyword-volume-scraper/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"keywords": ["crm software", "best crm", "free crm"], "country": "us"}'
```

### Tips and advanced options

- **Pick the country you sell to.** A global figure hides where the demand is. Run the same list for `us`, `gb` and `de` and compare.
- **Schedule it monthly.** Google updates once a month, so a second run in the same month shows no change. With **Monitor volume changes** on, the Actor keeps the last volume of every keyword per country and language in a named key-value store of your account (`keyword-volume-monitor`), and **Only keywords that changed** returns just the ones that moved. Unchanged keywords are neither returned nor charged. A request to Google is still made for them: the watch saves rows, not requests.
- **Read `monthlySearches` for seasonality.** The average volume hides a keyword that peaks in December.
- **Use ideas mode to grow a list**, then run the keywords you keep in volume mode if you want Google Ads figures for them. Idea rows come from the DataForSEO keyword database, which is derived from Google Ads data, so the volume of an idea can differ a little from the volume of the same keyword in volume mode.
- **Resume after a migration.** If the platform moves your run to another server, the Actor continues where it stopped and does not charge twice for a row already delivered.

### FAQ, disclaimers and support

#### Is the search volume exact?

It is the number Google gives advertisers, already rounded by Google (you will see 590, 720, 880, 1,000, 1,300 and never 874). Treat it as an order of magnitude, good for ranking keywords against each other.

#### Why does a keyword come back without data?

Google Ads returns nothing for very low-volume terms, for restricted categories (adult content, weapons and drugs, some financial products, some political terms, some terms about children) and for a keyword it merges with a close variant. The row has `status: no_data` or `close_variant` and a `note`. It is not charged.

#### Why does a worldwide figure differ so much from a country figure?

Worldwide merges every country and every language into one number. `crm software` is 450,000 worldwide and 201,000 in the United States. Pick a country.

#### Is the keyword difficulty the same as in Ahrefs or Semrush?

No. The difficulty (0 to 100) and the search intent come from the DataForSEO keyword database. They are computed with their own method, so they will not match the difficulty shown by other tools number for number. Use them to compare keywords of the same list.

#### Is the CPC what I would pay?

It is the historical average cost per click paid by advertisers for the keyword in that country. Your own bid, quality score and competition will differ. The top-of-page bid range is a better guide for a new campaign.

#### Why not the cheaper queued mode of the data provider?

Queued requests cost $0.06 instead of $0.09 per request of 1,000 keywords and take minutes. The Actor uses live requests: a result in seconds is worth more than three cents to most users.

#### Can I use it as a Google Keyword Planner API?

Yes, as a data source. Call the Actor by API (see above) with the keywords and read the dataset, or run it synchronously with `run-sync-get-dataset-items`. It is not the Google Ads API itself and it does not need a Google Ads account.

#### Is it legal?

Our Actors are ethical and do not extract any private user data, such as email addresses, gender, or location. This Actor reads keyword statistics, not personal data. The data comes from the DataForSEO API, resold under an agreement with DataForSEO. If you are unsure about your use case, consult your lawyers.

#### Support

Something wrong, missing or surprising? Open an issue on the **Issues** tab of the Actor page, with the run link. We answer there. The **API** tab has everything you need for programmatic access.

# Actor input Schema

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

One keyword per line, up to 100,000 per run (up to 200 seeds in ideas mode). Keywords are lowercased and cleaned before they are sent: symbols Google Ads refuses (<code>! ? , % @ ( ) =</code> and emojis) are removed instead of failing the batch, duplicates are removed, and a keyword over 80 characters or 10 words comes back as an <code>invalid_input</code> row, not charged.

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

<b>Volume</b>: read the keywords you give. <b>Ideas</b>: treat them as seeds and return related keywords with their volume, CPC, difficulty and intent.

## `country` (type: `string`):

Volumes are per country. <b>Worldwide</b> merges every country into one figure (for example 'crm software' is 450,000 worldwide and 201,000 in the United States), so pick the market you sell to. The choice is written in every row.

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

Optional two-letter language code such as <code>fr</code> or <code>es</code>. Empty uses the main language of the country (<code>en</code> for the United States). Not used with Worldwide.

## `includeDifficulty` (type: `boolean`):

Adds the keyword difficulty (0-100) and the search intent (informational, commercial, transactional, navigational) to each volume row. Needs a country. Charged separately, see the pricing section. Ideas mode always includes both.

## `maxIdeasPerSeed` (type: `integer`):

Ideas mode only. Up to 10,000 ideas per seed, read in pages of 1,000.

## `ideasMinVolume` (type: `integer`):

Ideas mode only. Skips ideas with a monthly search volume below this number. It filters at the source, so skipped ideas are not read and not charged.

## `monitor` (type: `boolean`):

Keeps the last volume of every keyword (per country and language) in a named key-value store of your account and adds <code>previousVolume</code>, <code>volumeChange</code> and <code>volumeChangePercent</code> to each row. Google Ads updates once a month: schedule the run monthly.

## `onlyChanged` (type: `boolean`):

With monitoring on, returns only the keywords that are new or whose volume changed since the last run. Unchanged keywords are not returned and not charged.

## `webhookUrl` (type: `string`):

Optional. The run summary is sent there by POST when the run ends (Slack, Discord, Zapier...). The URL is never written to a log.

## Actor input object example

```json
{
  "keywords": [
    "crm software",
    "project management software",
    "email marketing",
    "best crm for small business",
    "seo tools"
  ],
  "mode": "volume",
  "country": "us",
  "includeDifficulty": false,
  "maxIdeasPerSeed": 100,
  "ideasMinVolume": 10,
  "monitor": false,
  "onlyChanged": false
}
```

# Actor output Schema

## `results` (type: `string`):

All rows in the default dataset.

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

Keywords received, rejected and without data; Google Ads requests and variant rounds; rows and events charged; stop reason.

# 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": [
        "crm software",
        "project management software",
        "email marketing",
        "best crm for small business",
        "seo tools"
    ],
    "ideasMinVolume": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("alexmorain/keyword-volume-scraper").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": [
        "crm software",
        "project management software",
        "email marketing",
        "best crm for small business",
        "seo tools",
    ],
    "ideasMinVolume": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("alexmorain/keyword-volume-scraper").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": [
    "crm software",
    "project management software",
    "email marketing",
    "best crm for small business",
    "seo tools"
  ],
  "ideasMinVolume": 10
}' |
apify call alexmorain/keyword-volume-scraper --silent --output-dataset

```

## MCP server setup

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

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/adMLwaZhQqHTJyAfl/builds/enydXZnQKlotvVrqe/openapi.json
