# Bulk Keyword Search Volume & CPC Checker (`datapulsehq/keyword-search-volume`) Actor

Bulk keyword search volume, CPC, competition, top of page bid, 12-month trend for up to 10,000 keywords per run in any country. No Google Ads account needed. Seasonal keywords flagged by peak month. Keyword ideas and search intent included. Keywords without data are free. Not affiliated with Google.

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

## Pricing

from $5.00 / 1,000 keyword results

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

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

## What's an Apify Actor?

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

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

## How to integrate an Actor?

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

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

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

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

# README

## Bulk Keyword Search Volume & CPC Checker

This Actor looks up Google Ads keyword data for a list of keywords: average monthly searches, CPC, competition, the top-of-page bid range and the last 12 months of search volume. You can send up to 10,000 keywords in one run. There is also a keyword ideas mode that takes a few seed keywords and returns related keywords with the same numbers.

Data is provided by a licensed third-party data provider that sources it from the Google Ads API.

*This Actor is an independent tool and is not affiliated with, endorsed by, or sponsored by Google. Google, Google Ads and Google Keyword Planner are trademarks of Google LLC.*

### Output

One row per keyword. A real row from a US test run:

```json
{
    "keyword": "best crm software",
    "searchVolume": 9900,
    "cpc": 37.4,
    "competition": "LOW",
    "competitionIndex": 18,
    "lowTopOfPageBid": 7.81,
    "highTopOfPageBid": 50.08,
    "trendDirection": "up",
    "trendChangePct": 518.8,
    "seasonalPeakMonth": "August",
    "isSeasonal": true,
    "intent": "commercial",
    "intentSignal": "best",
    "intentConfidence": "rule",
    "latestMonth": "2026-09",
    "latestMonthSearchVolume": 2900,
    "monthlySearches": [{ "month": "2025-10", "searchVolume": 1900 }, "... 12 months, oldest first"],
    "monthlySearchesText": "2025-10:1900|2025-11:1300|...|2026-09:2900",
    "spellCorrection": null,
    "location": "United States",
    "locationCode": 2840,
    "language": "English",
    "languageCode": "en",
    "mode": "searchVolume",
    "hasData": true,
    "source": "live",
    "fetchedAt": "2026-10-21T20:42:23.000Z"
}
```

`cpc` and the bids are in USD. `monthlySearchesText` holds the same 12 months as `monthlySearches` in a single string, because nested arrays do not survive a CSV or spreadsheet export. Every other field is a plain value, so the dataset can go straight into a spreadsheet, an automation workflow, or an AI agent's tool output.

### Pricing

$0.008 per keyword that comes back with data ($8 per 1,000), plus $0.05 once per run when the run has to request new data. A run that is answered entirely from the cache does not pay the $0.05. Keywords without data and invalid keywords are free.

For example, 1,000 new keywords that all return data cost $8.05. Running the same list again within 30 days costs $8.00, because every row comes from the cache.

Rows served from the cache cost the same $0.008 as fresh rows. You get the same numbers, and Google only refreshes them once a month.

If you set a maximum cost per run, the Actor calculates how many keywords fit in that budget before it requests anything, requests only those, and lists the rest under `skippedForBudget` in the run summary.

### Apify free plan limit

Users on the Apify free plan get up to 20 keywords with data per run. In search volume mode the Actor looks up the first 20 valid keywords of the list and names the rest under `freePlanLimit.skipped` in the run summary. In keyword ideas mode it stops after 20 ideas. The run still finishes as succeeded, and its status message says that the free plan limit was reached.

This limit was set by the developer of this Actor, not by Apify. The keyword data is paid per request, and runs by free plan users bring the developer no revenue. Any paid Apify plan removes the limit, and nothing else about the run changes.

### Examples

Search volume for US keywords:

```json
{
    "mode": "searchVolume",
    "keywords": [
        "buy running shoes",
        "best running shoes for flat feet",
        "how to clean white sneakers",
        "nike store near me",
        "hubspot vs salesforce"
    ],
    "location": "United States",
    "language": "en"
}
```

The 500 highest-volume ideas for two seed keywords:

```json
{
    "mode": "keywordIdeas",
    "keywords": ["crm software", "email marketing software"],
    "location": "us",
    "maxResults": 500,
    "sortBy": "search_volume"
}
```

