# Keyword Search Volume History, Up to 8 Years (`automation_craft/keyword-search-volume-history`) Actor

Keyword search volume history: see how demand for each keyword moved over the years. Monthly Google Ads searches for 48 months, up to 8 years with the long history add-on, plus CPC, competition and bid history since 2021. Yearly totals, growth, highest and lowest month. JSON, CSV, API.

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

## Pricing

from $0.23 / 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

### Keyword Search Volume History, Up to 8 Years

**Keyword search volume history** for your keyword lists, keyword ideas and websites: **48 months of monthly Google searches** on every row from Google Ads (Keyword Planner data), **up to about 8 years** (back to about October 2018) with the long history add-on, and the **month by month history of CPC, competition and top of page bids** since mid 2021 with the metrics history add-on. Every row also carries its history figures: the total of every calendar year, the change between the first and the last full year and its yearly rate, the highest and the lowest month. Any Google Ads location: countries, regions, cantons, states and cities. No Google Ads account and no API key needed.

Built for SEO and PPC teams, agencies, content and market researchers, ecommerce sellers and automation builders who need to see how demand for a keyword moved over the years, not only last year's average.

#### What makes it different

- **Up to 8 years, labelled by source.** With the long history add-on, the months before the 48 Google Ads keeps come from the DataForSEO Labs keyword database (copied monthly from Google Ads), and every month of the long series says where it came from (`labs` or `google_ads`).
- **CPC and competition history.** The metrics history add-on gives one entry per monthly snapshot since mid 2021: average searches, CPC, competition and its level, low and high top of page bid, with a summary of how CPC and competition moved.
- **History figures computed for you, at no extra cost.** Yearly totals with the months each year has data for, the first and last full calendar year, the change between them and the yearly growth rate, the highest and lowest month, the average month.
- **48 months, labelled.** Every month is written `YYYY-MM` with its year and month number, oldest first, and a month Google did not report is listed in `missingMonths` instead of being left out.
- **Fair billing.** A keyword without Google data is a free row, and so is everything delivered again from your 30 day memory. A lookup that finds no keyword with data is free.
- **CSV for sheets.** `MONTHLY_SEARCHES.csv` has one line per keyword per month (long format) for the 48 Google Ads months, ready for Google Sheets or Excel.
- **Seasonality and planning too.** The seasonal peak months, the trend, and a rank of your keywords for each of the next 1 to 3 months you plan for.

### Quick start

1. Paste your keywords (one per line), or seed keywords with **What to look up** set to keyword ideas.
2. Pick one or more **Locations**: a country name, a two letter code (CH), Worldwide, or a region or city the way Google Ads writes it (`Zurich,Switzerland`).
3. Tick **Add 8 years of history and keyword insights** and **Add metrics history** for the longest history (both are ticked in the Console form).
4. Click **Start**. A queued lookup is usually ready in 10 to 60 seconds. Export the dataset as JSON, CSV or Excel.

**About 8 years of monthly searches and the CPC history for a keyword list:**

```json
{
    "keywords": ["winter jacket", "crm software", "iphone"],
    "locations": ["United States"],
    "includeInsights": true,
    "includeMetricsHistory": true
}
```

**48 months of search volume history for a keyword list in Switzerland (no add-on):**

```json
{
    "keywords": ["winterjacke", "regenjacke", "badehose"],
    "locations": ["Switzerland"]
}
```

**Keyword ideas for a seed with their history, at most 200 ideas:**

```json
{
    "mode": "ideas",
    "keywords": ["rain jacket"],
    "locations": ["United States"],
    "maxKeywords": 200
}
```

### What you get

One **keyword row** per keyword and location, one **plan row** per planned month, free **status rows** for anything that was refused or skipped, and a **summary row** at the end.

