# Changelog of Keyword Search Volume Scraper — Google Keyword Planner, CPC (`steadyfetch/keyword-search-volume-scraper`) Actor

- **URL**: https://apify.com/steadyfetch/keyword-search-volume-scraper/changelog.md
- **Full Actor documentation**: https://apify.com/steadyfetch/keyword-search-volume-scraper.md

## Changelog

### 1.0.71 — 2026-09-19

- **The whole "Maximum cost per run" you set is now spent on keyword rows.** Before this build the run held back about a tenth of your cost cap before it started — a cushion for platform running costs which, on this actor's pricing, your cap does not pay for at all: your cap buys keyword rows, and the running costs are ours. So the cushion was simply cap you had asked to spend and could not get. On a run capped at $0.10 with keyword rows at $0.002 the plan asked for 45 keyword rows where the cap paid for 50 — 5 keyword rows of your own cap, unspendable by construction. From this build the plan uses your cap in full, so a run asks for as many keyword rows as your cap can really pay for. Nothing you are charged changes, and nothing can be charged above the cap you typed: every charge is still checked against what is left of your cap at the moment it is posted, and a run that reaches your cap still stops cleanly, ships what it had already fetched, and tells you what is left. No price, input field or output column changes.

### 1.0.70 — 2026-09-19

**Internal accounting only — nothing changes in your rows, prices, charges, input fields or status line.** When the Country box names a market the search-volume source carries no data for, or asks for a whole region rather than one country, our own run record now notes which of the two it was. It is a count, never the value you typed; your run, your rows and the wording you already see are exactly as before.

### 1.0.69 — 2026-09-19

- **The run summary now counts the keywords it refused as part of what you asked for.** A keyword this actor refuses before it looks anything up — one over 80 characters, one over 10 words, or one carrying a character Google Ads does not accept — has always got its own uncharged row saying why. It was left out of the "requested" figure all the same, so a list of 81 keywords with 10 of them refused read "Delivered 31 keyword rows from 71 requested keywords" on the run page and in the summary row, while the dataset underneath it held 81 rows. The number now says 81: every keyword you sent is counted as asked for, whether it was delivered, came back with no data, was refused, or was left for a later run. That makes the summary reconcile against the dataset row for row — delivered plus every kind of miss equals requested — so the invoice and the row count can be checked against each other without arithmetic that never closed. Nothing else moved: the same keywords are refused for the same reasons with the same wording, the same rows are delivered, and the per-keyword price, the $0.19 fresh-lookup fee and the two cases that waive it, the input fields, the output columns and the charged events are all exactly as they were.

### 1.0.68 — 2026-09-19

- **One keyword Google Ads will not accept no longer takes the rest of your list down with it.** Search volume is fetched for your whole list in one batch, and Google Ads refuses an entire batch when a single keyword in it carries a character it does not allow — so a list of 81 keywords with a dozen commas, percent signs, parentheses and a semicolon among them came back with nothing at all: every row said "Google Ads will not accept this keyword", including the 69 keywords Google Ads would have answered for. From this build those characters are caught before anything is bought. A keyword carrying one gets its own uncharged row naming the exact character it tripped on, and every other keyword in your list is looked up and delivered as normal. The characters Google Ads does not accept inside a keyword are the comma, exclamation mark, at sign, percent sign, caret, parentheses, asterisk, equals sign, braces, semicolon, tilde, backtick, angle brackets, question mark, backslash, vertical bar and horizontal bar, plus emoji and other 4-byte characters; full stops, hyphens, plus signs, ampersands, hashes, apostrophes, quotes, colons and slashes are all accepted and are looked up exactly as they always were. And when a batch is still refused for a reason none of those explains, the run now narrows it down instead of blaming every keyword in it: it splits the list, finds the keyword the source actually refuses, delivers the rest, and where it cannot narrow it down within a bounded number of tries the row says the batch was refused rather than naming your phrase as the cause. Refused rows also carry the source's own status code now, so which refusal happened is visible on the row itself. Nothing about what you pay changed: the per-keyword price, the $0.19 fresh-lookup fee and the two cases that waive it, the input fields, the output columns and the charged events are all exactly as they were, and a keyword that is not looked up is never charged.

### 1.0.67 — 2026-09-18

- **The Country box now reads what people actually send, and a value it cannot place runs on the default market instead of refusing the whole list.** Three spellings that used to be refused are read now, and each of them lands on a market the bundled country table already carries, so nothing is ever invented: a locale tag is read for its market half, since the language half is what the Language field already carries (`en-GB` is the United Kingdom, `pt-BR` is Brazil, and `US-CA` is still not Canada); a comma list runs on the first country it names, so `germany, france` and `Dubai, United Arab Emirates` both work; and the value we could not read before now says which market the run used. Where nothing at all can be placed, the run no longer returns you an empty dataset: it uses the default market, United States, and says so three times over — an uncharged first row naming both the value you sent and the market used, the same sentence on the run page, and the market itself in the `country` column of every delivered row. A bare number that is not a country code gets its own sentence, because it is not a typo: that is a Google Ads target for a place inside a country — a city, a region, a postal code — and this actor reports volume one whole country at a time, so the row tells you to send the country's own code or name. Two values are still refused outright, uncharged and one row per keyword, because a default there would be worse than nothing: a market the source carries no data for at all (returning a different country's numbers to someone who named a real one is the one thing this field must never do), and a whole-region ask like `worldwide` or `EU`, where picking a single country for you is exactly the substitution you did not ask for. A Country box longer than any country name is refused too — that is a pasted block, not a market. Nothing about what you pay changed with this: the per-keyword price, the fresh-lookup fee, the input fields, the output columns and the charged events are all as they were, the notice row is uncharged like every other notice, and a keyword that is not looked up is still never charged.

- **Fresh search-volume lookups now have a daily allowance on the Apify free plan.** On a free Apify plan this actor buys at most 2 fresh lookups from the search-volume source per account per day. Everything you have already paid for is untouched by it: a keyword your account already had inside 30 days is still handed straight back uncharged, a keyword already in our own 30-day cache is still delivered and charged exactly as before, and the run still finishes successfully either way. Past the allowance a keyword that would have needed a brand-new lookup is simply not bought — it ships as its own uncharged row saying so, `resumeCursor` lists it so a later run picks it up where it stopped, and the run page says the allowance is used and that the rest comes back the next day or on any paid plan. If the run's API token cannot open your account's key-value store the daily count cannot be read at all, so that run keeps a single-run allowance of one fresh lookup and the rows and the run page say so too: a count we cannot read never becomes an unlimited allowance, and never becomes a refusal either. Paid plans are not affected in any way — the allowance does not exist on them — and a run whose plan this build cannot read is treated as a paid one. Nothing about what you pay changed here: the per-keyword price, the $0.19 fresh-lookup fee and the two cases that waive it, the input fields, the output columns and the charged events are all exactly as they were, and a keyword that was not looked up is never charged.

### 1.0.66 — 2026-09-18