### Use cases and common questions

#### Bulk keyword search volume for a long list

Paste the list from your spreadsheet or content plan into `keywords`. Up to 1,000 keywords go into one data request, so 2,000 keywords take two requests. You get one row per keyword with search volume, CPC and the 12-month history side by side, which makes it easy to sort a content plan by demand.

For a bulk keyword CPC check before a paid campaign, leave `includeNoDataRows` off. Keywords that Google reports nothing for are then left out of the dataset and are not charged.

#### What the top of page bid fields mean

`lowTopOfPageBid` and `highTopOfPageBid` are the low and high end of what advertisers have paid for an ad shown above the organic results, in USD. In the example row above, `best crm software` has a CPC of 37.40 and a top of page bid range of 7.81 to 50.08. When a keyword has only a few advertisers, the range tells you more about the budget you need than the CPC alone.

#### Finding seasonal keywords

Filter the dataset on `isSeasonal = true` and sort by `seasonalPeakMonth`. That gives you the seasonal keywords in your list and the month each one peaks, worked out from the last 12 months as described under Derived fields. `best crm software` peaks in August in the run above, so a page or campaign for it should be ready by July.

#### Is this a keyword planner?

It returns the kind of numbers a keyword planner shows: average monthly searches, competition, CPC, the bid range, and related keyword ideas for seed keywords. It is a separate tool, not Google's Keyword Planner, and it is not affiliated with Google. The data comes from a licensed third-party provider that sources it from the Google Ads API.

### Input

| Field | Default | What it does |
| --- | --- | --- |
| `mode` | `searchVolume` | `searchVolume` for your own list, `keywordIdeas` for related keywords from seeds |
| `keywords` | example | Your keywords, or up to 100 seed keywords in ideas mode (sent 20 at a time). Without this field the Actor runs a small example (5 keywords, or one seed with up to 20 ideas) |
| `stripInvalidCharacters` | `false` | Remove characters Google Ads rejects instead of skipping the keyword |
| `location` | `United States` | Country name, two-letter code (`us`), Google Ads location code (`2840`), a full location name such as `New York,New York,United States`, or `Worldwide` for all locations combined. Leaving the field out means the United States. An empty value, an unknown location or an unknown language is invalid input. Invalid input ends the run as SUCCEEDED with a clear error message and no charge ($0). |
| `language` | `en` | Language code or name |
| `maxResults` | `1000` | Ideas mode: maximum number of ideas returned |
| `sortBy` | `relevance` | Ideas mode: `relevance`, `search_volume`, `competition_index`, `low_top_of_page_bid` or `high_top_of_page_bid` |
| `searchPartners` | `false` | Include Google search partner traffic |
| `useCache`, `cacheTtlDays` | `true`, `30` | Reuse results up to 30 days old |
| `includeNoDataRows` | `false` | Also output keywords without data (free) |
| `pushSummaryToDataset` | `false` | Add the run summary as the last dataset row |

Worldwide figures (`"location": "Worldwide"`, also accepted as `global` or `all`) are one aggregate across all locations for which data is available. The data source does not document in detail how the per-location data is combined, so treat a worldwide number as a guide to overall demand and do not expect it to equal the sum of country figures. Rows are labelled `Worldwide` with an empty `locationCode`. The language setting still applies.

### Supported countries and languages

Search volume and CPC are available for any country that Google Ads targets, and also for regions and cities, using their full location name such as `New York,New York,United States`. Russia and Belarus are not available. A country can be given by name, two-letter code or location code. The language is a language code or name. Some examples:

| Country | `location` | Location code | Typical `language` |
|---|---|---|---|
| United States | `us` | 2840 | `en` |
| United Kingdom | `uk` | 2826 | `en` |
| India | `in` | 2356 | `en` or `hi` |
| Germany | `de` | 2276 | `de` |
| France | `fr` | 2250 | `fr` |
| Brazil | `br` | 2076 | `pt` |
| Spain | `es` | 2724 | `es` |
| Mexico | `mx` | 2484 | `es` |
| Indonesia | `Indonesia` | 2360 | `id` |
| Japan | `jp` | 2392 | `ja` |

Keywords in German for Germany:

```json
{
    "keywords": ["laufschuhe", "laufschuhe damen", "trailschuhe"],
    "location": "Germany",
    "language": "de"
}
```

