# Changelog of Keyword Difficulty Checker — Bulk Keyword Difficulty Scores (`steadyfetch/keyword-difficulty-scraper`) Actor

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

## Changelog

### 1.0.10 — 2026-10-07

- **A run that runs out of time now names the limit that actually stopped it.** A run stopped by your "Max run seconds" says to raise `maxRunSeconds`; one stopped by the run's own Timeout (Run options, or `timeout` on an API call) says to raise that Timeout; one stopped by both at once says to raise both; and one that reached this actor's 30-minute limit per run says to continue in a new run, with the keywords it did not reach listed in `resumeCursor` as before. Until now a run ended by the Timeout or the 30-minute limit could only say it was one of the two. Nothing about which keywords are looked up, delivered or charged changed.
- **The input form's descriptions now lead with an example.** Every field's first sentence shows the kind of value it takes with one example, the Max run seconds field says the run's Timeout is a separate limit, and the description now points to the sibling actors for keyword ideas from a seed and for the keywords a site already ranks for. The price, the input fields and the output columns are unchanged.

### 1.0.9 — 2026-10-06

- **A Traditional Chinese language tag now looks up Traditional Chinese.** `zh-HK`, `zh-MO`, `zh-Hant` and tags like `zh-Hant-TW` used to be read as Simplified Chinese, so a Hong Kong or Taiwan run was refused and a Singapore run could be scored in the wrong script. They now read as Chinese (Traditional), the same as `zh-TW`, and `zh-Hans-*` tags read as Chinese (Simplified). What is charged for a run is exactly what the plain `zh-TW` or `zh-CN` code charges.

### 1.0.8 — 2026-10-04

- **The input form's description now says what an empty input does.** Sent with no keywords, a run looks up a two-keyword sample in the US market, charged like any run; the description now says so in one sentence, beside the note that "\_demo" is the sample switch and not a keyword. Nothing changes about the price, which keywords are charged, the input fields or the output columns.

### 1.0.7 — 2026-10-03

- **The Apify free plan's daily fresh-lookup allowance keeps applying correctly if this actor's free-plan price ever changes.** The run recognises the free plan by the price it is billed, and it now checks against a list of free-plan prices instead of a single one, so a future price change cannot quietly switch the allowance off. Today's prices, what is charged, and every paid-plan run are unchanged.
- **The note row for a run window outside its range now names the real limit.** It used to say a run of this actor lasts up to an hour; a run never works past 30 minutes, this actor's own ceiling, and the note now says so. The row is still uncharged.

### 1.0.6 — 2026-10-03

- **Maintenance: the shared lookup cache is now reached with a key limited to that cache alone.** Nothing about your runs, your results or what is charged changed.

### 1.0.5 — 2026-10-03

- **A country we cannot place is now refused before anything is looked up, and nothing is charged.** Until now a Country value we could not match — a typo like "XX", a made-up name, or a Google location code for a city or region — ran your whole list on the United States market and charged for it, with a note row saying so. Now nothing is looked up: every keyword gets an uncharged row naming the value you sent and the values that work, so you can fix it and re-run on the market you meant. Every country we do place, by code, name, locale tag or list, runs and is charged exactly as before.

### 1.0.4 — 2026-10-03

- **A keyword you already paid for is no longer charged again after several of your runs finish at the same moment.** When runs on one account ended together, a keyword one run had just remembered could be forgotten, and a later run charged it again. The memory now checks that its last write landed and puts it back if it did not. Nothing else about what is charged changed.

### 1.0.3 — 2026-10-02

- **The status line no longer says a refused keyword was "refused by the source".** A keyword over 80 characters, over 10 words, built from emoji or carrying a symbol a Google keyword cannot hold is turned away before any lookup, so the line now counts it as "not accepted". Each row still names the rule, and charges are unchanged.
- **The README links a real sample dataset:** five keywords from one verified run, including one uncharged row for a phrase with no score on record.

### 1.0.2 — 2026-10-01

- **The run summary (OUTPUT) carries counts only.** Its `vendorGuard` entry lists how many fresh-lookup checks ran, how many were allowed and whether lookups were paused — named fields, nothing else. Keyword rows, prices and charged events are unchanged.

### 1.0.1 — 2026-10-01

- **First release.** Paste a keyword list and get a 0–100 keyword difficulty score for each keyword — how hard it is to rank in Google's top 10 organic results, computed from the backlink strength of the pages ranking there — with Google Ads monthly search volume, CPC, competition, the top-of-page bid range, search intent, the top-ten backlink averages and a 12-month series on the same row.
- 94 markets, each in its own languages. Leave Language empty and the country's main language is used; a language the country is not reported in is refused, uncharged, and the row names the ones that work there.
- One `keyword-result` charge per keyword delivered with a score, and one `keyword-lookup` fee per run that buys fresh data and delivers at least one row. A keyword with no score, a keyword the source will not accept, a rate limit and an outage all ship as uncharged rows with a reason, and the run still succeeds.
- 30-day cache and account memory: a keyword your account already got for the same market in the last 30 days comes back uncharged (`repeat: true`), and a run answered entirely from the cache pays no fresh-lookup fee.
- `maxItems` and `maxRunSeconds` are hard limits that stop the run cleanly and report what is left in `resumeCursor`.