- **The fresh-lookup fee is live, and every page now says so in the present tense.** The README, the store listing and the input form had all announced it as a change still to come: a run that buys fresh search-volume data pays one fresh-lookup fee of $0.19, once per run whatever the size of the list. It is being charged now, so the wording that put it in the future is gone and each of those pages states the fee plainly where you decide to run it. Nothing about what you pay moved with this build: the per-keyword price is unchanged, the fee is still waived in full on a run answered entirely from the 30-day cache and on a fresh lookup that comes back with no data for any keyword, a keyword Google has no figures for still costs nothing, and the run option Maximum cost per run still needs $0.25 to cover one fresh lookup plus one keyword row. One more sentence on the input form was reworded for the same reason: with a second priced event, "one charged unit = one keyword" was an equality that no longer held, so it now says that each keyword that comes back with Google Ads data is charged once — which is what it always did.

### 1.0.65 — 2026-09-16

- **A country or language this actor cannot place now shows you the value you sent beside the values that work — on every row and on the run page.** Only one row ever carried that: the field row at the top of the dataset named your value and listed the spellings that resolve. The rows underneath it — one per keyword, the ones a dataset opens on — said only that "the country you set could not be matched", naming neither what you typed nor what to type instead, while the run page closed by promising that "each row names the accepted values". From this build every one of those per-keyword rows carries your own value in quotes, in a new `input` column beside the field name, and — where the value is simply one we cannot read — the accepted forms in full: a two-letter or three-letter country code, the country's English name, or its Google Ads location code. The run page names your value too, so a typo is visible without opening the dataset at all, and a value too long for the page is shortened rather than pushing the accounting off the end of the line. The two other answers keep their own wording: a country the source publishes no data for still says so and names the nearest markets that do, and a worldwide or region-wide ask still says volume is reported one country at a time. The Country field's own description now says the Google Ads location code works, that there is no worldwide figure, and that an unplaceable value refuses that field only. Nothing about money moved: the same keywords are refused, still uncharged, and no price, input field, output column or charged event changed.

### 1.0.64 — 2026-09-15

- **"Maximum run seconds" is now really the limit — a lookup already in flight can no longer run past it.** The setting bounded when a new lookup could START; nothing bounded how long one could RUN. A request begun with nine seconds of a thirty-second limit left was still handed twenty seconds of transport, so a run could close more than a third past the time you set — and on every window the LAST lookup of a run was given the whole remaining clock, leaving none of it for the summary the run owes you. From this build each request to the search-volume source gets the smaller of its own timeout and the time your run actually holds above what the closing summary needs, and a lookup your remaining time cannot cover is not started at all: those keywords ship in `resumeCursor` under "stopped before the run timeout — raise maxRunSeconds", uncharged, exactly as any other time stop already reads. Runs with time to spare are unchanged in every respect — the same requests, the same timeouts, the same rows — and no price, input field, output column or charged event changed.

### 1.0.63 — 2026-09-14

- **A token that can READ your keyword memory but not WRITE it was quietly costing you full price on every next run — the run now says so, and says what to grant.** The memory that stops you paying twice for the same keyword lives in a key-value store in your own Apify account, and an API token limited with "Restrict what Actors can access using the scope of this Actor" can carry key-value store Read without Write (or Write without Create on a store your account does not have yet). Such a run opened the memory, handed back every keyword you already had correctly — and then saved nothing, so your NEXT run bought the same Google figures all over again at full price, with no sign of it anywhere. From this build the very first refused write is named on the run page, in one uncharged row written the moment it happens, and in the run log beside Apify's own message, each saying to give the token key-value store Write (and Create) under Settings → API & Integrations or set Actor runs to Full access. A run whose memory saves normally is unchanged in every respect, a passing network hiccup on a write is still just a hiccup, and no price, input field, output column or charged event changed.

- **A run started with a scoped API token could be charged twice for the same keyword, and nothing told you why — it now says so, and says exactly what to grant.** The check that hands back a keyword your account already paid for lives in a key-value store in your own Apify account (`kw-volume-account`). An API token limited with "Restrict what Actors can access using the scope of this Actor" cannot open that store unless it carries key-value store **Read, Write and Create** permission, so runs started from the API with such a token silently lost the check and paid full price again for figures Google had not changed — while the run page said only that it "could not check what you already had". From this build the run names the cause and the fix in three places: on the run page, in one uncharged note row written before any lookup (so an agent reading rows never misses it), and in the run log beside Apify's own message. Nothing changes for a run started from the console or with a full-access token, no price, input or delivered column changes, and every other reason the check can be unavailable keeps the wording it had.

- **A run that held keywords back under your cost cap no longer says it stopped at your maximum total charge when it charged you nothing.** This actor reserves its charge slots out of `maxTotalChargeUsd` before it buys anything, so a cap can hold the last few keywords of a list back on a run that then charges $0.00 — every keyword it did look up came back with no published volume, or your account already had them. The run page used to answer that with "stopped at your maximum total charge — raise it", which reads as though the cap had been spent. It now says how many keywords were held back and which cap held them, for example "1 keyword held back under your $0.12 cap — raise it", and makes no charge claim. A run that really did charge up to your cap still says so, unchanged. The `_summary` row carries the same sentence and still lists the held keywords in `resumeCursor` so you can re-run just those. No price, no input field, no output column and no charged event changed.

- **A field name this actor does not read no longer disappears in silence.** Send `countryCode`, `locations` or any other name the actor does not declare alongside real keywords and the platform passes it straight through — until this build the run looked perfect and quietly ignored it, so a caller (or an AI agent) had no way to learn the market, filter or limit they asked for was never applied. Runs now carry one uncharged row naming the fields you sent, saying they were ignored and nothing else about the run changed, and listing every field this actor does read with its form label. It refuses nothing and stops no keyword: the same rows are delivered and the same keywords charged as before, and a run that carries the row is still a clean run. A name the actor already understands as a keyword field — `keyword`, `searchTerm`, `query` and the rest — is read as before and is never reported here, and a run with no keywords keeps the answer it already gave, so nothing is said twice. No price, no input field, no output column and no charged event changed.

### 1.0.62 — 2026-09-13

- **A run that names the keywords field in the singular now looks up your keywords, instead of ours.** If you — or an AI agent calling this actor — send `keyword`, `searchTerm`, `searchTerms`, `term`, `query` or another obvious name for the same thing, those keywords are now read and looked up as though you had used this actor's own field name, `keywords` (and `keywordText` lands in the paste box, `keywordsText`). The run log says which name you sent and which field was read. Until this build the platform passed the unrecognised name straight through and the run treated your input as empty: it looked up the two example keywords and charged you for them, which is the wrong answer for someone who had named their own. If you set both names, everything you typed is kept and a keyword sent twice is still charged once. Sending one of those names with nothing in it now says so, naming the field you set and the field this actor reads, instead of running the sample. Nothing about money moved: no input field, output column, event or price changed, the form still shows `Keywords`, and the country, language, network and mode you set are read exactly as before.

### 1.0.61 — 2026-09-13

- **The page now shows a full delivered row, including the whole 12-month history.** The Output section described the columns and never showed one; it now ends with a complete row from a live run — volume, CPC, competition, the bid range, every month of the trend and the charge record — so you can see the exact shape before you spend anything. No price, no input, no output column and no charged event changed.