For one combined figure across all locations, set `"location": "Worldwide"`. Keep the language that matches your keywords, usually `en` for English keywords. Results for each location, and for Worldwide, are cached separately.

### How keywords are cleaned

Google Ads refuses a whole request if one keyword in it contains a character it does not accept. So every keyword is checked before anything is sent.

First the text is trimmed, lower-cased and repeated spaces are collapsed. Curly quotes and long dashes are turned into `'` and `-`, and match-type syntax (`"phrase"`, `[exact]`, `+word`, `-word`) is removed. `Running Shoes` and `running  shoes` then count as the same keyword and are looked up once.

Then the keyword is checked against Google's list of forbidden characters. That list includes `! @ % ^ * ( ) = { } ; ~ ? < > \ |`, backticks, commas, emoji and a long tail of Unicode symbols. `&`, `'`, `.`, `-`, `+`, `/`, `#`, `$` and accented letters are fine, so `at&t plans`, `men's shoes`, `c++ tutorial` and `nike.com` are looked up as written. A keyword with a forbidden character is skipped, not charged, and listed under `invalidKeywords` in the summary with the exact character, for example `forbidden character '%' (U+0025)`. With `stripInvalidCharacters` the character is removed and the rest is looked up.

Keywords longer than 80 characters or 10 words are skipped the same way.

If Google still rejects a batch because of one keyword, the Actor splits the batch in half and retries each half until it finds the keyword that caused it. That keyword goes into `invalidKeywords` and the others are returned normally.

### Derived fields

These come from simple rules on Google's numbers. They are useful for sorting and filtering, but they are not a forecast.

`trendDirection` compares the median of the last 3 months with the median of the 9 months before them: `up` at +15% or more, `down` at -15% or less, `flat` in between. Medians keep a single spike month from deciding the direction. It is `null` when fewer than 6 months have data.

`seasonalPeakMonth` is the calendar month with the most searches. `isSeasonal` is `true` when that month is at least 1.5 times the 12-month average.

`intent` is set from words in the keyword. `buy`, `price`, `coupon` or `near me` make it transactional. `best`, `vs`, `review` or `alternatives` make it commercial, and `how to`, `what is` or `guide` make it informational. It is navigational when the keyword contains a domain (`amazon.com`), an account word (`login`, `sign in`, `sign up`, `account`, `app download`) or is just the name of a very common site such as `youtube` or `gmail`. `intentSignal` shows the word that matched. `intentConfidence` is `rule`, `brand` (whole keyword is a known site name) or `none`. When nothing matches, `intent` is `null`. I would rather leave it empty than guess. Brand names outside the short built-in list, like `nike`, end up there.

### Run summary

Each run writes a `SUMMARY` record to its key-value store (linked from the Output tab). It has the counts of valid, duplicate, invalid, cached, missing, failed and budget-skipped keywords, the lists themselves (up to 1,000 each), the charged events, the number of data requests and how long the run waited on rate limits. If the input has nothing the Actor can use (no valid keywords, every keyword rejected by Google Ads, an unknown location or language, or a maximum charge per run too low for one keyword), the run ends as succeeded with an empty dataset, nothing is charged, and the status message says what was wrong and how to fix it; the summary status is then `invalid_input`.

### Cache

Results are kept for 30 days in a named key-value store in your account, `keyword-search-volume-cache`, keyed by keyword, location and language. `running shoes` in the US, in the UK and worldwide are separate entries. Keywords that had no data are kept for 7 days. Ideas from ideas mode are cached too, so a later search volume run for them is served from the cache.

The cache is updated after every batch, so a run that stops halfway still keeps what it fetched. Two runs at the same time each add their own keywords to the cache; neither overwrites the other's entries. Turn off `useCache` to always fetch fresh data. You can delete the store whenever you like.

### Speed and rate limits

Keywords are requested in batches of 1,000. The keyword data source allows 12 requests per minute, and that limit is shared by everyone running this Actor at the same moment. Each run leaves at least 5 seconds between its own requests. When the shared limit is hit anyway, the run waits and tries again rather than failing, for up to 15 minutes in total.

So a few hundred keywords take a few seconds, a 10,000-keyword run needs 10 requests and at least a minute, and very large runs can take noticeably longer when other people are using the Actor at the same time. Cached keywords do not count toward the limit.