| Field | What it holds |
|---|---|
| `keyword` | The keyword as Google Ads keys it (lower case) |
| `dataStatus` | `ok` (Google has data, the row is charged unless it came from the memory) or `no_data` (free) |
| `searchVolume` | Google Ads average monthly searches (last 12 months) |
| `monthlySearches` | Oldest first: `month` (YYYY-MM), `year`, `monthNumber`, `searches` (48 months by default) |
| `missingMonths`, `monthsWithData` | Months of the window Google did not report |
| `history` | Over the Google Ads months: `yearlyTotals` (each year's `searches`, `monthsWithData`, `fullYear`), `firstFullYear`, `lastFullYear`, `fullYearChangePct`, `compoundAnnualGrowthPct`, `highestMonth`, `lowestMonth`, `totalSearches`, `averageMonthlySearches`, `firstMonth`, `lastMonth` |
| `extendedMonthlySearches`, `extendedMissingMonths` (add-on) | Up to about 8 years of months, oldest first: the Labs months before Google's 48, then the Google Ads months, each with `source` (`labs` or `google_ads`); for a whole country with the default window, where the Labs database holds older months for the keyword (otherwise null, with `extendedHistoryNote`) |
| `extendedHistory`, `extendedHistoryNote` (add-on) | The same history figures over the long series, and a note on its sources |
| `metricsHistory` (add-on) | One entry per monthly snapshot since mid 2021: `searchVolume`, `cpc`, `competition` (0 to 1), `competitionLevel`, `lowTopOfPageBid`, `highTopOfPageBid` |
| `metricsHistoryMissingMonths` (add-on) | Months between the first and the last snapshot that the Labs answer did not hold |
| `metricsHistorySummary`, `metricsHistoryFrom`, `metricsHistoryLocation`, `metricsHistoryLanguage`, `metricsHistorySource` (add-on) | First and latest CPC and the change in percent, highest and lowest CPC month, first and latest competition, the number of snapshots, and where they come from |
| `cpc`, `competition`, `competitionIndex`, `lowTopOfPageBid`, `highTopOfPageBid`, `currency` | Google Ads cost per click, competition and top of page bids today (USD) |
| `emptyFields` | The metric fields Google left empty on a row with data |
| `trend` | `yoyChangePct`, `qoqChangePct`, `momChangePct`, `last3MonthsVsYearBeforePct`, `last12MonthsTotal`, `previous12MonthsTotal`, `direction` (growing, stable, declining), `latestMonth` |
| `seasonality` | `seasonalIndex` and `monthlyAverages` per calendar month, `peakMonth`, `peakMonths`, `lowMonth`, `lowMonths`, `strength`, `label`, `peakConsistency`, `cyclesCovered` |
| `planning` | Per planned month: `historicalAverage`, `seasonalIndex`, `lastYearSearches`, `changeFromLatestPct`, `historicalEvidence`, `planningAssumption` |
| `locationCode`, `locationName`, `locationType`, `countryCode`, `language`, `searchPartners` | The targeting of the row |
| `dateFrom`, `dateTo`, `dataAsOf`, `retrievedAt`, `source`, `sourceEndpoint`, `lookupId` | The month window, the date Google last updated its keyword data, when the row was read and where it came from |
| `closeVariants`, `spellCorrectedTo` | Keywords of the same lookup that Google gave the same figures (close variants), and Google's spelling |
| `discoveredBy`, `seedKeywords`, `isSeed`, `extraWords`, `extraWordsNote`, `conceptGroups`, `discovery` | For ideas and website keywords: the source, the seeds it matches, the words it adds to them, Google's concept groups, and for ranked keywords the ranking position, page, title and estimated traffic |
| `inputPositions` | Where the keyword stood in your list |
| `fromMemory`, `memoryDeliveredOn` | Delivered again from your 30 day memory (free) |
| `insights` (add-on) | `keywordDifficulty` (0 to 100), `searchIntent`, `otherIntents`, `serpFeatures`, `googleResultsCount`, `coreKeyword`, `detectedLanguage`, `categories`, `longHistory` (the Labs months on their own), `longHistoryFrom`, `longHistorySource`, `insightsLocation`, `insightsLanguage`, `insightsUpdatedAt` |
| `aiSearchVolume`, `aiMonthlySearches` (add-on) | How often the keyword is asked in AI tools, 12 months |
| `bing` (add-on) | Bing `searchVolume`, `cpc`, `competition` (a number from 0 to 1), `monthlySearches` (24 months) |

**Words of demand, not product facts.** In ideas, `extraWords` lists what a keyword adds to its seed. They describe what people search for; use one in a product title only when the product really has it.

#### Measured fill

Measured on the platform matrix of this Actor on its own private listing on 2026-10-19 (31 scenarios, `platform-results/matrix-real1.json`; US, Swiss, Canadian and British keywords of different kinds: head terms, seasonal, B2B, brands, a new term, a long tail phrase, keyword ideas):

| Field | Rows |
|---|---|
| `monthlySearches` (48 months) and `history` | 1,124 of 1,124 keyword rows with data (100 percent); 1,122 of them with two or more full calendar years, 1,121 with `fullYearChangePct` (one row's first full year had no searches) |
| `extendedMonthlySearches` with the long history add-on | 10 of 10 United States rows over the default window that asked for it (an 11th row asked for a 24 month window, which never gets Labs months); 91 to 96 months, every one from 2019-06 or earlier |
| `metricsHistory` with the metrics history add-on | 10 of 10 rows the run could pay for (2 more were held back by a charge limit scenario); 60 to 63 monthly snapshots each; CPC on 612 of 615 snapshots, top of page bids on 539 (bids and the competition level start in 2022-03); 5 of the 10 rows had one to three snapshot months missing in the Labs answer, listed in `metricsHistoryMissingMonths` |

The latest snapshot of the metrics history equals the row's Google Ads CPC and top of page bids on 10 of 10 rows. In the feasibility spike (`FEASIBILITY.md`), the Labs months of the long series equalled Google Ads on 166 of 167 of the last 24 months; older Labs months can differ, most for long tail keywords (a long tail phrase matched Google Ads on 2 of 24 older months). A new keyword has fewer snapshots (a term that appeared in late 2022 had 44; a long tail phrase 23). Labs answers can leave out a snapshot month: one keyword came back with 63 snapshots in one run and 60 in another the same evening.

#### History figures, and what they are not

`history` is computed from the monthly searches of the row (`extendedHistory` from the long series): a calendar year is a full year when all 12 of its months have data, `fullYearChangePct` compares the last full year with the first, and `compoundAnnualGrowthPct` is the yearly rate between them. The long series is built only for a whole country, on Google search alone (search partners off), over the default window (the 48 months Google Ads keeps, to the latest month it published): a window you shortened at either end is never filled with Labs months, and a region or city row, or a row with search partners, gets no long series, since the Labs months are national figures for Google search. All of it is past search demand: it is not a forecast of demand or sales. Seasonality and planning (`seasonality`, `planning`, plan rows) are computed the same way over the Google Ads months.

### How much does it cost to get keyword search volume history?

You pay per result with Apify's pay per event pricing. Prices in USD:

| Event | When it is charged | FREE | BRONZE | SILVER | GOLD |
|---|---|---|---|---|---|
| Keyword | One keyword row with Google data | 0.00032 | 0.00029 | 0.00027 | 0.00023 |
| Lookup | One Google Ads or Bing request (up to 1,000 keywords, or one set of ideas) that delivered a charged keyword | 0.36 | 0.33 | 0.30 | 0.26 |
| Fast lookup | Fast speed only: one paid request that was sent live | 0.21 | 0.19 | 0.17 | 0.15 |
| Keyword insight | Add-on, one charged row with the long history and insights | 0.00084 | 0.00077 | 0.0007 | 0.0006 |
| Metrics history | Add-on, one charged row with its CPC, competition and bid history | 0.0042 | 0.0038 | 0.0035 | 0.003 |
| Discovered keyword | One charged row found through related searches, suggestions, autocomplete or ranked keywords | 0.00084 | 0.00077 | 0.0007 | 0.0006 |
| AI search volume | Add-on, one charged row with an AI search volume | 0.00084 | 0.00077 | 0.0007 | 0.0006 |
| Actor start | Once per run | 0.40 | 0.40 | 0.40 | 0.40 |

Platinum and Diamond plans pay the Gold price. The Store pricing card shows these same prices per 1,000 events: "$0.23 / 1,000" on the Keyword row means one keyword costs $0.00023, "$3.00 / 1,000" on the Metrics history row means one keyword's metrics history costs $0.003, and "$260.00 / 1,000" on the Lookup row means one lookup costs 26 cents.

**What a run costs** (Gold plan): 3 keywords cost 0.40 (start) + 0.26 (one lookup) + 3 x 0.00023 = **0.6607**; with both history add-ons 0.6607 + 3 x 0.0006 + 3 x 0.003 = **0.6715**. 1,000 keywords in one lookup cost 0.40 + 0.26 + 0.23 = **0.89**; with both history add-ons 0.89 + 0.60 + 3.00 = **4.49**. The price does not depend on how many months you ask for.

**Never charged as events** (the start fee is charged at every run, refused ones included): keywords without Google data, rows delivered again from your memory, filtered keywords, refused input, the plan rows, the status rows and the summary, a lookup that finds no keyword with data, a close variant left out by Merge close variants, and an add-on for a keyword the Labs database does not hold. A run whose lookups keep finding nothing stops after what its start fee can carry, with a free row that says so (`spend_bound`); free rows past what the run's price can carry are not written, and a status row says how many (`free_rows_capped`, `memory_rows_capped`).

**Spending control:** set `spendingLimitUsd`, or the run option Max total charge; the run stops before its charges would pass it; the keywords it did not reach are counted in the summary (`keywordsNotLookedUp`, and `rows.withheldByChargeLimit` for keywords looked up but held back by the limit), and `resumeFromPosition` names the first list position to run again. An add-on is bought only for the keywords whose add-on event still fits the limit, and only while the run's price carries its request; an add-on request it does not carry is skipped with a free row (`addon_not_bought`), and the keyword rows go out without it. With more locations or lookups still ahead in the run, an add-on request that would leave too little room for the next lookup gives way first, so the keyword rows of every location are looked up before add-ons. The free row names which of the two happened and what to do: run that location on its own, or run the keywords again with the add-on in a smaller run.

**Restarts.** When the platform restarts a run (a migration), the run goes on from its ledger without buying or charging anything twice; its summary counts the rows and charged events of the whole run, add-ons included, and its outcome is `partial` with a note, since a lookup or add-on in flight at the restart may be missing. If the restarted run cannot read its dataset back, it stops (`restart_read_failed`) and `rowCountsCover` says its row counts cover that attempt only.

### Input

| Setting | What it does |
|---|---|
| `mode` | `volume` (your keywords), `ideas` (ideas from seeds, seeds included), `website` (keywords for websites) |
| `keywords`, `keywordsText` | Keywords or seeds, one per line; the paste box takes a copied column |
| `websites`, `websiteScope`, `websiteSource` | Website mode: up to 100 domains or page links; Google Ads keywords for them, or the keywords a domain ranks for |
| `ideasSource`, `maxKeywords`, `maxIdeasPerSeed`, `sortBy` | Ideas mode: Google Ads ideas, related searches, suggestions or autocomplete; the caps and the order |
| `locations`, `language` | Up to 10 locations; the language for ideas, website keywords and the Labs add-ons |
| `historyMonths`, `dateFrom`, `dateTo` | The month window of the Google Ads history (default the latest 48 months) |
| `includeInsights` | The long history (about 8 years) and keyword insights add-on |
| `includeMetricsHistory` | The CPC, competition and bid history add-on |
| `planningMonths`, `planFrom` | The months to plan for (default the next 3) |
| `minSearchVolume`, `includeWords`, `excludeWords` | Filters (whole words, one rule for both lists) |
| `mergeCloseVariants`, `searchPartners`, `includeAdultKeywords` | Google Ads options (`mergeCloseVariants` delivers one keyword of each close variant group in the whole run; it works with your list, Google Ads ideas and Google Ads website keywords) |
| `speed` | `slow` (queued, default) or `fast` (live requests) |
| `includeAiVolume`, `includeBing` | Add-ons |
| `memoryName`, `refreshMemory` | The 30 day memory and its refresh |
| `spendingLimitUsd`, `maxRunMinutes` | Limits |

A location name must be written the way Google Ads writes it; `Zurich` alone is refused with the closest names (`Zurich,Switzerland` is the canton, `Zurich,Zurich,Switzerland` the city). Country names also work in their own languages (Schweiz, Suisse) and as ISO codes (CH, CHE) or locale tags (de-CH). The two Labs add-ons are given per country (a region or city row gets its country's insights and metrics history, named in `insights.insightsLocation` and in `metricsHistoryLocation`, and no long series), in the languages the Labs database lists for that country.

**Speed.** `slow` sends each lookup through the queue: on 2026-10-18 eight queued lookups were ready in 10 to 21 seconds. `fast` sends live requests instead, which skip the queue; the same day five live requests took 23 to 161 seconds, so slow is the right default and fast is for the days the queue is long. A fast run sends a lookup live only while its spending bound can carry the live price; otherwise that lookup goes through the queue without the Fast lookup fee, and the row says which speed was used (`speedUsed`).

**Memory.** With `memoryName`, a keyword, location and month range charged in the last 30 days under that name is delivered again free from the memory, with no new lookup, with the long history, AI volume and Bing figures it was bought with. The metrics history is not kept in the memory: a row delivered again from the memory comes without it and never buys it (the summary says so). A memory holds up to 30,000 keywords and 32 MB of stored figures (about 9,000 keywords with the long history add-on); past that, new rows are not delivered and not charged, and a free row (`memory_full`) says so.

### Example row

A keyword row from a run of this Actor (United States, both history add-ons, health run NDlwaFVMadCfVAQVg of 2026-10-19), with the series shortened:

```json
{
  "type": "keyword",
  "keyword": "crm software",
  "dataStatus": "ok",
  "searchVolume": 201000,
  "monthlySearches": [
    {
      "month": "2022-10",
      "year": 2022,
      "monthNumber": 10,
      "searches": 33100
    },
    {
      "month": "2022-11",
      "year": 2022,
      "monthNumber": 11,
      "searches": 33100
    },
    {
      "...": "46 more months"
    }
  ],
  "monthsWithData": 48,
  "missingMonths": [],
  "competition": "LOW",
  "competitionIndex": 4,
  "cpc": 37.13,
  "lowTopOfPageBid": 7.14,
  "highTopOfPageBid": 51.57,
  "currency": "USD",
  "history": {
    "basis": "google_ads",
    "firstMonth": "2022-10",
    "lastMonth": "2026-09",
    "monthsCovered": 48,
    "monthsWithData": 48,
    "totalSearches": 4972000,
    "averageMonthlySearches": 103583,
    "highestMonth": {
      "month": "2026-05",
      "searches": 550000
    },
    "lowestMonth": {
      "month": "2022-12",
      "searches": 27100
    },
    "yearlyTotals": [
      {
        "year": 2023,
        "searches": 489200,
        "monthsWithData": 12,
        "fullYear": true
      },
      {
        "year": 2024,
        "searches": 780000,
        "monthsWithData": 12,
        "fullYear": true
      },
      {
        "year": 2025,
        "searches": 1468500,
        "monthsWithData": 12,
        "fullYear": true
      },
      {
        "...": "2022 and 2026 (part years)"
      }
    ],
    "firstFullYear": 2023,
    "lastFullYear": 2025,
    "fullYearChangePct": 200.2,
    "compoundAnnualGrowthPct": 73.3
  },
  "trend": {
    "latestMonth": "2026-09",
    "latestSearches": 550000,
    "momChangePct": 173.6,
    "yoyChangePct": 93.1,
    "last12MonthsTotal": 2506500,
    "previous12MonthsTotal": 1298000,
    "last3MonthsVsYearBeforePct": 118.9,
    "qoqChangePct": 5.1,
    "direction": "growing"
  },
  "extendedMonthlySearches": [
    {
      "month": "2018-11",
      "year": 2018,
      "monthNumber": 11,
      "searches": 22200,
      "source": "labs"
    },
    {
      "...": "46 more Labs months, then the 48 Google Ads months"
    }
  ],
  "extendedMissingMonths": [],
  "extendedHistory": {
    "basis": "labs_then_google_ads",
    "firstMonth": "2018-11",
    "lastMonth": "2026-09",
    "monthsCovered": 95,
    "monthsWithData": 95,
    "totalSearches": 6408100,
    "averageMonthlySearches": 67454,
    "highestMonth": {
      "month": "2026-05",
      "searches": 550000
    },
    "lowestMonth": {
      "month": "2018-11",
      "searches": 22200
    },
    "yearlyTotals": [
      {
        "...": "2018 to 2026, one entry a year"
      }
    ],
    "firstFullYear": 2019,
    "lastFullYear": 2025,
    "fullYearChangePct": 311.8,
    "compoundAnnualGrowthPct": 26.6
  },
  "extendedHistoryNote": "The months before the 48 months Google Ads keeps come from the DataForSEO Labs keyword database (copied monthly from Google Ads; months older than about 2 years can differ from what Google Ads showed then). Each month names its source.",
  "metricsHistory": [
    {
      "...": "60 earlier monthly snapshots, from 2021-08"
    },
    {
      "month": "2026-08",
      "searchVolume": 165000,
      "cpc": 38.64,
      "competition": 0.08,
      "competitionLevel": "LOW",
      "lowTopOfPageBid": 6.18,
      "highTopOfPageBid": 51.72
    },
    {
      "month": "2026-09",
      "searchVolume": 201000,
      "cpc": 37.13,
      "competition": 0.04,
      "competitionLevel": "LOW",
      "lowTopOfPageBid": 7.14,
      "highTopOfPageBid": 51.57
    }
  ],
  "metricsHistoryFrom": "2021-08",
  "metricsHistoryMissingMonths": [],
  "metricsHistorySummary": {
    "snapshots": 62,
    "firstCpc": {
      "month": "2021-08",
      "cpc": 22.580321
    },
    "latestCpc": {
      "month": "2026-09",
      "cpc": 37.13
    },
    "cpcChangePct": 64.4,
    "highestCpc": {
      "month": "2023-09",
      "cpc": 124.65
    },
    "lowestCpc": {
      "month": "2024-08",
      "cpc": 20.94
    },
    "firstCompetition": {
      "month": "2021-08",
      "competition": 0.63
    },
    "latestCompetition": {
      "month": "2026-09",
      "competition": 0.04
    }
  },
  "metricsHistorySource": "DataForSEO Labs historical keyword data (monthly snapshots of the Google Ads keyword metrics, from mid 2021 on)",
  "metricsHistoryLocation": "United States",
  "metricsHistoryLanguage": "en",
  "locationCode": 2840,
  "locationName": "United States",
  "countryCode": "US",
  "dateFrom": "2022-10",
  "dateTo": "2026-09",
  "source": "Google Ads (Keyword Planner data through DataForSEO)",
  "dataAsOf": "2026-10-15",
  "retrievedAt": "2026-10-19T22:35:56.874Z",
  "fromMemory": false,
  "lookupId": "r0f978c35051ff9a97981",
  "speedUsed": "slow"
}
```

### FAQ

#### Is there a way to see the historical Google search volume of a keyword?

Yes. Every row carries the monthly searches of the last 48 months that Google Ads keeps, oldest first, with a total for every calendar year. With the long history add-on the row also carries about 8 years of months, back to about October 2018, each month naming its source.

#### How far back does keyword search volume history go?

Google Ads keeps 48 months, and the current month is never available (Google publishes a month after it ends). The DataForSEO Labs keyword database adds the older months back to about 2018-10 for the keywords it holds; the metrics history goes back to mid 2021.

#### How can I see historical CPC and competition for a keyword?

Turn on the metrics history add-on: `metricsHistory` lists the CPC, competition, competition level and top of page bids of every monthly snapshot since mid 2021, and `metricsHistorySummary` gives the first and latest CPC, the change in percent and the highest and lowest CPC month.

#### Do I get exact monthly search volumes or ranges like 1K to 10K?

Exact figures as Google Ads reports them to advertisers, for every month of the window, not the ranges Keyword Planner shows to accounts without ad spend. Google rounds its figures (for example 49,500 or 60,500).

#### Why does the search volume of a month stay the same across months?

Google Ads rounds its figures into steps (27,100, 33,100, 40,500 and so on), so a keyword whose demand moves a little shows the same figure for several months; it also gives one figure to close variants (plurals, word order), listed in `closeVariants`.

#### Why does this Actor run with limited permissions?

Least privilege: it reads only its input and writes its own dataset and key-value store. The 30 day memory is a named key-value store it creates in your account under the name you choose.

### What this Actor does NOT do

- It does not forecast. History figures, seasonality and plan ranks describe past searches; the next year can differ.
- No Google Ads month older than 48 months: the older months come only from the Labs database, with the long history add-on, labelled `labs`, and they can differ from what Google Ads showed then.
- No metrics history before mid 2021, and none in the memory (a row from the memory comes without it).
- The long series is not built for a window you shortened, for a region or city row, or with search partners on; the CSV file holds the Google Ads months only (the dataset holds the long series).
- No current month: Google Ads publishes a month after it ends, usually around the middle of the next month.
- No data for keywords Google has too few searches for, or that it does not report (some restricted topics): those are free rows with `dataStatus` `no_data`.
- It does not say what a product is. Words found in keywords are demand, never product attributes.
- Not split by device, and no click or impression forecasts.
- Bing figures only for Australia, Canada, France, Germany, the United Kingdom and the United States (and their regions and cities), in English, French or German; no Bing only runs.
- No intent classifier of its own: search intent comes from the Labs database with the insights add-on.
- The Labs add-ons cover whole countries in the languages the Labs database lists, and leave out keywords it does not hold (a free omission, not charged).
- No backlinks, domain overviews, competitor domain discovery or competitor traffic reports, no Semrush scores, no Google Trends index, and no use of your own Google Ads or DataForSEO account.
- No Standby (always on) HTTP mode: every lookup buys a data request that costs the same whatever it finds, so each call would need a fee of its own of about $0.25 or more; the run sync endpoint below gives the same rows in one HTTP response.
- Postal code locations are not offered (Google Ads returns no data for them).

### Use it through the API

The Actor works through the Apify API, as a scheduled job, and from Make, n8n or Zapier. The run sync endpoint returns the rows in the same HTTP response, which makes it a keyword search volume history API:

```bash
curl -X POST "https://api.apify.com/v2/acts/automation_craft~keyword-search-volume-history/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"keywords": ["crm software", "iphone"], "locations": ["United States"], "includeInsights": true, "includeMetricsHistory": true}'
```

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('automation_craft/keyword-search-volume-history').call({ keywords: ['crm software'], locations: ['United States'], includeInsights: true });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

```python
from apify_client import ApifyClient
client = ApifyClient("YOUR_TOKEN")
run = client.actor("automation_craft/keyword-search-volume-history").call(run_input={"keywords": ["crm software"], "locations": ["United States"], "includeMetricsHistory": True})
items = client.dataset(run["defaultDatasetId"]).list_items().items
```

### Changelog

- 0.1 (2026-10-19): first release.
- 0.1 (2026-10-21): a run the platform restarted counts the rows and charged events of the whole run in its summary, every add-on included, and reports `partial` with a note (never `complete` or `no_results`); the summary gains `rowCountsCover`. The free row of an add-on that was not bought names why and what to do.

### More data tools by Automation Craft

- [Google Keyword Search Volume & Seasonality](https://apify.com/automation_craft/google-keyword-search-volume)
- [Google Trends Scraper - Compare and Trending Now](https://apify.com/automation_craft/google-trends-scraper)
- [Google Ads Transparency Scraper - Decoded Creatives](https://apify.com/automation_craft/google-ads-transparency-scraper)
- [Google Ads Library Scraper - Competitor Ads, No Login](https://apify.com/automation_craft/google-ads-library-scraper)
- [Amazon Sponsored Products Scraper - Ads by Keyword](https://apify.com/automation_craft/amazon-sponsored-products-scraper)
- [Google News Scraper: Search, Topics, Decoded URLs](https://apify.com/automation_craft/google-news-scraper)

# Changelog

This Actor's version history is a separate document: https://apify.com/automation_craft/keyword-search-volume-history/changelog.md

# Actor input Schema

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

volume: monthly Google search volume for exactly the keywords you list. ideas: related keyword ideas for your seed keywords, each with its own monthly volume (the seeds are included). website: the keywords Google Ads finds for a domain or a page, or the keywords a domain ranks for.

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

One keyword per line. In the volume mode up to 10,000 keywords; in the ideas mode up to 200 seed keywords. Google Ads accepts up to 80 characters and 10 words per keyword, and no emoji or symbols; a keyword it would refuse is listed in a free status row instead. Example: winter jacket.

## `keywordsText` (type: `string`):

Optional: paste keywords here, one per line (commas also split). They are added after the Keywords list above. Useful for a column copied from a spreadsheet.

## `websites` (type: `array`):

Website mode only: up to 100 domains such as example.com, or with Website scope page, page links such as https://example.com/shoes/. Each website is one lookup.

## `websiteScope` (type: `string`):

Website mode only. site (the default): keywords for the whole domain. page: keywords for one page link.

## `websiteSource` (type: `string`):

Website mode only. google_ads (the default): keywords Google Ads suggests for the site or page. ranked: keywords the domain ranks for in Google search, with its ranking position, page, title and estimated traffic, from the Labs database (adds the Discovered keyword event per charged row).

## `ideasSource` (type: `string`):

Ideas mode only. google_ads (the default): Google Ads keyword ideas, up to 20 seeds a lookup. related: the searches Google shows as related to each seed, two levels deep. suggestions: longer searches that contain the seed. autocomplete: what Google suggests while typing the seed, then the seed followed by each letter. The last three add the Discovered keyword event per charged row; their keywords then get their monthly series from Google Ads.

## `maxKeywords` (type: `integer`):

Ideas and website modes: the most keyword rows with data delivered per location (default 1,000). The volume mode delivers every keyword you list.

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

Ideas mode only, optional: each seed gets its own lookup and at most this many keyword rows with data (with the Google Ads source, one lookup per seed instead of one per 20 seeds, so more Lookup events).

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

Up to 10 locations, one per line: a country (Switzerland), a two letter code (CH), a Google Ads location code (2756), Worldwide, or a region or city written the way Google Ads writes it (Zurich,Switzerland for the canton, Zurich,Zurich,Switzerland for the city). A name Google Ads does not know is refused in a free row that lists the closest names. Left out: United States (an empty list is refused).

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

Optional language: a code (de, fr, en), a name (German), a Google Ads language id (1001) or a locale tag (de-CH). Google Ads volumes do not depend on the language, so the volume mode does not send it; it narrows keyword ideas and website keywords, and picks the language of the Labs insights and of autocomplete.

## `historyMonths` (type: `integer`):

How many months of monthly searches, ending with the latest month Google Ads has published (default 48, the most Google Ads keeps). The price does not depend on it. Use either this or Date from.

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

Optional first month of the history, for example 2023-01. Google Ads keeps the last 48 months.

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

Optional last month of the history, for example 2025-12. Default: the latest month Google Ads has published (the current month is never available).

## `planningMonths` (type: `integer`):

How many upcoming months get a planning rank (default 3: the three months after the month the run starts). Each keyword row gets the historical figures for those months, and one plan row per month ranks the keywords of the run.

## `planFrom` (type: `string`):

Optional first planned month, for example 2026-12. Default: the month after the run starts.

## `minSearchVolume` (type: `integer`):

Optional: only keywords whose average monthly searches are at least this many. Keywords below it are left out and not charged.

## `includeWords` (type: `array`):

Optional: keep only keywords that contain at least one of these words or phrases as whole words (for example damen, or rain jacket).

## `excludeWords` (type: `array`):

Optional: leave out keywords that contain any of these words or phrases as whole words (the same matching rule as Must contain words).

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

Ideas and website modes with the Google Ads source only: the order that decides which ideas fill Maximum keyword ideas (default relevance). CPC reads the whole Google Ads answer and keeps the ideas with the highest CPC.

## `mergeCloseVariants` (type: `boolean`):

Google Ads gives one combined figure to close variants (plurals, word order). A group is keywords with the same average, the same figure in every month and the same words in any order, a word and its regular plural with s, es or ies counting as one. Every row lists the variants of the same lookup that share its figures. With this on, the run delivers and charges one keyword of each group: the first in the order the run fills its rows (your list, keywords from your memory first; for ideas and websites the order of Google's answer, or the CPC order with Order of keyword ideas set to CPC). Every other member, in the same lookup, another lookup of the run or the memory, is left out before any maximum counts it, free; a free row names the positions in your list of members left out after an earlier lookup. It works with your own list, Google Ads keyword ideas and Google Ads website keywords; with related searches, suggestions, autocomplete or ranked keywords the run is refused, free (their candidates are counted before their figures are known).

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

Add the searches of Google's search partner sites to the volumes (default off: Google search only).

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

Let Google Ads return adult keyword ideas (default off).

## `speed` (type: `string`):

slow: lookups go through the queue, usually ready in 10 to 60 seconds. fast: live lookups that skip the queue (on 2026-10-18 they took 23 to 161 seconds, longer than the queue; worth it on days the queue is long); each paid live lookup adds one Fast lookup event.

## `includeInsights` (type: `boolean`):

Optional add-on per charged keyword row: up to about 8 years of monthly searches (back to about 2018-10, where the Labs database holds older months for the keyword) as one series, the Google Ads months plus the older months from the DataForSEO Labs keyword database, each month naming its source, with yearly totals over that series; and keyword difficulty (0 to 100), search intent, SERP features, Google result count and categories, by country. Charged as one Keyword insight event per row that gets it. The longer series is built for a whole country, with search partners off and the default window (the 48 months Google Ads keeps, to the latest month); a region or city row gets its country's insights and no longer series.

## `includeMetricsHistory` (type: `boolean`):

Optional add-on per charged keyword row: the month by month history of CPC, competition, top of page bids and average searches, from the monthly snapshots the DataForSEO Labs database has kept since mid 2021, by country, with a short summary of how CPC and competition moved. Charged as one Metrics history event per row that gets it. Not kept in the memory: a row served again from the memory comes without it.

## `includeAiVolume` (type: `boolean`):

Optional add-on per charged keyword row: how often the keyword is asked in AI tools, with 12 months, by country. Charged as one AI search volume event per row that gets it.

## `includeBing` (type: `boolean`):

Optional: Bing search volume, CPC, competition and 24 months for the charged keyword rows, where Bing has data (Australia, Canada, France, Germany, the United Kingdom and the United States). Each Bing request that delivers figures is one Lookup event.

## `memoryName` (type: `string`):

Optional name of a memory kept in your account. A keyword, location and month range charged in the last 30 days under this name is delivered again free from the memory, with no new lookup (add-ons come along when they were bought for the same language and country). Use the same name for retries and repeat runs; runs that share a name must not overlap. Up to 30,000 keywords per name and 32 MB of stored figures (about 9,000 keywords with the long history add-on); past that, new rows are not delivered and not charged, with a free row that says so.

## `refreshMemory` (type: `boolean`):

With a memory name: look every keyword up again (charged) and store the new figures, instead of serving the stored ones.

## `spendingLimitUsd` (type: `number`):

Optional: the run stops before its charges would pass this amount (the start fee included). The run option Max total charge works the same way; the lower of the two applies.

## `maxRunMinutes` (type: `integer`):

Optional: after this many minutes no new lookup starts; the run delivers what it has and ends with its summary.

## Actor input object example

```json
{
  "mode": "volume",
  "keywords": [
    "winter jacket",
    "crm software",
    "iphone"
  ],
  "locations": [
    "United States"
  ],
  "speed": "slow",
  "includeInsights": true,
  "includeMetricsHistory": true
}
```

# Actor output Schema

## `items` (type: `string`):

No description

## `monthly` (type: `string`):

No description

## `planner` (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": [
        "winter jacket",
        "crm software",
        "iphone"
    ],
    "locations": [
        "United States"
    ],
    "includeInsights": true,
    "includeMetricsHistory": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation_craft/keyword-search-volume-history").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": [
        "winter jacket",
        "crm software",
        "iphone",
    ],
    "locations": ["United States"],
    "includeInsights": True,
    "includeMetricsHistory": True,
}

# Run the Actor and wait for it to finish
run = client.actor("automation_craft/keyword-search-volume-history").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": [
    "winter jacket",
    "crm software",
    "iphone"
  ],
  "locations": [
    "United States"
  ],
  "includeInsights": true,
  "includeMetricsHistory": true
}' |
apify call automation_craft/keyword-search-volume-history --silent --output-dataset

```

## MCP server setup

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

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/nXuKeB46La1JmuTWB/builds/4FiGkRDF6LPaoUGHG/openapi.json