### 1.0.60 — 2026-09-13

- **The store page now says how often these numbers actually change, and what a second run of the same list gets you.** Google refreshes Keyword Planner volumes monthly, so a new section says that monthly is the cadence worth scheduling, that a weekly run of the same list inside the 30-day window is charged nothing for those keywords but also brings back nothing new, and how to set the schedule up: save the input as a Task, put the Task on an Apify Schedule, and add an integration or a webhook on run succeeded. It also names `dataAsOf` as the field that tells you whether a run brought a new month of data. Nothing else moved: no input field, output column, event or price changed, and a run behaves exactly as before.

### 1.0.59 — 2026-09-12

- **A keyword you asked for ideas on, that the source has no ideas for, now comes back with a row saying so instead of an empty run.** When you sent a keyword in "keyword ideas" mode and the source answered with nothing at all for it, the run finished with no rows about that keyword, nothing on the status line about it, and — the part that mattered — a run record that said you had asked for nothing. You now get one uncharged row per keyword saying the source answered and listed no ideas for it, that this is a gap in what Google publishes rather than an error, and that a broader head term is the thing to expand instead; the status line says the same in one clause, and the run's own record now carries the keyword you actually sent. Nothing was charged for those runs before and nothing is charged for them now — including the fresh-lookup fee that starts on 16 September 2026, which a run that delivers no rows never pays. That row also marks the keyword as completed, so a restarted run still never repeats an expansion you already paid for.
- **On a run that could not read its own delivery record, the status line no longer says duplicates were "charged once" while the same line says nothing was charged.** The two sentences contradicted each other; the charge statement is now made by whichever clause is entitled to make it, exactly as the rest of that line already worked. Nothing about what you are charged has changed. This also bought back the room the widest status line was over the run page's own 500-character cut with, measured at 502 characters on a 1,000-keyword run where every kind of outcome appeared at once — past that point the run page truncates, and what it truncates is the accounting and the support ask at the end.
- **A run that was interrupted and restarted now reports the keywords its first attempt already delivered.** An ideas run that was moved to a new machine part-way through reported the rows it had delivered but reported the ask behind them as zero. The rows, the charges and your data are unchanged — only the run's own summary of what was asked for was wrong, and it now matches what was delivered.

### 1.0.58 — 2026-09-12

- **An aborted or migrated run now leaves its receipt from the first moment of the run, instead of only after start-up finishes.** The record that says how a run ended was only put in place once the run had finished starting up — reading your input, opening its stores, working out what a previous attempt had already delivered and charged. A run stopped inside that window ended with nothing written about it at all, which mattered most on a re-started run, where the thing not written about was the earlier attempt's work. It is now in place from the run's first moment. Nothing else moved: no input field, output column, event or price changed, and a run that reaches its work behaves exactly as before.

### 1.0.57 — 2026-09-12

- **This actor has a new title on the store: Keyword Search Volume Scraper — Google Keyword Planner, CPC.** Same actor, same id, same URL, same input, same output, same price — only the words on the listing changed, so nothing you have saved, scheduled or wired into an integration needs touching. The title now carries the phrase buyers actually search for, which is the only reason it moved.
- **The input page now spells out, in one place, what to send and what a run costs.** The description at the top of the input now opens with a working example call, says that one charged unit is one keyword that comes back with Google Ads data ($0.008 on the Apify free plan, from $0.002 on paid plans — about $4 / $1 for 500 keywords), repeats that a keyword Google has no volume for is never charged so a whole list is safe to send in one run, names the fresh-lookup fee that starts on 16 September 2026 and the maximum cost per run to set for it, and says plainly that `_demo` is the sample switch rather than a keyword. The Keywords field now leads with the value shape and an example instead of the console sample instruction. This is the screen an AI agent reads before it calls this actor — it never sees the store page.
- **One wording fix on the price line in the FAQ:** it now says "the Apify free plan", which is the name Apify uses and the one the rest of this page already used. Your price, the fresh-lookup fee and every figure on the page are exactly as they were.
- **A limit outside this actor's range no longer refuses to start the run.** "Max keyword ideas per keyword", "Max keywords to expand", "Max keyword rows for the whole run" and "Max run seconds" each had their range enforced by the input form itself, so `maxItems: 200000`, or an AI agent guessing `maxRunSeconds: 10`, did not start a run at all: you got a validation error, no rows, no status line and nothing to look at. The ranges are unchanged — up to 2,000 ideas per keyword, 20 keywords expanded in one lookup, 100,000 rows for a run, and a run window between 30 seconds and an hour. What changed is what an ask outside one of them does: the run now starts, uses the nearest end of the range, writes one uncharged row saying what you asked for and what bound it, and names it on the run's status line. Your defaults are unchanged, and nothing about what a run costs moved.
- Nothing else moved: no input field, output column, event or price changed.

### 1.0.56 — 2026-09-11

- **A maintenance build: nothing about your runs changes.** The top of this actor's store page now carries the one-click MCP pin, this actor's id, the one required input field with a real example, and the field that caps what a run can spend. An AI agent reads only the first part of a listing, and until this build all four sat far enough down the page that one never reached them. Every word about price, input and output is unchanged.
- **The private run-report this actor writes for our own support** — counts only, never anything you typed — now also records whether a run that stopped at the row limit you set had actually filled it, and the per-event prices the run was billed at. Until now a run stopped by your own limit looked the same to us as one that simply ran out of keywords.
- **Charges are unchanged.** No price, event, output column or charge moved: only delivered keyword rows are charged, a phrase with no published volume is uncharged, a keyword the source did not answer for is uncharged, and a keyword this account already had is still handed back uncharged.

### 1.0.55 — 2026-09-10

- **The summary row no longer tells you to re-run with keywords that are not there.** When a run stopped early — on the cost cap, the time limit or the task cap — the last row always closed by telling you to re-run with the remaining keywords to pick up where it left off. It said that even when nothing was left: if the stop landed just as the final keyword was answered, the same row's `resumeCursor` was empty and the instruction pointed at nothing. That sentence now appears only when there really are keywords still to do, and it names `resumeCursor`, the field on that row which lists them. A run that finished everything is unchanged, and so is what you were charged.

### 1.0.54 — 2026-09-09

- **Two runs started at the same time no longer erase each other's memory.** The account's repeat memory is now merged on every write, so an item one run delivered stays remembered, and a later re-run hands it back instead of charging it again.
- **Patience is now priced.** A refused lookup is waited out only for as long as the keyword results it could still recover are worth, at the price and the memory this run actually has. The first pause is shorter, and a run on a short time limit now gets patience it never got before instead of none at all. A run that has delivered nothing still gets one pause whatever its ask is worth.
- **Charges are unchanged.** No price, event, output column or charge moved: only delivered keyword rows are charged, a phrase with no published volume is uncharged, a keyword the source did not answer for is uncharged, and a keyword this account already had is still handed back uncharged.

### 1.0.53 — 2026-09-09

