# Keyword Search Volume, CPC & Difficulty Checker (`nice_dev/keyword-volume-scraper`) Actor

Google search volume, CPC, competition, keyword difficulty, search intent and monthly trend for up to 10,000 keywords per run, by country or city, plus keyword ideas from a seed. No account or API key. Export to JSON, CSV or Excel.

- **URL**: https://apify.com/nice_dev/keyword-volume-scraper.md
- **Developed by:** [Nice Dev](https://apify.com/nice_dev) (community)
- **Categories:** SEO tools, Marketing, MCP servers
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.76 / 1,000 keywords

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 is Keyword Search Volume, CPC & Difficulty Checker?

**Keyword Search Volume, CPC & Difficulty Checker** gives you **Google search volume data** for your keywords: **monthly searches, cost per click (CPC), ad competition, keyword difficulty, search intent, device share, click rate, searcher age and gender, and the volume in every other country**. It works by **country, by city or worldwide**, in any language, for up to 10,000 keywords per run (raise **Max keywords**, 1,000 by default). It is a **Google Keyword Planner API alternative**: no Google Ads account, no API key, no quota to request. Run it on a schedule, call it from your code, or plug it into Make, Zapier or n8n.

Paste your **keywords** (`running shoes`, `chaussures de running`), pick a **location** (`United States`, `France`, `Paris, France`), click **Start**, and download the results in JSON, CSV or Excel. Or switch to **Keyword ideas** mode: give a few seed keywords and get a few hundred **keyword ideas from real searches** around each seed, every idea with the same metrics.

It saves about **200 keywords in 30 seconds** (500 keyword ideas in about a minute), for **$1.79 per 1,000 keywords** (Free plan, less on paid plans).

### 📋 What data can you extract?

One row per keyword × location × language, 33 fields:

| Category | What you get |
| --- | --- |
| 🔑 **Keyword** | the keyword, the keyword as you typed it, its Google results link; in Ideas mode the seed it came from and the kind of idea — `laptop stand` |
| 📈 **Search volume** | average monthly searches — `301,000`; with **Full keyword data**: searches of the last months (usually 4), worldwide volume and the volume in each other country |
| 💵 **Advertising** | cost per click in the local currency and in US dollars, ad competition (0-1, index 0-100, LOW / MEDIUM / HIGH) — `$0.26`, `HIGH` |
| 🎯 **Keyword difficulty** (add-on) | SEO difficulty (how hard to reach the top 10) and paid difficulty (how crowded the ads are), 0-100 |
| 🧭 **Search intent** | informational, navigational, commercial and / or transactional |
| 📱 **Devices and clicks** | share of searches on mobile and on desktop, click rate, share of organic and of paid clicks — `68% desktop` |
| 👥 **Who searches** | share of each age group and of each gender — `62% male` |
| 🌍 **Place and language** | country or city, Google Ads location id, country code, language, date the figures were last updated |
| ✅ **Found or not** | a keyword the source returns no data for (`found: false`) is kept as a free row, so you see every keyword you sent; a keyword nobody searches (a made-up word, a typo) usually comes back with `0` searches, a real measure, and is a normal row |

Every field, with an example, is listed in the **Output** section below.

Volume, CPC and competition are always there for a keyword that has data. Intent, months, devices, clicks, age, gender and the other countries are filled when **Full keyword data** (`extractDetails`) is on; the two difficulty scores when **Keyword difficulty** (`includeDifficulty`) is on. Both are **paid options, off by default**.

### ✅ Why use Keyword Search Volume, CPC & Difficulty Checker?

- 🔓 **No account, no API key**: no Google Ads account with spend, nothing to set up.
- 📊 **More columns than a plain volume check**: difficulty, search intent, devices, click rate, age and gender, and the volume in every other country.
- 🏙️ **Countries, cities or worldwide**: `Paris, France` or `Springfield, Illinois, United States`, and the log tells which place was used.
- 💡 **Keyword ideas mode**: a few hundred ideas from real searches per seed, with question and comparison ideas (`how`, `best`, `vs`…) in English, French, German, Spanish, Italian, Portuguese and Dutch.
- 🧩 **Nothing missed, nothing twice**: one row per keyword you send (even without data), up to **Max keywords** (1,000 by default, `0` = no limit), duplicates looked up once.
- 🔔 **Monitoring built in**: tick **Only new keywords**, schedule the Actor, and each run returns (and charges) only the keywords it has never delivered before.
- 🔌 API, scheduling, monitoring, integrations (Make, Zapier, n8n, Google Sheets…) and JSON/CSV/Excel export via the Apify platform.

### 🚀 How to check keyword search volume

1. Create a free Apify account.
2. Open **Keyword Search Volume, CPC & Difficulty Checker** and paste your keywords into **Keywords**, one per line (e.g. `running shoes`, `standing desk`).
3. Pick a **Location**: a country (`France`), `worldwide`, or a city with its country (`Lyon, France`). Leave **Language** empty to use the main language of the place.
4. Choose the **Mode**: Keyword metrics (exactly your keywords) or Keyword ideas (expand each keyword into ideas). Set **Max keywords** (1000 by default, 0 = no limit), then click **Start**.
5. Download the dataset in JSON, CSV, Excel or via API.

### 💰 How much does it cost to check keyword volume?

This Actor uses **pay per event** pricing (the same amounts are in the **Pricing** tab): a price per keyword row, and a price per option you tick. Platform usage (compute, proxy) is included in the price.

Prices are per 1,000 events, by Apify plan (a higher plan pays less).

| What you pay for | Event | Free | Bronze | Silver | Gold |
| --- | --- | --- | --- | --- | --- |
| Keyword row: volume, CPC, competition | `result` | $1.79 | $1.78 | $1.77 | $1.76 |
| Keyword row with **Full keyword data**: months, intent, devices, clicks, age, gender, other countries. Paid option, off by default; replaces the plain row | `result-with-details` | $2.69 | $2.68 | $2.67 | $2.66 |
| **Keyword difficulty** add-on, on top of the row. Paid option, off by default | `keyword-difficulty` | $0.92 | $0.91 | $0.90 | $0.89 |
| Keyword dropped by a filter of this Actor (a keyword you keep pays its normal price instead) | `filter-check` | $0.12 | $0.11 | $0.10 | $0.09 |
| Same, when the keyword had to be looked up on its own first (Full keyword data, or `worldwide`) | `detail-filter-check` | $0.12 | $0.11 | $0.10 | $0.09 |
| Run start | `apify-actor-start` | $0.01 per run | $0.01 per run | $0.01 per run | $0.01 per run |
| Keyword the source returns no data for (`found: false`) | — | free | free | free | free |

A keyword nobody searches comes back as `0` searches and is a normal row. With `minVolume` or `minCpc`, a keyword without data has no figure to reach the minimum: it is dropped and costs the filter fee.

Examples (Free plan): 10,000 keywords with volume, CPC and competition ≈ $17.91 ($17.90 + $0.01 run start); 1,000 keywords with full data and difficulty ≈ $3.62 ($2.69 + $0.92 + $0.01). A keyword a filter drops costs only the filter fee, lower than a row: filtering never costs more than taking everything.

### ⚙️ Input

```json
{
    "query": "running shoes",
    "location": "United States",
    "maxItems": 100
}
```

French keywords in France and in one city, with full data and difficulty:

```json
{
    "searchQueries": ["chaussures de running", "tapis de yoga"],
    "locations": ["France", "Lyon, France"],
    "language": "fr",
    "extractDetails": true,
    "includeDifficulty": true
}
```

Keyword ideas around a seed, at most 200 per seed, only ideas with 100 searches a month or more, only the ones not delivered before:

```json
{
    "mode": "ideas",
    "searchQueries": ["standing desk"],
    "location": "United Kingdom",
    "maxItemsPerQuery": 200,
    "minVolume": 100,
    "onlyNew": true,
    "stateKey": "desk-ideas-uk"
}
```

| Field | Notes |
| --- | --- |
| `mode` | `metrics` (default): data for exactly the keywords you give. `ideas`: each keyword is a seed, and the Actor returns the related searches people type, the seed included. |
| `query`, `searchQueries` | Your keywords, one per line (`running shoes`, `chaussures de running`), up to 10,000 per run (the rows saved stop at `maxItems`, 1,000 by default). Duplicates (case ignored) are looked up once. In Ideas mode each one is a seed. |
| `location`, `locations` | A country (`France`, or its code `fr`), `worldwide`, a city with its country (`Paris, France`, `Springfield, Illinois, United States`) or a Google Ads location id (`1023191`). Empty = United States. Every keyword is looked up in every location; in Ideas mode each seed × location is one search (max 500 searches per run). |
| `language` | 2-letter code of the searches (`en`, `fr`, `de`, `ja`). Empty = the main language of each location. |
| `maxItems` | Rows to save for the whole run, after filters (`0` = no limit). |
| `maxItemsPerQuery` | Ideas mode: cap for EACH seed × location, its own row included. Metrics mode: cap for each location. `0` = no cap. |
| `extractDetails` | **Full keyword data** (paid option, off by default): months, intent, devices, clicks, age, gender, other countries. |
| `includeDifficulty` | **Keyword difficulty** add-on (paid option, off by default): SEO and paid difficulty, 0-100. |
| `includeQuestionIdeas` | Ideas mode: also expand each seed with question and comparison words (`how`, `what`, `why`, `best`, `vs`, `for`…), in the language of the search (English, French, German, Spanish, Italian, Portuguese, Dutch; other languages get the English words) (on by default). |
| `includeNotFound` | Keep a free row with `found: false` for each keyword the source returns no data for (on by default; a keyword with 0 searches is not one of them). Off: such keywords are counted as failed in the log. |
| `minVolume`, `maxVolume`, `minCpc`, `maxDifficulty` | Filters: monthly searches between two bounds, minimum CPC in US dollars, maximum SEO difficulty (works without the difficulty add-on). `0` = no bound. |
| `excludeKeywords` | Drop the keywords that contain one of these words (`free`, `amazon`), case and accents ignored. |
| `stateKey`, `onlyNew`, `resetState` | Monitoring: `onlyNew` returns only the keywords never delivered under this memory key (`shoes-ideas`); `resetState` forgets the memory. |
| Advanced | `proxyConfiguration` (Apify proxy by default, included in the price; the residential proxy is not available), `maxConcurrency`, `maxRequestsPerMinute`, `minRequestIntervalMs`, `maxRequestRetries`, `debugLog`. |

### 📦 Output

A real row (keyword `laptop stand`, worldwide, with full data and difficulty), with shortened decimals and `volumeByCountry` cut to 3 countries:

```json
{
    "id": "laptop stand|worldwide|en",
    "url": "https://www.google.com/search?q=laptop+stand&hl=en",
    "keyword": "laptop stand",
    "inputKeyword": "laptop stand",
    "seedKeyword": null,
    "ideaType": null,
    "found": true,
    "searchVolume": 301000,
    "cpc": 0.26,
    "cpcCurrency": "USD",
    "cpcUsd": 0.26,
    "competition": 1,
    "competitionIndex": 100,
    "competitionLevel": "HIGH",
    "seoDifficulty": null,
    "paidDifficulty": 100,
    "searchIntent": [],
    "monthlySearches": [
        { "year": 2026, "month": 6, "searchVolume": 368000 },
        { "year": 2026, "month": 7, "searchVolume": 368000 },
        { "year": 2026, "month": 8, "searchVolume": 450000 },
        { "year": 2026, "month": 9, "searchVolume": 301000 }
    ],
    "mobileShare": 0.3183,
    "desktopShare": 0.6817,
    "clickRate": 0.587,
    "organicClickShare": 0.3795,
    "paidClickShare": 0.2075,
    "searcherAge": { "under18": 0, "18to24": 0.1305, "25to34": 0.1329, "35to44": 0.2309, "45to54": 0.075, "55to64": 0.022, "over65": 0, "unknown": 0.4087 },
    "searcherGender": { "male": 0.6243, "female": 0.1783, "unknown": 0.1974 },
    "globalSearchVolume": 301000,
    "volumeByCountry": [
        { "countryCode": "IN", "locationId": 2356, "language": "hi", "searchVolume": 165000 },
        { "countryCode": "US", "locationId": 2840, "language": "es", "searchVolume": 40500 },
        { "countryCode": "GB", "locationId": 2826, "language": "en", "searchVolume": 18100 }
    ],
    "location": "Worldwide",
    "locationId": null,
    "countryCode": null,
    "language": "en",
    "dataUpdatedAt": "2026-04-12T07:31:27.000Z",
    "scrapedAt": "2026-10-15T09:00:00.000Z"
}
```

You can download the dataset in various formats such as JSON, HTML, CSV or Excel.

#### All 33 fields

| Fields | What you get |
| --- | --- |
| `id`, `url` | a stable key (keyword, location id or worldwide, language, joined by a vertical bar), the Google results page of the keyword |
| `keyword`, `inputKeyword` | the keyword as Google counts it (lower case), the keyword as you typed it |
| `seedKeyword`, `ideaType` | Ideas mode: the seed, and `seed`, `suggestion` or `question`; null in Metrics mode |
| `found` | `false` = no data for this keyword: every metric is null and the row is free |
| `searchVolume` | average monthly searches, `301000` |
| `cpc`, `cpcCurrency`, `cpcUsd` | cost per click in the local currency, that currency, and in US dollars |
| `competition`, `competitionIndex`, `competitionLevel` | `1`, `100`, `HIGH` (LOW < 34 ≤ MEDIUM < 67 ≤ HIGH) |
| `seoDifficulty`, `paidDifficulty` | add-on, 0-100 |
| `searchIntent` | full data: `informational`, `navigational`, `commercial`, `transactional` |
| `monthlySearches` | full data: year, month and searches of each recent month, oldest first |
| `mobileShare`, `desktopShare` | full data: share of searches on a phone / a computer, 0-1 |
| `clickRate`, `organicClickShare`, `paidClickShare` | full data: share of searches with a click, on an organic result, on an ad, 0-1 |
| `searcherAge`, `searcherGender` | full data: shares by age group and by gender, 0-1 |
| `globalSearchVolume`, `volumeByCountry` | full data: searches in all countries together, and in each country (largest first) |
| `location`, `locationId`, `countryCode`, `language` | `United States`, `2840`, `US`, `en` |
| `dataUpdatedAt`, `scrapedAt` | when the figures were last refreshed, when the row was saved (ISO 8601) |

### 💡 Tips

#### How to get more keywords

Use **Keyword ideas** mode: each seed gives a few hundred ideas from real searches (more with **Question ideas** on). Set `maxItems` to `0` and use short, broad seeds (`yoga mat` rather than `best non slip yoga mat for hot yoga`).

#### How to reduce costs

The price is per keyword row, so the levers are `maxItems`, `maxItemsPerQuery`, the filters (a keyword a filter drops costs only the filter fee, lower than a row) and `onlyNew` for recurring runs (you never pay twice for the same keyword). Keep **Full keyword data** and **Keyword difficulty** off when volume, CPC and competition are enough. Only the keywords the source returns no data for (`found: false`) are free: a misspelled or made-up word can come back with `0` searches and counts as a row.

#### Several keywords and locations in one run

Paste up to 10,000 keywords into **Keywords** and add places in **More locations**: every keyword is looked up in every location (3 keywords × 4 locations = 12 rows). The single `query` and `location` fields still work and are added to the lists. A keyword sent twice (case ignored) is looked up, saved and charged once. In Ideas mode, set `maxItemsPerQuery` to give every seed × location its own cap: without it the first seeds can use up the whole `maxItems` budget.

#### Cities: always add the country

Write a city as `City, Country` (`Lyon, France`) or `City, Region, Country` (`Springfield, Illinois, United States`). Many towns share a name, and `Paris` alone may give a town in the United States. The log always tells which place was used, for example `Location "Paris, France" → Paris, Ile-de-France, France (location id 1006094)`, and warns when several places share the name. Write the city's English name (`Vienna, Austria`, not `Wien`). When no place carries the name exactly, a place whose name begins with it as a whole word is used and the log says so (`Freiburg, Germany` → Freiburg im Breisgau); a name that no place carries or begins with stops the run before anything is charged, never a town whose name only starts with the same letters. States and regions alone are not available: `California` gives a city of that name, not the state. City rows carry volume, CPC and competition; the full data is often empty for a city.

#### Monitoring: only the new keywords

Tick **Only new keywords** (`onlyNew`) and schedule the Actor, best in Ideas mode to catch the new searches that appear around your seeds. The first run returns everything; each later run skips the keywords (same keyword, location and language) already delivered: they are not saved and not charged. The memory lives in a named key-value store of your account (`keyword-volume-scraper-seen`, up to 150,000 keywords per key) and is only updated with keywords that really reached the dataset, so a failed run never hides anything. Give each schedule its own `stateKey` (two schedules sharing a key would hide each other's keywords), and tick `resetState` once to start over. Your keyword list is always read to its end: new keywords you add to it are found wherever they are.

### 🔌 Integrations and API

Call the Actor via the Apify API, the JavaScript or Python clients, or connect it with integrations and webhooks (Make, Zapier, n8n, Google Sheets, Slack, Airtable…). The dataset can be fetched as JSON or CSV from any tool.

### 🤖 Use with AI agents (MCP)

AI agents (Claude, ChatGPT, Cursor…) can find and run this Actor through the [Apify MCP server](https://mcp.apify.com), billed to their Apify account like any run. It returns one row per keyword × location with its Google search volume, CPC and competition. Actor id: `nice_dev/keyword-volume-scraper`; MCP server with this Actor only: `https://mcp.apify.com/?tools=fetch-actor-details,nice_dev/keyword-volume-scraper`.

Smallest input, for a cheap first call:

```json
{
    "query": "running shoes",
    "location": "United States",
    "maxItems": 10
}
```

Key output fields: `keyword`, `searchVolume`, `cpcUsd`, `competitionLevel`, `location`, `found`, `searchIntent` (with `extractDetails`) and `seoDifficulty` (with `includeDifficulty`).

Cost: $1.79 per 1,000 keywords on the Free plan ($1.76 on Gold) plus $0.01 per run start, see the pricing section above. Cap each call with `maxItems` and, through the API, with the run option `maxTotalChargeUsd`.

### ❓ FAQ

#### Is it legal to scrape keyword search volume?

The Actor only reads keyword statistics that are shown publicly to any anonymous visitor. It logs in to nothing and solves no captcha. The data is made of counts and shares about searches, never about a person: it holds no personal data. You are responsible for using the data in compliance with the Terms of Use of the services involved and applicable law. This Actor is not affiliated with Google.

#### Do I need a Google Ads account or an API key?

No. You need nothing but your Apify account: no Google Ads account, no ad spend, no API key.

#### How exact are the volumes?

They are Google's own search volume figures, rounded the way Google Ads rounds them (… 33,100, 40,500, 49,500, 60,500, 74,000, 90,500, 110,000 …): two keywords in the same bracket show the same number. The CPC is the average cost of a click on a Google ad for the keyword. `dataUpdatedAt` tells when the figures of each keyword were last refreshed.

#### Does it need a login or a proxy?

No login. The proxy is included in the price: leave the default setting (the residential proxy is not available). A request that is turned away is retried at once on a new proxy session, up to 10 times on top of the retries (without a proxy, after a pause of 5 seconds, doubled at each retry up to 150 seconds). Behind a proxy, when the source keeps answering "too many requests" (HTTP 429), the retries that count wait the same way.

#### Is the data safe to open in Excel or to show on a web page?

Keywords are what people type: a keyword can begin with `-`, `+`, `=` or `@`, and Excel or Google Sheets may read such a cell of a CSV file as a formula or as a number. The Actor leaves the text as it is, so that the JSON and the API give the real value: when you open a CSV, import the keyword columns as text. On a web page, escape every field like any text written by a stranger. `url` is always a Google search link.

#### Known limitations

- `monthlySearches` covers the last months only (usually 4), not 12 months.
- Volumes are rounded to Google Ads brackets (see above), and keywords of categories Google Ads restricts (such as `vpn`) show a volume of 0.
- States and regions alone are not available, only countries, cities and `worldwide`. For a city that shares its name with others, check the place written in the log.
- `worldwide` reads each keyword on its own, one request per keyword instead of one per 10: the figures of the whole world are only given keyword by keyword, so such a run takes longer, and a keyword a filter drops there costs the `detail-filter-check`.
- A Google Ads location id that is not a country (a region, a city) is used as given: its rows have `cpcUsd` but no `cpc` in a local currency.
- The full data (intent, months, devices, clicks, age, gender, other countries) is often empty for a city, and the volume in other countries is given for country searches.
- Top-of-page bid ranges are not available.
- `onlyNew` remembers keywords, not their figures: a keyword whose volume changed is not returned again.
- Two runs sharing the same `stateKey` at the same time may both return the same new keyword.

**A run that reaches its timeout** stops itself about 45 seconds before it: no new request is sent, what it read is saved and, with `onlyNew`, remembered, and the run ends *Succeeded* with "Stopped before the run's timeout". Resurrect it to go on from there, or give the next run a longer timeout (Run options).

**A run the platform stops without warning** (out of memory)

- Resurrect it: it goes on from where it stood at most a minute before the stop. What it had read since is read again, and the keywords already saved are skipped: none is delivered or charged twice, and `maxItems` still counts them.
- With `onlyNew`, the memory is saved after each write (at most once every 2 seconds): resurrect the stopped run and the keywords it had saved meanwhile join the memory; leave it stopped for good, and the next run may return the keywords of its last 2 seconds once more.

#### Something doesn't work?

The last line of the log counts the keywords saved and filtered out, and the requests that failed after every retry. Those requests are listed, with the reason, in the `FAILED_REQUESTS` record of the run's key-value store. A run that saved nothing and had failed requests fails, and its last message gives the cause (a place that cannot be found says so before anything is charged, and suggests writing it as `City, Country`).

If the data source changes, you are told instead of paying for blank rows. If the first 20 keywords read all lack their search volume, CPC, competition or update date, the run saves nothing more, stops and fails, and its last message names the missing field: at most those first keywords are charged. Keywords without any data (`found: false`) are not counted in that check. A run of fewer keywords (a scheduled `onlyNew` run that finds a few new ones, a small **Max keywords**) is checked at its end, from 5 keywords: its keywords are already saved, and the run fails with the missing field instead of ending green.

### 🛟 Support

Open an issue in the **Issues** tab with a link to your run: the run log and the `FAILED_REQUESTS` record of the key-value store show exactly which requests failed and why.

# Actor input Schema

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

**Keyword metrics**: data for exactly the keywords you give. **Keyword ideas**: each keyword is a seed; the Actor finds the related searches people type (seed + a…z + question words) and returns the metrics of every idea, the seed included.

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

One keyword or phrase (e.g. `running shoes`). Put more in **Keywords** below.

## `searchQueries` (type: `array`):

Your keyword list, one per line (up to 10 000). Added to **Keyword**; duplicates (case ignored) are looked up once. In Ideas mode each line is a seed.

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

Where the volumes are counted: a country (`France` or its 2-letter code `fr`), `worldwide` (all countries together), a city written as `City, Country` (`Paris, France`; with a region `Springfield, Illinois, United States`) or a Google Ads location id (`1023191`). Always add the country to a city: many towns share a name (`Paris` alone may give an American town) and the log tells which one was used. States and regions alone are not available, only countries and cities. An id that is not a country is used as given: its rows have `cpcUsd` but no `cpc` (currency unknown). Empty = United States.

## `locations` (type: `array`):

Several locations in one run, written as in **Location** (`France`, `Paris, France`, `1023191`, `worldwide`): every keyword is looked up in every location (3 keywords × 4 locations = 12 rows). Added to **Location**. Max 100.

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

2-letter language code of the searches (`en`, `fr`, `de`, `ja`…). Empty = the main language of each location (French for France, English for worldwide).

## `maxItems` (type: `integer`):

Maximum number of keyword rows to save for the whole run (after deduplication and filters). 0 = no limit.

## `maxItemsPerQuery` (type: `integer`):

Ideas mode: cap for EACH seed keyword × location, so that the first seed cannot use up the whole **Max keywords** budget (a seed gives a few hundred ideas; its own row counts). 0 = no per-seed cap. In Keyword metrics mode it caps the rows of each location.

## `extractDetails` (type: `boolean`):

On: monthly searches of the last months, search intent, mobile / desktop share, click rate (organic / paid / no click), searcher age and gender, and search volume in the other countries. Off: volume, CPC and competition only (faster, cheaper per keyword, see Pricing).

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

Add the SEO difficulty (how hard it is to rank in the top 10 organic results, 0-100) and the paid difficulty (how crowded the ads are, 0-100) to each keyword. Charged per keyword that has them (see Pricing).

## `includeQuestionIdeas` (type: `boolean`):

Ideas mode: also expand each seed with question and comparison words (`how`, `what`, `why`, `best`, `vs`, `for`…), in the language of the search (English, French, German, Spanish, Italian, Portuguese, Dutch; other languages get the English words).

## `includeNotFound` (type: `boolean`):

Save a row with `found: false` for each keyword the source returns no data for (a keyword nobody searches comes back with 0 searches, a normal row): in Keyword metrics mode the output then keeps one row per input keyword, in Keyword ideas mode an idea without data is kept too. These rows are free.

## `minVolume` (type: `integer`):

Only keywords with at least this many monthly searches. 0 = no minimum.

## `maxVolume` (type: `integer`):

Only keywords with at most this many monthly searches (find long-tail keywords). 0 = no maximum.

## `minCpc` (type: `number`):

Only keywords whose cost per click, in US dollars, is at least this. 0 = no minimum.

## `maxDifficulty` (type: `integer`):

Only keywords whose SEO difficulty (0-100) is at most this. Works with or without the difficulty add-on. 0 = no maximum.

## `excludeKeywords` (type: `array`):

Drop the keywords that contain one of these words (case and accents ignored), e.g. `free`, `amazon`.

## `onlyNew` (type: `boolean`):

Skip the keywords (same keyword, location and language) that a previous run with the same **Memory key** already delivered: they are not saved and not charged. First run = everything is new.

## `stateKey` (type: `string`):

Name of the memory used by **Only new keywords**. Give each schedule / task its own key (e.g. `shoes-ideas`) so that they do not share their memory. Letters, digits, `-` and `_`.

## `resetState` (type: `boolean`):

Forget everything remembered under this **Memory key** before the run: this run returns (and charges) every keyword again. Untick it afterwards.

## `proxyConfiguration` (type: `object`):

Apify Proxy or your own proxies. Keep the default: it is included in the price. The residential Apify proxy is not available in this Actor.

## `maxConcurrency` (type: `integer`):

Maximum number of requests processed in parallel.

## `maxRequestsPerMinute` (type: `integer`):

Most requests in any 60 seconds, all of them counted (keyword data and idea lookups). A budget, not an even pace: up to this many can leave at once when the minute starts (spread them with the minimum delay below). Lower it if the log shows HTTP 429 / 403.

## `minRequestIntervalMs` (type: `integer`):

Smallest gap between two requests, in milliseconds. Unlike the per-minute rate, this spreads the requests evenly instead of letting them go out in a burst. 0 = no gap.

## `maxRequestRetries` (type: `integer`):

Retries per request before it is marked as failed. Behind a proxy, a request that is turned away is also retried on a new proxy session up to 10 times without using up these retries.

## `debugLog` (type: `boolean`):

Include debug messages in the run log.

## Actor input object example

```json
{
  "mode": "metrics",
  "query": "running shoes",
  "searchQueries": [
    "yoga mat",
    "standing desk"
  ],
  "location": "United States",
  "locations": [],
  "maxItems": 1000,
  "maxItemsPerQuery": 0,
  "extractDetails": false,
  "includeDifficulty": false,
  "includeQuestionIdeas": true,
  "includeNotFound": true,
  "minVolume": 0,
  "maxVolume": 0,
  "minCpc": 0,
  "maxDifficulty": 0,
  "excludeKeywords": [],
  "onlyNew": false,
  "stateKey": "default",
  "resetState": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "maxConcurrency": 10,
  "maxRequestsPerMinute": 600,
  "minRequestIntervalMs": 0,
  "maxRequestRetries": 5,
  "debugLog": false
}
```

# Actor output Schema

## `results` (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": "running shoes",
    "searchQueries": [
        "yoga mat",
        "standing desk"
    ],
    "location": "United States",
    "extractDetails": false,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("nice_dev/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 = {
    "query": "running shoes",
    "searchQueries": [
        "yoga mat",
        "standing desk",
    ],
    "location": "United States",
    "extractDetails": False,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("nice_dev/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 '{
  "query": "running shoes",
  "searchQueries": [
    "yoga mat",
    "standing desk"
  ],
  "location": "United States",
  "extractDetails": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call nice_dev/keyword-volume-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nice_dev/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/eoQ6EfM6WNVF8oUvh/builds/9wIhLTHxC7UTkilan/openapi.json