### Why some keywords have no data

Google does not report volume for keywords in restricted ad categories such as weapons, tobacco and drugs. In my US test `buy bitcoin` and `best vpn` came back empty. Google also groups close variants, so a misspelling or plural can return the numbers of the grouped keyword; `spellCorrection` shows the spelling Google used when it changed it. Search volume is rounded by Google into buckets (880, 1,000, 1,300 and so on), and the current month is never included. Google updates the data around the middle of each month.

### Calling it from code

```bash
curl -X POST "https://api.apify.com/v2/acts/datapulsehq~keyword-search-volume/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"keywords": ["buy running shoes", "best running shoes"], "location": "us"}'
```

This returns a JSON array with one object per keyword. Add `&format=csv` for CSV.

# Actor input Schema

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

<b>Search volume</b>: metrics for the exact keywords you list (up to 10,000 per run).<br><b>Keyword ideas</b>: related keywords (with the same metrics) generated from up to 100 seed keywords.

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

One keyword per line. In 'Keyword ideas' mode these are the seed keywords (max 100, sent in groups of 20). Keywords are trimmed, lower-cased and de-duplicated. Keywords with characters Google Ads does not accept, or longer than 80 characters or 10 words, are skipped, not charged, and listed in the run summary under invalidKeywords. Users on the Apify free plan get the first 20 valid keywords per run. If the field is left out, the Actor runs a small example: 5 running shoe keywords, or in 'Keyword ideas' mode the seed 'running shoes' with up to 20 ideas.

## `stripInvalidCharacters` (type: `boolean`):

Off: a keyword with a character Google Ads does not accept (for example ! @ % \* ? or an emoji) is skipped and listed as invalid. On: the character is removed and the rest of the keyword is looked up.

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

Country name (e.g. 'United States'), two-letter code ('us') or Google Ads location code (2840). A region or city can be given by its full Google Ads location name, e.g. 'New York,New York,United States'. Enter 'Worldwide' for one combined figure across all locations. Leave the field out for the United States; an empty value is rejected.

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

Language code (e.g. 'en') or name ('English').

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

Keyword ideas mode only: maximum number of ideas (rows with data) to return and charge. Users on the Apify free plan get at most 20 per run.

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

Keyword ideas mode only. Order in which ideas are returned (and therefore which ones are kept when 'Max keyword ideas' cuts the list).

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

Include search volume from Google search-partner sites.

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

Reuse results fetched in the last 30 days for the same keyword + location + language. Faster; cached rows are delivered and charged like fresh rows (Google refreshes this data only once a month).

## `cacheTtlDays` (type: `integer`):

Maximum age of cached results to reuse.

## `includeNoDataRows` (type: `boolean`):

Also output (free of charge) a row for keywords Google returned no volume for. They are always listed in the run summary.

## `pushSummaryToDataset` (type: `boolean`):

Append the run summary as the last dataset item (type = 'summary'). Off by default to keep the dataset a clean table; the summary is always saved in the key-value store as SUMMARY.

## Actor input object example

```json
{
  "mode": "searchVolume",
  "keywords": [
    "running shoes",
    "best running shoes for flat feet",
    "buy nike air max",
    "how to clean white sneakers",
    "nike store near me"
  ],
  "stripInvalidCharacters": false,
  "location": "United States",
  "language": "en",
  "maxResults": 1000,
  "sortBy": "relevance",
  "searchPartners": false,
  "useCache": true,
  "cacheTtlDays": 30,
  "includeNoDataRows": false,
  "pushSummaryToDataset": false
}
```

# Actor output Schema

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

No description

## `summary` (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 = {
    "keywords": [
        "running shoes",
        "best running shoes for flat feet",
        "buy nike air max",
        "how to clean white sneakers",
        "nike store near me"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("datapulsehq/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": [
        "running shoes",
        "best running shoes for flat feet",
        "buy nike air max",
        "how to clean white sneakers",
        "nike store near me",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("datapulsehq/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": [
    "running shoes",
    "best running shoes for flat feet",
    "buy nike air max",
    "how to clean white sneakers",
    "nike store near me"
  ]
}' |
apify call datapulsehq/keyword-search-volume --silent --output-dataset

```

## MCP server setup

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