- **A "Max run seconds" of 30 now actually buys you a lookup.** The setting offers anything from 30 seconds upward, but the run kept a flat 20 seconds back to write its summary — leaving ten working seconds against a lookup that measures about 17. The time kept back is now a quarter of the window you set, so a 30-second run has 22 working seconds and one real lookup fits. Nothing changes at 80 seconds or above, the 900-second default included: those runs keep the same 20 seconds they always did.
- **A run can no longer overrun the time limit you set while it waits on a slow source.** Every request to the search-volume source was given up to two minutes to answer, whatever your limit was, so a hanging request could run a 30-second run for two minutes. A request is now given no more time than your run has left, and never less than one measured lookup needs. A run with more than two minutes left is unaffected.
- **A phrase with no published volume no longer tells you why.** The row used to say Google "groups close variants under a broader term" — one reason stated as the reason. All we can actually see is that the figure came back blank, and there are at least three ways that happens: it may be grouped under a broader term, sit below what Google reports, or be something Google simply has nothing for. The row now says what we saw and offers the three, instead of picking one. The advice is unchanged, because it works under all three: try the head term, or a nearby larger city.
- **A keyword the source answered about but returned nothing for no longer reads as an outage.** When a lookup comes back successfully and simply does not carry one of your keywords — and the extra ask for it comes back short too — the row said "the search-volume source was unavailable". It answered; it just had nothing for that keyword. The row now says that. A request that really did fail still reports as unavailable, exactly as before.
- **Charges are unchanged.** No price, event, output column or charge moved: only delivered keyword rows are charged, a phrase with no published volume is uncharged, a keyword the source did not answer for is uncharged, and a keyword this account already had is still handed back uncharged.

### 1.0.52 — 2026-09-07

- **Keyword ideas now cost the same whether you expand one keyword or twenty.** Your keywords used to be sent for expansion one at a time, as a separate paid lookup each, while the search-volume source accepts twenty keywords in a single lookup and charges for the lookup rather than the keywords in it. They are now expanded together in one lookup. What you receive does not get smaller for it: a live check confirmed the same suggestions come back either way, and the run's idea ceiling is now filled from one list sorted by search volume, so a keyword with few suggestions no longer strands its share of the ceiling. You get at least as many ideas as before, and often more.
- **"Max keyword ideas per keyword" is now the run's ceiling divided by the keywords you expand.** Three keywords at 200 is up to 600 ideas for the run, taken from the highest-volume suggestions across all of them; it used to be applied to each keyword on its own. Your row cap and your maximum total charge still stop the run exactly where you set them.
- **An idea row no longer names one keyword it came from.** Google returns the suggestions as a single list and does not report which of your keywords produced which idea, so "From keyword" is now empty on an idea row and a new "Ideas expanded from" column lists the keywords it was expanded from. Nothing is guessed. "From keyword" is still empty on your own keyword rows, exactly as before.
- **A run whose maximum total charge cut the keyword ideas short now says so** instead of reporting as a completed run, and names the setting to raise.
- **The receipt row that marks a keyword as expanded is written even when your row cap ends the run.** Each keyword you expand gets one uncharged receipt row saying so. A run that filled its row cap with keywords this account already had could finish without them, leaving the expansion unrecorded in your dataset.
- **That receipt row also counts the ideas you actually received.** It counted only the ideas that were charged for, so a run answered entirely from keywords this account already had said the lookup returned none while the rows were sitting in the dataset.
- **Charges are unchanged.** No price, event or charge moved: only delivered keyword rows are charged, a phrase with no published volume is uncharged, and a keyword this account already had is still handed back uncharged.

### 1.0.48 — 2026-09-07

- **The names shown on the form for "What to return" and "Network" now select the option they label.** Both fields offer a choice, and the wording the form displays for the non-default option in each — "Keyword ideas from my keywords" and "Google search and search partners" — was not recognised, so typing what you saw quietly used the default instead: you asked for keyword ideas and received metrics, or asked for partner volumes and received Google-only ones. Both now select what they name, along with several everyday variants.
- **Everything else about these two fields is unchanged.** A value neither of them recognises still uses the documented default and tells you so on its own uncharged row, and your keywords and market are still looked up exactly as you asked — that is the behaviour described in the note sent on 4 September and it has not been taken back.
- Nothing else moved: no price, event, output column or charge changed, and only delivered results are charged.

### 1.0.47 — 2026-09-06

- **A block of links pasted into the keywords box now comes back with an uncharged row saying so.** "Keywords" looks up search volume for words. A whole block of links pasted into a single row used to be sent to the search-volume source as one keyword — a paid lookup for something nobody searches. It is now answered before anything is looked up with a single uncharged row that says how many links it found, and it points at "Keyword list (paste)", which does split a pasted list on lines and commas.
- **Your search text is never split apart.** Only you know whether "red running shoes" is one search or three, so nothing is guessed — a row holding words rather than links runs exactly as before.
- Nothing else moved: no price, event, output column or charge changed, and only delivered results are charged.

### 1.0.46 — 2026-09-06

- **Rows this actor had already paid for are no longer thrown away when a run's time limit ends.** A keyword any earlier run looked up is answered from this actor's own 30-day store of Google's figures, with no new lookup at all. The run's clock was being checked between reading one of those answers and writing it to your dataset, so a run that ran out of time could deliver nothing while the answers were already in hand — nothing delivered, nothing charged, and the money for those lookups already spent. The clock now stops the run from looking anything NEW up, exactly as before, and the answers already in hand are still written and charged. A keyword-ideas expansion behaves the same way: once the expansion has been bought, its suggestions reach you. Your own settings are unchanged — your row cap and your maximum total charge still stop a row, because a stored answer is charged like any other, and a run close enough to the platform's own timeout still stops in time to write its summary row.
- **The head term suggested for a phrase with no published volume is now a whole phrase.** When Google publishes no volume for your exact wording, the row offers a broader phrase built from your own words by dropping the trailing place. A place written in two words was only half dropped — "physical therapy north county san diego" suggested "physical therapy north county san", and "physical therapy solana beach" suggested "physical therapy solana" — and a phrase that ends in no place at all was cut the same way, so "BJJ injury physical therapy" suggested "BJJ injury physical". The cut now recognises the words that open a place name (san, santa, los, new, fort, saint, lake, port and the rest) and the generic ones that close it (beach, city, county, springs, park and the rest), and takes the whole name off; a phrase whose ending is not a place keeps its own two-word head term instead. Nothing is looked up to do it, and the suggestion is still made only from the words you typed.
- **Charges are unchanged.** A phrase with no published volume is uncharged, a keyword that was not delivered is uncharged, and only delivered keyword rows are charged.

### 1.0.45 — 2026-09-06

- **A keyword Google publishes no volume for now tells you what to try instead.** The row used to say the phrase was "too rare to report or grouped under a similar phrase", which pointed at rarity that is usually not there: Google publishes volume per phrase, and its gaps are per phrase too — on one US/English lookup "dentist near me" comes back at 1,830,000 and "physical therapy san diego" at 1,600, while "physical therapy near me" and "physical therapy carlsbad" come back empty. The row now says Google publishes no search volume for that exact phrase, and names the head term to try — your own phrase without its trailing city or "near me" — plus the other move that works, a larger nearby city.
- **The run's status line and its summary row count those phrases apart.** They used to sit inside the general list of things that went wrong, beside rate limits and source outages, where a re-run reads like the fix. They now have their own sentence — "N phrases have no published volume — try the head term or a nearby city" — so a run that delivered nothing says why in the first line you read.
- **The README says it too.** A new "three things about Google's numbers" point spells out that the gaps are per phrase and not per topic, with the measured examples and the head-term advice.
- **Charges are unchanged.** A phrase with no published volume is not charged, was never charged, and the run still finishes successfully.

### 1.0.44 — 2026-09-05

- **A country this actor cannot look up is now counted against every keyword it stopped.** When the country (or language) you set is not one the search-volume source reports on, nothing can be looked up and every keyword you sent gets its own uncharged row saying so. The run's own record of the outcome counted that refusal ONCE, whatever the size of the list — so a run of eight keywords reported one problem and seven keywords named by nothing. The refusal now counts the keywords it stopped. Your rows and the status line are unchanged.
- **Keywords a stop never reached are now named in the run's record.** When your row cap or budget fills, the run's clock runs out, this actor's per-run source-request limit is reached, or a run is moved to another server, the keywords left over were only listed in the resume cursor. They are now counted in the record as well, so nothing the run planned goes unexplained.
- **Keyword IDEAS are now counted as what they are.** In "ideas" mode the record compared the many suggestion rows delivered against an ask of one seed keyword, which never added up. The ask for a seed is now the suggestions its expansion actually returned.
- **A setting that could not be read and fell back to its default is reported as a note, not as a missing keyword.** An unreadable "network" or "search mode" still gets its uncharged row naming the field and the accepted values, and the keyword itself is still delivered — it is simply no longer counted as work the run did not do.
- **Charges are unchanged.** Nothing is charged for a keyword that was not looked up, and only delivered keyword rows are charged.

### 1.0.43 — 2026-09-05

- **A run that waits now always keeps room to finish and report.** When the search-volume source refuses a request on every try, the run can pause and submit it once more — but the check that decided whether a pause was affordable kept back only half a minute for that second submission, while a submission is given two whole minutes. On a run whose time limit was short, the pause was allowed and the submission after it then ran past the end of the run, so the honest uncharged rows it was taken for never landed. The check now keeps back a whole submission, measured from the source's own request limit, so a pause is only taken when the run can finish the submission the pause was for. A run with room to wait waits exactly as it did before.
- **Charges are unchanged.** Waiting is uncharged, a refused request is uncharged, and only delivered keywords are charged.

### 1.0.41 — 2026-09-05

- **A run started with no input at all now has room to wait out a refusal.** Press Start on the untouched form and this actor looks up two example keywords, charged like any run. The search-volume source sometimes refuses every request for a few minutes at a time, and one round of tries could use up most of the sample's own two-and-a-half-minute limit — so the wait-and-try-again this actor gained in the last build could never happen on the most common first run of all: it reported "please re-run" while the refusal was still lifting. The sample's time limit is now seven minutes, sized to hold one full round of tries, one pause, one more submission, and enough time left over to stop cleanly and report. A sample the source answers still finishes in seconds; a re-submitted request costs nothing extra, because the source bills a request it answers and never one it refuses; and nothing is charged unless rows are delivered.
- **How much of its remaining time a refused run may spend waiting now follows the size of what you asked for.** A run with more requests still to send keeps exactly the limit it had — no more than half the time left — so a wide refusal still leaves the later requests their share of the run. A run with a single request to send may spend what is left on its one pause, because nothing else is waiting for that time.
- **The no-input sample now always looks the keywords up.** It used to be answered from this account's own history of past runs, so a second Start handed the first one's figures straight back. The sample now ignores that history in both directions: it looks the keywords up every time and does not add its own to the history. A run with your own keywords in it is unchanged — a keyword this account already paid for inside the 30-day window is still handed back to you, not charged.

### 1.0.40 — 2026-09-05

- **A data request the source refuses is now tried again after a short wait, instead of ending as "please re-run" while most of your run's time is still unused.** When the search-volume source rate-limits a request or is briefly unavailable, this actor already retried it a few times over about twenty seconds; after that, every keyword in that request came back as an uncharged row with a re-run note — even on a run that still had many minutes left. Now, when the run still holds the time and the keywords waiting on that request are worth the idle minutes, it waits 90–180 seconds and submits the same request once more. A refused request costs nothing at the source, so the wait is the only cost, and it is ours. A keyword answered on the second try is delivered and charged exactly as if the first try had answered.
- **What still stops at once:** a keyword Google Ads will not accept, a keyword with no data, and a problem with our own account at the source. None of those change in three minutes, so none of them wait.
- **A short run never waits.** A run at or near its time limit, and the two-keyword sample, still get the single request they always did, and the honest rows ship inside the run's own time.
- **The rows and the summary say what happened.** A keyword still refused after the wait keeps its uncharged row, now with "We tried again after waiting N minutes in total across 2 passes; it was still refused." before the charge sentence. The run's `_summary` row carries `patience` (requests refused, waits, seconds waited, requests recovered) whenever a request was refused, and the run log says how many requests the wait recovered. Nothing about what is charged changes: only delivered keyword rows are charged, and a wait is never charged.

### 1.0.39 — 2026-09-05

- **A run near its time limit now stops cleanly instead of retrying a data request past it.** When the data provider is briefly throttled, this actor retries the request a couple of times with a pause between tries; those retries now watch the run's own clock, so a run at its time limit ends with the keywords it already has rather than being cut off part-way through a retry.

### 1.0.38 — 2026-09-04

- **Pick a country, a network or a limit but paste no keywords and you now get the sample, not a note asking for keywords.** Pressing Start with nothing at all always looked up the two example keywords. Changing one thing first — your market, "What to return", a row cap — used to cancel it: you received one uncharged row asking for keywords and no data at all. Narrowing a setting made the run worse than touching nothing, which is backwards. Now the sample runs **under** what you set: your market, your network, your mode, your limits, and everything you did not set takes the sample's own value. A limit larger than the sample's own is capped at it, so a sample can never grow into a bill you did not ask for; a limit smaller than the sample's still binds.
- **Asking for keyword ideas with no keywords pasted returns real ideas, and stays a sample**: one keyword expanded, at most five ideas, at most ten rows.
- **One extra uncharged row on those runs names the settings it used**, in the same words the form uses, and says how to run your own list. It is not a miss and nothing is charged for it.
- **A field name this actor does not recognise, a keyword field sent as the wrong type, and a country or language nobody can place all still get their own uncharged row.** Each is you asking for something the sample would answer wrongly, and each names only the field to fix.
- **A "What to return" or "Network" value we did not recognise no longer leaves a run with nothing at all.** It already ran on the documented default and said so on its own row; when no keywords were pasted it also cancelled the sample, so the run ended holding one note and no data. The default answer now comes with the sample.
- **`{}` is untouched.** The bare Start delivers exactly what it delivered before, at the same price, with no extra row, and the owner's own health check still buys no lookup.

### 1.0.37 — 2026-09-04

**Every row of a refused run now tells you the same thing, and names the field.**

1.0.36 started saying plainly when a country has no data at the source rather than implying you had mistyped it — but only on the row about that field. The rows for your individual keywords, and the run summary, still said the value "could not be matched", which reads like a spelling problem. All three now carry the same answer, so it does not matter which row you read first.

**Each refusal also names its own field now.** A `network`, `mode`, `keywords` or `keyword list (paste)` value we could not read is reported against that field by name, the way `country` and `language` already were. Nothing about what you are charged changes — these rows have always been uncharged — but a run that goes wrong is now specific about which field to look at instead of just saying the input could not be read.

### 1.0.36 — 2026-09-04

**27 markets we were wrongly refusing now work, including Hong Kong, Taiwan and Puerto Rico.**

The country table shipped with this actor was built by asking the search-volume source for its locations and keeping the ones it labels "Country". That label leaves out territories and special administrative regions — so Hong Kong, Taiwan, Macao, Puerto Rico, Kosovo, Palestine, Gibraltar, Greenland, the Cayman Islands, Bermuda and 17 more were refused, even though the source reports search volume for every one of them. The table is now built from what the source actually offers as a market, and covers all **244**.

**A country with no data now says so, instead of asking you to check your spelling.** Six countries have no search-volume data at the source at any price — Russia, Belarus, Iran, Cuba, North Korea and the Åland Islands. Asking for one used to return the same "use a two-letter code or the English name" advice as a typo, which is no help when what you typed was correct. Now the run names the country, says plainly that no spelling will fix it, and points at the nearest markets that do have data.

**Region-wide asks get their own answer too.** `worldwide`, `global`, `EU`, `Europe`, `EMEA` and the like now explain that search volume is reported one country at a time, rather than reading as a typo.

**And the Google Ads location code works as a country.** If you already have `2840` or `2826` to hand from the ads API, send it.

All of this is uncharged, one row per keyword, and the run still succeeds.

### 1.0.35 — 2026-09-04

**A country or language we do not recognise no longer costs you the whole run.**

`USA`, `UK`, `united-states`, `en-US`, `en_us` and `eng` were all refused before this build — and one refused value emptied the entire request, so a run with eight good keywords looked up none of them and returned a single guidance row. That is fixed twice over.

**Spellings we now understand.** Countries take a two-letter code, a three-letter code, or the English name, with the everyday variants: `US`, `USA`, `America`, `GB`, `UK`, `Britain`, `United Kingdom`, `AE`, `UAE`, `SA`, `KSA`, `KR`, `Korea`. Languages take a code, a BCP-47 tag, or the English name: `en`, `eng`, `en-US`, `en_GB`, `pt-BR`, `English`. Case, spaces, hyphens, underscores and accents no longer matter. `network` and `mode` are just as forgiving — `google_search`, `partners`, `metrics`, `keyword-ideas` all land where you would expect.

**And when a value genuinely cannot be placed**, the run refuses that one field instead of your whole request. Every keyword you sent comes back as its own uncharged row naming the field that stopped it and the spellings that work, so you can see exactly what to change. Nothing is charged, and the run still succeeds.

`network` and `mode` work the other way round now. They only widen or narrow an answer about the keywords and market you did name, so a value we do not recognise there runs on the documented default, says so on an uncharged row naming the field — and **your keywords are still looked up and delivered**. Previously a single mistyped `mode` bought nothing at all.

**Country and language are dropdowns on the console**, listing every market Google Ads reports on, so a value that cannot work is not one you can pick. The API still accepts any string, which is what the spellings above are for.

### 1.0.34 — 2026-09-04

**You are never charged twice for the same keyword.**

Ask for a keyword you already got from us inside the last 30 days and the same Google Ads figures are handed straight back, with nothing charged for them. No lookup is bought and no `keyword-result` event is charged — the check happens before either — so re-running a list, adding ten keywords to a list of a hundred, or resuming a stopped run costs you only what is genuinely new.

Those rows say so: `repeat: true`, `charged: false`, `firstSeenAt` and `firstSeenRunId`, plus a note naming the day and the run. The run's status line says how many were handed back.

Past 30 days Google has refreshed its figures, so the same keyword is new data and a new charge — the same window the shared 30-day cache uses, and for the same reason. Note the difference between the two: that cache is shared across everyone who uses this actor and only saves the lookup, so a keyword someone else looked up is still your first one.

The memory is yours: it lives in **your own account**, in a key-value store called **`kw-volume-account`** on your Storage tab — delete it to start over and be charged again. On a run where it cannot be read, the run still delivers and charges exactly as it did before, and the status line says the check was unavailable so you know a repeat could have been billed.

Also: the run status line is shorter, so the counts, the named cap and the support ask survive on the run page.

### 1.0.33 — 2026-09-04

- **A keyword Google Ads rejects is now reported as rejected, not as a temporary outage.** The row for such a keyword always said to edit it and start a new run, but the run summary above it counted the keyword as "temporarily unavailable (re-run)" — so following the summary bought another lookup for a keyword that will be rejected every time. The summary and the row now agree.

### 1.0.32 — 2026-09-04

- **A run that stops before it starts now reports its outcome too** — a run refused for its memory setting, and a run stopped because this build was missing a credential of ours, now reach us the same way every other run does: counts and reason codes only, never your input or your rows.

### 1.0.31 — 2026-09-04

- **Every run now reports its own outcome to us** — counts and reason codes only, never your input or your rows — so a run that goes wrong reaches us even when nobody shares it.

### 1.0.30 — 2026-09-04

- **A stray double full stop in the run summary is gone.**
- **Run status lines fit the run page again; sample-run wording shortened.**

### 1.0.29 — 2026-09-04

**A run that resumes after a platform restart now recognises every row it already delivered, so nothing is delivered or charged twice.**

Apify occasionally moves a running actor to another server. When that happens, the run re-reads its own dataset to remember what it already delivered. Until now it trusted the dataset's row count, which can lag for a moment after a restart; a lagging count could make the run start over and charge again for rows you already had, stop before the end, or stop the run outright as a precaution. The run now checks for real rows instead of trusting the count, and reads to the end whatever the count says. Rows, prices, charges and the status line on a normal run are exactly as before.

### 1.0.28 — 2026-09-04

- **A run that filled the row cap you set is no longer treated as a run that went wrong.** The Issues-tab line now appears only when something actually did.

### 1.0.27 — 2026-09-04

- **A run that ends with a problem now says where to reach us.** A miss, an input error, an early stop or a failed run closes by pointing at the Issues tab and naming the reply time; a fully delivered run is left alone.
- **One support promise across this page** — issues are answered in a couple of hours, always within a day.

### 1.0.26 — 2026-09-03

Fresh lookups are back on. The daily lookup budget introduced in 1.0.24 is gone: nothing about a run is refused automatically. Fresh lookups can still be paused on our side for maintenance; then cached keywords are delivered and charged as usual and one uncharged row names the rest. Prices are unchanged.

### 1.0.25 — 2026-09-03

Fresh lookups are paused until 16 September 2026. A run still delivers every keyword already in the 30-day cache, charged as usual, and ships one uncharged row naming the remaining keywords and when to re-run (`resumeCursor` lists them). Nothing is charged for a keyword that was not looked up. Prices are unchanged.

### 1.0.24 — 2026-09-03

Fresh lookups now sit behind a daily budget on our side. Every new search-volume lookup this actor makes is bought from a licensed source, and from this build the actor checks that source's daily budget and its account balance before buying one. When the day's budget is used up — or while we have paused fresh lookups for maintenance — a run still delivers every keyword already in the 30-day cache, charged as usual, and ships one uncharged row saying that the remaining keywords were not looked up and when to re-run; the summary row's `resumeCursor` lists them. Prices are unchanged, and nothing is charged for a keyword that was not looked up.

### 1.0.22 — 2026-09-03

An unrecognised value in the Network or "What to return" dropdown now stops the run at one uncharged guidance row naming the value and the accepted options, instead of running on the default.

Both fields are dropdowns, and either can still be left out or sent as `null` to take its default — that is unchanged. What changed is what happens to anything else. Until now a value neither field recognised was swapped for the default and the run went ahead, with an extra row saying which default it had used. That still meant paying for numbers you had not asked for: ask for `google-and-partners`, mistype it, and the run delivered charged Google-only rows that look exactly like a correct answer. Now nothing is looked up, no keyword is charged, and the single row names what was sent, the options that work, and the default. The run still finishes as SUCCEEDED with that row and its summary — fix the value and start a new run.

### 1.0.21 — 2026-09-03

The two screenshots on this page now load from Apify's own storage instead of an outside website, and the free templates section no longer carries an outside link — the workflow templates are still free, and our Apify profile points to them. Nothing about what this actor delivers or charges changed. The 16 September 2026 fresh-lookup fee described below is unchanged.

### 1.0.20 — 2026-09-02

**From 16 September 2026, a run that buys fresh volume data pays one fresh-lookup fee of $0.19. Runs answered from the 30-day cache pay none. Per-keyword prices are unchanged.**

- **One fee per run, not per keyword.** The fee is charged once, when a run has bought fresh search-volume data and delivered at least one keyword row from it. It is waived when the whole run is answered from our 30-day cache, and when the fresh lookup came back with no data for any keyword. A run resumed after a move to another server never pays it twice.
- **Why.** The licensed source this actor reads from bills every fresh lookup as one batch, however few keywords are in it, so very short lists were costing more than they earned. The fee covers that lookup; the price of each keyword row stays exactly where it was, and a keyword with no data is still never charged.
- **Your run's Maximum cost per run must cover one fresh lookup plus one keyword row** — $0.25 is the minimum from that date. Below it, keywords already in the cache are still delivered, no fresh lookup is bought, and the summary row says what to raise.
- **Until 16 September 2026 nothing changes.** This build recognises the new fee only once it takes effect; every run before then is charged exactly as today. The summary row states the fee whenever a run paid it, so the dataset still reconciles the invoice on its own.

### 1.0.19 — 2026-09-02

**Internal cost accounting only — nothing changes in your rows, prices or charges.**

Each lookup this actor buys from its data provider now carries an internal reference to the run that bought it, so our own cost reports can attribute provider spend correctly. Your rows, prices and charges are unchanged.

### 1.0.18 — 2026-09-02

See the real thing before you run it.

- **Two screenshots of an actual run** now sit on this page: the input form exactly as it looks when you open the actor, and the dataset table it fills — three keywords with average monthly searches, CPC, competition, the top-of-page bid range and trend direction.

### 1.0.17 — 2026-09-02

**Start with nothing set and get real Keyword Planner rows instead of sample rows.**

Clicking Start with no keywords used to return a frozen set of sample rows. It now runs a small real sample — two everyday keywords ("project management software", "keyword research tool") for the United States — charged like any run, so the first thing you see is live search volume, competition, bid range and trend in the exact row shape. The status line and every row say it was the sample and how to run your own list. If the data source does not answer at that moment, you still get one uncharged row per keyword explaining it plus the run's summary row, never an empty result.

### 1.0.16 — 2026-08-31

Sending `null` for a setting you do not want now means "use the default", instead of stopping the run before it starts — and a mistyped Network or What-to-return no longer passes in silence.

- **An optional setting sent as `null` now takes its default.** Agents, n8n templates and MCP callers routinely fill every field in a template and send `null` for the ones they have no value for. Until now Apify refused those runs before the container even started — you got a validation error, no run and no rows, and the fix was to know that you had to leave the key out entirely. Every optional setting on this actor now accepts `null` and reads it as "use the default", which is exactly what omitting it does. `keywords` is the one required setting and still needs real keywords.
- **A `null` limit means the documented default, never the smallest allowed value.** Sending `null` for "Max keyword rows", "Max run seconds", "Max keyword ideas per keyword" or "Max keywords to expand" gives you 1000, 900, 200 and 5 — not 1. A `null` country or language means US and English, and never drops your keywords.
- **A mistyped Network or "What to return" is now reported on its own uncharged row.** Both are still dropdowns with the same choices, but they no longer refuse anything else outright — which is what let them accept `null`. The important half: a value that is neither is no longer quietly swapped for the default. If you ask for `google-and-partners` and mistype it, you used to get Google-only volumes that looked exactly like the numbers you asked for. You now get those rows *and* an uncharged row naming what was not recognised and what the run used instead.

### 1.0.15 — 2026-08-31

A billing safeguard, invisible on this listing today. Nothing about what this actor delivers or charges changed.

- **Rows that carry no charge can no longer be written where writing one would charge you.** Apify offers a billing setup in which every row an actor writes is itself the charge. This actor is not on it, so the safeguard is dormant here — but had it ever been switched to one, the rows it deliberately does not charge for (the sample rows a run with no keywords returns, the notice for a keyword Google publishes no volume for, a rate-limit notice asking you to re-run, the closing summary row) would each have quietly become a billed row. They are now held back on that setup, and their counts move to the run's status line and its `OUTPUT` record instead, so nothing is lost from the report.
- **Every delivered row still states its own charge correctly on either setup.** A row now says it was charged whenever it was, whichever way the charge was raised, and a run can never raise a second charge for a row that was already paid for.
- No change to prices, to which rows are billed, or to any output on this listing: a keyword with search volume is one charge, a keyword with no data is still never charged, and the summary row still closes every run.

### 1.0.14 — 2026-08-30

This page now states the price up front. Nothing about what this actor delivers or charges changed.

- **The price is on the page, not only in the store header.** From $2.00/1,000 keyword results — all-inclusive pay per event, no start fee, no minimum, charged only on delivery — the same figure the store header and the Pricing tab already showed. The "How much does 1,000 keywords cost?" answer now carries the number too, with the free-plan price beside it.

### 1.0.13 — 2026-08-30

A figure Google does not publish for your keyword now says so on the row, instead of arriving as a blank column.

- **An empty CPC now names its own reason.** Google publishes no average CPC for roughly one keyword in eleven — often on keywords whose top-of-page bid range is right there beside it. Those rows used to arrive with `cpcUsd` simply empty and no way to tell a withheld figure from a bug. Every delivered row now carries `cpcMissReason`, which says whether Google gave a bid range but no average CPC, or no advertiser cost figures at all. A keyword Google does publish a CPC for still carries the CPC and an empty reason, exactly as before, and no CPC is ever invented to fill a gap.
- **The same rule now covers every field this actor promises.** `emptyFields` lists each promised field a row came back without — the top-of-page bid range, the competition level and index, the trend direction, the month the data runs to — with a short reason for each, and reads `{}` on a row where nothing is missing. `statusReason` says the same thing in a sentence, so it is visible in the table view without opening the JSON.
- **The sample rows show it before you spend anything.** A run with no keywords returns one sample keyword with a named empty CPC alongside one with every field populated, so the shape you will meet on a real run is visible for nothing first. The output table also gained a "Why no CPC" column.
- Nothing about pricing, charging or which rows are billed changed: a row with search volume is one charge whether or not Google published its CPC, and a keyword with no search volume is still never charged.

### 1.0.12 — 2026-08-30

A keyword left out of an otherwise successful lookup is now asked for again before it is reported.

- **A keyword the lookup did not carry gets one more ask before anything is reported.** A volume lookup can succeed and still come back short — truncated, or listing a phrase in a slightly different form. Such keywords used to be reported straight away as temporarily unavailable, with a request to re-run. The run now sends exactly the missing keywords back to the source once more, within the same run, and delivers whatever comes back as ordinary rows; only a keyword still missing after that second ask is reported as unavailable and re-runnable, uncharged. More lists come back complete on the first run.
- **The second ask stays inside the limits you set.** It never runs once the run has stopped for its cost cap, row cap or time limit, and it happens at most once per batch of keywords.

### 1.0.11 — 2026-08-30

Suite links now point at the full live shelf — every actor named in this README is a live
store link. Nothing about what this actor delivers or charges changed.

### 1.0.10 — 2026-08-30

Review-fix build: a moved or resurrected ideas run no longer repeats work it already finished, and empty settings now read as defaults.

- **A seed already expanded into keyword ideas is never expanded again.** When a run in ideas mode was moved to another server mid-flight or resurrected, a seed whose ideas had already been delivered could be looked up again from scratch — the rows were never duplicated and never double-charged, but the run spent its time repeating a lookup it had already finished. Each expanded seed now leaves a small uncharged receipt row in your dataset saying how many ideas it produced, and a restarted run sees the receipt and moves on. A seed whose expansion failed leaves no receipt, so it is still retried honestly.
- **A resumed run that cannot trust its own delivery record now stops rather than charging again.** Right after a run is moved or resurrected, the dataset read-back can momentarily lag behind what has already been charged. A run that trusted such a read would start the work over — collecting, writing and charging a second time for keywords you had already paid for. It now notices that its charges exceed what the record shows, stops cleanly without charging anything more, and says so. Re-running once the record has settled carries on normally.
- **An empty numeric setting now means "use the default".** Sending `null` or an empty string for a numeric option — as templated API callers often do — used to be read as zero and clamped up to the smallest allowed value. It is now treated as not set and gets the documented default.

### 1.0.9 — 2026-08-29

A keyword we got no answer for is no longer reported as a keyword with no volume.

- **A keyword missing from an otherwise successful lookup is now a temporary, retryable row.** A volume lookup can succeed and still come back short — truncated, or listing a phrase in a slightly different form. Every keyword it did not carry used to be reported as "too rare to report or grouped under a similar phrase", which is a verdict about your keyword and reads as final. Such a keyword now says the source was unavailable for it and asks you to re-run, and it is listed in the run's errors so a short lookup is visible instead of passing for a list of rare keywords. A keyword the lookup **did** answer for, with no volume, keeps the "too rare to report" wording exactly as before. Neither kind of row has ever carried a made-up zero, and neither is charged.

### 1.0.8 — 2026-08-29

A typo in an input field name now gets a helpful pointer instead of sample rows.

- **A misspelled or unrecognised input field is named back to you.** Apify ignores any field
  an actor does not declare, so an input like `{"keyword": [...]}` used to look exactly like an
  empty run and came back with uncharged US sample rows — reading as if the typo had worked.
  Now the run returns one uncharged row that names the field it did not recognise, names
  `keywords` (or `keywordsText`) as the field you meant, and shows the shape. Sending only
  settings — a country, a limit — with no keywords gets the same pointer. A run that does carry
  keywords is unchanged, unknown field beside it or not, and a genuinely empty run still returns
  the sample rows.
- **A country or language we cannot look up is now always reported.** If you sent an
  unrecognised market and no keywords, that run used to return sample rows and never mention it;
  it now returns the uncharged row naming the value it could not read, with valid examples.

### 1.0.7 — 2026-08-28

Large runs finish faster — same rows, same prices.

- Results are now written to your dataset a page at a time instead of one row at a time,
  and each page is billed in one step. A run of a few thousand keywords spends its time on
  lookups now, not on bookkeeping. Nothing about the output changed: same rows, same
  fields, same order, same prices, and interrupted or moved runs still resume without
  repeating or double-charging anything.

### 1.0.6 — 2026-08-27

Review-fix build: the time limit you set bounds the run, not each server the run lives on.

- **`maxRunSeconds` is measured from the moment your run started.** Apify can move a run
  to another server mid-flight; until now the new server started the clock again from zero,
  so a run you capped at ten minutes could keep working for ten more after every move. The
  cap now covers the whole run, however many times it is moved, and the run still stops
  cleanly with a summary row and a resume cursor.

### 1.0.5 — 2026-08-27

Keyword search volume, CPC, competition and 12-month trend direction for any keyword list,
in any country and language Google Ads reports on.

- Paste keywords as a list or as one block of text; duplicates and case or spacing variants
  are charged once. The same rule now holds across the whole run: in `ideas` or `both` mode,
  a suggestion repeating a keyword already delivered — including your own seed handed back
  as one of its ideas — is skipped, never charged a second time, and counted in the run
  message.
- **A run that is moved to another server, or that you resurrect from the console, picks up
  exactly where it stopped.** Every row carries a hidden delivery key, so a resumed run
  never repeats a row you already have, never looks a keyword up twice, and never charges
  twice for it. If the first attempt delivered a row without billing it, the resumed run
  settles that one charge and nothing more.
- `maxItems` is now a cap on the **run**, not on the attempt: a run that is moved mid-way
  can no longer deliver more rows than the limit you set.
- `mode` picks metrics for your own keywords, keyword ideas expanded from them, or both.
  Every idea row names the keyword it came from.
- Real Google Ads Keyword Planner figures through a licensed data provider — `isEstimated`
  is `false` on every row and you never supply a Google Ads login.
- 30-day cache: Google refreshes these volumes monthly, so a repeat lookup inside that
  window is served instantly from the same figures, marked `servedFromCache`.
- Keywords with no data, keywords Google Ads will not accept, rate limits and source
  outages all ship as uncharged rows with a reason — the run still succeeds. A figure that
  arrives unusable counts as no data on the same terms, so it can never be billed as a
  result with an empty search volume.
- `maxItems`, `maxIdeasPerSeed`, `maxIdeaSeeds` and `maxRunSeconds` are hard limits that
  stop the run cleanly and report what is left.
- Running with no input returns uncharged sample rows showing the exact output shape.
