# Changelog of Google Ad Copy Scraper — Ads Transparency Center, CTAs & OCR (`steadyfetch/google-ads-creative-text-scraper`) Actor

- **URL**: https://apify.com/steadyfetch/google-ads-creative-text-scraper/changelog.md
- **Full Actor documentation**: https://apify.com/steadyfetch/google-ads-creative-text-scraper.md

## Changelog

### 1.0.86 — 2026-09-19

- **The whole "Maximum cost per run" you set is now spent on creatives.** A capped run used to hold back about a twentieth of your cap as a cushion against platform usage — but your cap pays for the extracted creatives and nothing else, so that twentieth was cap you had asked to spend and did not get: a cap worth a hundred and five creatives delivered a hundred. A run now extracts every creative its cap can pay for, and still ends the way it always has — on its own line naming your cap and what was left, never on a run the platform cuts short. Nothing about the price, the charged event, the input form or any output column changed.

### 1.0.85 — 2026-09-16

- **Price copy only.** The 15 September 2026 20:15 UTC rise notified on 1 September is now in effect, so the README and the input form say what every paid plan pays today — $0.006 per extracted creative — instead of announcing it as upcoming. The Apify free-plan price stays $0.015. Nothing about what is delivered or charged changed in this build.

### 1.0.84 — 2026-09-15

- **A run now ends inside the time it was given, even when the Transparency Center or the ad-text reader stops answering in the middle of a read.** Every fetch this actor makes — a Transparency Center page, a creative's screenshot, the ad-text read itself — carried its own separate stopwatch, so one read that hung could carry a run minutes past its own time limit and into the platform's hard kill, which ends a run with no summary, no explanation and no record of what it delivered. Each read is now given whatever is left of the run's own time, and never more than its own limit, so the run always keeps enough back to write its rows, say what stopped it and name what it did not reach. Nothing about what is delivered or charged changes: the same creatives come back at the same price, and anything the clock cut still ships as an uncharged row saying so.
- Nothing else moved: no input field, output column, charged event or price changed.

### 1.0.83 — 2026-09-14

- **A run whose API token can READ your account memory but not WRITE it now says so, instead of silently charging you for the same creatives again next run.** There are two ways a scoped API token can break the promise that you never pay for the same creative twice, and only one of them was visible. The first is the store refusing to open, which the bullet below covers. The second is quieter: the memory opens, every creative you already had is recognised and handed back uncharged exactly as it should be, and then every attempt to write this run's creatives into it is refused — so the run looks perfect, remembers nothing, and your NEXT run pays full price for everything it just delivered. That is now named the moment the first write is refused, while the run is still going: on the run page, in the run log, in an uncharged note row in the dataset, and in the run's OUTPUT record, with the exact permission to grant (key-value store Write, and Create). Nothing about what is delivered or charged changed. Two more things moved with it: a watchlist whose record could not be written used to end the run mid-delivery, and now the run finishes normally and tells you why the list was not updated; and the run page's status line now gives up the "open an issue" line, and then its own optional detail, before it will let the 500-character limit cut the sentence about your money — four shapes of ordinary run were losing that sentence, and the charge statement under it, before this build.
- **A run started with a scoped API token now says so, instead of quietly charging you for creatives you already had.** Apify mints a run's token from the token that started it, so an API run made with a scoped token in restricted-access mode carries that token's permissions — and without key-value store Read, Write and Create it cannot open the memory that makes "you never pay for the same creative twice" true, nor a named watchlist. Until now that looked like ordinary bad luck: the run said the repeat check was unavailable, suggested a re-run, and charged every repeat again on the next run too. It now names the cause and the permission to grant, on the run page, in the run log, and in an uncharged note row at the top of the dataset. Nothing else changed: the run still delivers and charges exactly as before, no price, input field or output column moved, and every other reason a store cannot be opened keeps the wording it had.

### 1.0.82 — 2026-09-13

- **The Pricing block on this page now names the price rise that lands on 15 September 2026, with its date and hour.** Paid plans are charged $0.004 per extracted creative today and $0.006 from 15 September 2026 20:15 UTC — a change filed and notified on 1 September, the fourteen days ahead that the Apify Publishing Terms require. The Apify free-plan price is unchanged at $0.015, nothing about what is charged or not charged moves, and until that instant every run still bills at $0.004 on paid plans. The input form already carried this sentence; this page was the surface that did not.

### 1.0.81 — 2026-09-13

- **This actor has a new title on the store: Google Ad Copy Scraper — Ads Transparency Center, CTAs & OCR.** The old one led with "Google Ads Scraper", which is the name of a hundred other things and says nothing about what you get here. The new one says it: this actor reads what an ad actually SAYS — the headline, the body and the call-to-action of each creative in the Ads Transparency Center, with image ads read by OCR. Same actor, same id, same URL, same input, same output, same price, so nothing you have saved, scheduled or wired into an integration needs touching.
- Nothing else moved: no input field, output column, charged event or price changed.

### 1.0.80 — 2026-09-13

- **Writing `advertiser`, `brand` or `domain` instead of the plural now returns creatives instead of an error row.** An agent or a person who guesses the singular of a field name used to get a run that looked nothing up and one uncharged row saying no advertisers were provided. Those names — `advertiser`, `advertiserId`, `advertiserIds`, `brand`, `brands`, `domain`, `website`, `websites` — are now read as the advertisers or domains they hold, one value or a list, deduplicated against anything already in "Advertisers" or "Domains", with one uncharged row saying which name was read and which field this actor reads so the next run needs no guess. A single brand typed straight into "Advertisers" without the list brackets is read the same way. A name this actor cannot place still gets the same guidance row as before, and pressing Start with nothing set still runs the sample. Nothing charged, priced or delivered changes.
- Nothing else moved: no input field, output column, event or price changed.

### 1.0.79 — 2026-09-13

- **Pressing Start with one setting changed now comes back with creatives, not with an empty result.** With no advertiser named, the run shows you a small sample of one brand's creatives — and it used to take whichever creatives the Transparency Center happened to list first. Some creatives are rendered by a script and carry no public screenshot, so there is nothing on them to read; when the first ones the Center listed were all of that kind, the sample delivered uncharged rows saying so and no ad text at all. The sample now looks at the whole page of creatives it already fetched and takes the ones that actually carry a readable screenshot first. It costs nothing extra — no additional page is requested and no extra reading is bought — and it never re-labels a creative: one with no public screenshot still comes back as exactly that, uncharged. If a brand's whole page has nothing readable on it, the run still says so plainly instead of pretending otherwise. A run where you name your own advertisers or domains is untouched and keeps the Center's own order.
- Nothing else moved: no input field, output column, event or price changed.

### 1.0.78 — 2026-09-13

- **The form now tells an AI agent what a run costs before it calls this actor.** Apify's MCP server hands an agent the input form and never this page, so the description at the top of the form was the one screen that had to carry the whole story and did not. It now opens with the call that works, names what one extracted creative costs on the Apify free plan and on a paid one with the arithmetic for a hundred creatives, states the paid-plan price that takes effect on 15 September 2026, lists what is never charged — a video creative among them, listed with its metadata and never downloaded — names the run option that caps the bill, and says where video ads and Facebook Ad Library rows belong instead. **Advertisers** now leads with the value shape instead of the console instruction, which moved to the end.
- Nothing else moved: no input field, output column, event or price changed.

### 1.0.77 — 2026-09-12

- **Chaining in a dataset that turns out to be empty now comes back with a row saying so, instead of with nothing at all.** If you pointed this actor at another run's dataset (the "Dataset ID" field) and that dataset held no rows, the run ended with an empty result — which looks exactly like a run that broke, and told you nothing about which end the problem was at. It now returns one uncharged row, `status: chained_dataset_empty`, saying the dataset was read and was empty, and naming the two things that fix it: check that the run you chained actually finished with results, or name the advertisers or domains directly. The run's status line says the same thing now, instead of "held no rows naming an advertiser or domain" — which pointed you at the shape of rows that were never there. A dataset whose rows simply do not name an advertiser or a domain still answers exactly as before. Nothing about what you pay changed: that row is uncharged, and only extracted creatives carry a result fee.
- Nothing else moved: no input field, output column, event or price changed.

### 1.0.76 — 2026-09-12

- **A scheduled run that finds nothing new now says so, instead of reading like an empty search.** With "New creatives only" on and no watchlist name, the run compares against everything your account already has — and on a run where every creative was already yours it ended "No creatives extracted. Nothing was charged.", which reads as if the advertiser had stopped running ads. It now names the count: "3 creatives you already had were skipped: not fetched and not charged." A named watchlist already carried this in its own line and still does, so the count is never stated twice, and on a very long status line this sentence is given up before the charge statement is — the numbers are always in the run's OUTPUT either way. Nothing about what you pay changed: a skipped creative is never fetched and never charged.
- **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.
- **The ceiling on what a run spends reading screenshots that come back with no text is now checked before EVERY read, not once per creative.** A text-heavy creative can take up to three reads of the same screenshot, and each one asks for more room than the last — so a creative let in for one read could buy three, and the run only discovered that afterwards. The check now happens before each read and counts what that read really costs, so the ceiling a run is given is the ceiling it keeps. A creative stopped part-way through now comes back as an uncharged row saying this run had used its allowance, instead of one claiming the creative could not be read — and re-running it with fewer creatives ahead of it gets its text. Nothing about your bill changes: this ceiling has never charged you for anything, and only extracted creatives carry a result fee.
- **A maintenance detail you will not see:** the private cost record this actor keeps for our own accounting — what our reading service bills US, never anything you typed and never anything you are billed for — now counts the extra reads a text-heavy creative triggers, which it had been recording as costing nothing. Nothing a run does, delivers or costs you is different: no input field, output column, row status, event or price changed.
- Nothing else moved: no input field, output column, event or price changed.

### 1.0.75 — 2026-09-12

- **A creative with a lot of text on it now comes back with its copy, instead of an empty row saying it could not be read.** Reading a screenshot returns the ad's text inside a fixed amount of room, and a text-heavy creative could fill that room before the answer was complete. The run treated that as a creative it could not read: an uncharged row with no text, saying the text "could not be read reliably" and inviting a re-run — which came back exactly the same. From this build such a creative is asked for a third time in a form that fits (all of its visible text, nothing else), and it delivers. If even that is not enough, the text ships cut short at about 3,500 characters and the row carries a new `textTruncated: true` field plus a reason line saying so, instead of shipping nothing. And in the rare case a screenshot is too big to send to the reader at all, the row now names our own limit and does not ask you to re-run something that would answer the same way. Nothing about what you pay changed: only extracted creatives carry a result fee, and a row without text is not one.
- **The links to our other scrapers on this page name them correctly again.** Several of those actors were retitled on the store, and this page still used their old names. The links always pointed at the right actors; only the words were out of date.
- **Asking for a bigger cap than the actor can do no longer stops the run before it starts.** "Max creatives per advertiser/domain" refused anything above 500 and "Advertisers to open per name" anything above 10 — the platform checked both before the actor was even started, so a bigger number (a round one typed by hand, or guessed by an AI agent calling through MCP) came back as an error with no run, no rows and nothing to read. The limits are unchanged and still real; they are now applied by the actor. A bigger ask runs at the limit, and the dataset carries one uncharged row saying what you asked for and what the run used.
- **A screenshot too big for our ad-text reader now gets read from a smaller copy of itself, instead of coming back with no text at all.** Some ads render a screenshot bigger than the reader accepts in one go, and that creative used to end there — an uncharged row saying its text was not read, and a re-run that answered the same way. The run now makes one smaller copy of that screenshot and reads it; only if the smaller copy is still too big does the row come back as before. Nothing about what you pay changed: only extracted creatives carry a result fee, and a row without text is not one.
- **The run's own limit on how much it spends reading screenshots that come back with no text now counts every read, not every creative.** A text-heavy creative can take up to three reads before it delivers, and that limit was only ever told about one of them — so a run could keep buying reads well past the ceiling set for it. It now counts what it actually spent. Nothing about your bill changes: this limit has never charged you for anything, and a creative it stops still ships as an uncharged row that says so.
- Nothing else moved: no input field, output column, event or price changed.

### 1.0.74 — 2026-09-11

- **An AI agent can now pin this actor straight from the top of the store page.** The one-click MCP link, the actor's id, the input field most runs start from with a real example, and the run setting that caps what a run may spend now sit in the opening lines of the page instead of far down it. An agent that reads only the start of a listing could not see any of that before, so it could not work out how to call this actor without being told. The longer MCP configuration block further down the page is gone, because the one link at the top does the same job; the API example beside it is unchanged.
- **The page also names our other ad scrapers correctly again.** Four links still used titles those actors no longer have. The links always pointed at the right actors; only the words were out of date.
- **One maintenance detail you will not see:** the private run-report this actor writes for our own support — numbers only, never anything you typed — now also records the price each charged item carried on that run, so a question about a bill can be answered from the run itself.
- Nothing else moved: no price, event, input field or output column changed, and only extracted creatives carry a result fee.

### 1.0.73 — 2026-09-10

- **A creative that came back with nothing to read is tried again, instead of being handed back with the same empty answer.** Once a creative has been delivered to your account we never charge you for it twice, and to do that we remember what every creative answered. The trouble was that the memory also remembered the EMPTY answers — no screenshot to read, no text found on the image, a video creative whose metadata is all this version extracts — so re-running the same advertiser handed the same nothing straight back from your own record, without buying the screenshot or reading it at all. Once an empty answer was written it could never be improved on, however much better a later build got. The fault was found on a real customer run of our Facebook ads transcriber, where the same ad came back empty five runs in a row, and the same memory works here. From this build, a creative whose remembered answer was an empty one is read again the first time you run it on a newer build, and charged once if it then delivers text. It could never have been charged before, because an empty answer is never billed. A creative ALREADY DELIVERED to you is untouched: it still comes back uncharged, from the run that delivered it, and is never charged a second time. And "New creatives only" no longer hides one of these — an empty answer is a creative you never actually got, so it is not treated as one you already have.
- Nothing else moved: no price, event, input field or output column changed, and only extracted creatives carry a result fee.

### 1.0.72 — 2026-09-10

- **A run that hits a refusal now waits a length that fits your run, instead of skipping the wait entirely.** When the Transparency Center refuses every attempt, the run pauses and walks the whole ladder again. Until now that pause was one fixed 90–180 second block: if your run's time limit could not hold a whole one, the run took no pause at all and you got an uncharged "please re-run" row with almost all of your time unspent. The pause is now sized to the time your run actually has left, and the first one is short — about 30 to 60 seconds — so a refusal that lifts quickly costs you far less waiting. A short run, a small creative cap and the untouched Start form now all get a real second walk where they used to get none.
- **The waiting is also sized to what is still worth recovering**, so a run with only a couple of creatives left to read no longer spends eight minutes waiting for them — while a run that has not delivered anything yet always gets one pause whatever its size, because the first thing you see should not be an empty result.
- Nothing else moved: no price, event, input field or output column changed, and only delivered rows are charged.

### 1.0.71 — 2026-09-09

- **Two runs of the same watchlist started at the same time no longer erase each other's sightings.** Watchlists and the account memory are 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.
- Nothing else moved: no price, event, output column or charge changed, and only extracted creatives carry a result fee.

### 1.0.70 — 2026-09-07

- **A run that answered every creative it found no longer ends by asking whether something went wrong.** Two ordinary results were reading as failures on the run page. An advertiser whose creatives are all video comes back as uncharged metadata rows — that is what this actor does with video — and a run whose **Formats** setting matched none of the creatives listed comes back with a row explaining exactly that. Both were finishing with "Something off? Open an issue", on runs that did precisely what was asked. They now finish clean. The run page still asks for an issue whenever something really did go wrong — a creative that could not be read, a source that refused us, a stop before the work was done — and it now also says so when a run cannot account for creatives it asked for, instead of staying quiet about them.
- **Long runs now keep a little time back at the end, so a run always reports what it did.** A run that reached its time limit part-way through could be cut off by the platform mid-creative, which meant no closing summary and no record of the run at all — the rows were there, but nothing said what had happened or what was left. Every run now stops a short way before its own time limit, writes its summary and its remaining-target rows, and finishes cleanly. The time held back scales with the length of the run, so a short run keeps only a few seconds and still gets to do its work.
- Nothing else moved: no price, event, output column or charge changed, and only extracted creatives carry a result fee.

### 1.0.69 — 2026-09-07

- **A creative image the run cannot reach is now fetched from a different address, instead of the same one three times over.** When the image host could not be reached at all from the address this run went out from, or answered with a fault of its own, the run tried the identical route twice more and then handed back an uncharged "please re-run" row. It now moves to a fresh address after the first such answer, so creatives that used to come back without their text now come back extracted. A creative that is genuinely gone is unchanged: answered once, uncharged, no retrying.
- **A run stopped before it can look anything up now answers every advertiser and domain you named.** Two stops used to end with an explanation on the run page and an empty dataset: a Transparency Center rule that disallows automated access, and a run restarted after its processing window had already closed. Each target you named now gets its own uncharged row saying it was not looked up and why, and the run's own record counts them, so nothing you asked for goes unexplained. Nothing is fetched and there is no result fee for those rows.
- **A finished run that also wrote an explanatory row no longer ends by asking whether something went wrong.** A run that delivered what you asked for and added a row describing what it did — which settings it applied to the sample, a list it had to shorten — was reading as a problem run on the run page. It now reads as what it is.
- Nothing else moved: no price, event, output column or charge changed, and only delivered results are charged.

### 1.0.68 — 2026-09-06

- **A host we cannot reach directly is no longer reported as a host that does not exist.** Some servers publish only one kind of internet address, and it is a kind our machines cannot dial straight out. The ad-library lookups and the creative-image fetch used to demand the other kind, get "no such address" back, and answer as though the ad were gone — a verdict that also stopped the run from retrying through a different route, because a thing that does not exist cannot be found on another route either. They now *prefer* the address they can dial rather than demanding it, so every host that publishes both is reached exactly as before, and one that does not is answered honestly and retried. Nothing was ever charged for either row.
- Nothing else moved: no price, event, output column or charge changed, and only delivered results are charged.

### 1.0.67 — 2026-09-06

- **A block of website links pasted into "Domains" is now read as the list you meant, and a block pasted into "Advertisers" comes back with an uncharged row saying so.** "Domains" reads a site address, so a pasted block of them — separated by spaces, line breaks, tabs, commas, semicolons, pipes or nothing at all — is now split back into the individual domains and each is searched on its own. "Advertisers" searches by brand name, so a block of links there is refused before anything runs, with a row that says how many links it found and points at "Domains".
- **A single link, and a brand name or AR… ID, are untouched.** Nothing that already worked reads differently.
- **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.66 — 2026-09-06

- **Every advertiser and domain your maximum cost per run never reached now gets its own row.** The run timeout already worked this way; the cost cap did not. When the cap was reached part-way down the list of targets you named, the ones behind that point vanished — no row, no reason, nothing in the count. Each remaining target is now answered with its own uncharged `skipped_budget` row saying the run reached your maximum cost per run before it was looked up. Nothing extra is asked of the Transparency Center for them, so this costs nothing.
- **A target the cost cap cut mid-listing now says so too**, with a row naming it and the creatives it never reached.
- **The rows say how to get the rest:** raise **Maximum cost per run**, lower **Max creatives per advertiser**, or split the targets across runs, and re-run. They are flagged `retryable`.
- **The run's own record now counts them.** A run stopped by the cost cap reported an ask, some rows, and a gap; it now reports every creative row the cap stopped, under its own reason.
- **Nothing is charged for these rows** — nothing was listed, screenshotted or read.
- **Nothing about pricing, events, input or output columns changed.**

### 1.0.65 — 2026-09-06

- **Every advertiser and domain the run timeout never reached now gets its own row.** When a run hit its time limit part-way down the list of targets you named, the ones behind that point vanished — no row, no reason, nothing in the count. Each remaining target is now answered with its own uncharged `skipped_deadline` row saying the run reached its time limit before it was looked up. Nothing extra is asked of the Transparency Center for them, so this costs nothing.
- **A target the clock cut mid-listing now says so too**, with a row naming it and the creatives it never reached.
- **The rows say how to get the rest:** raise the run timeout, lower **Max creatives per advertiser**, or split the targets across runs, and re-run. They are flagged `retryable`.
- **The run's own record now counts them.** The time limit was the one stop this actor could not name: a run cut short by the clock reported an ask, some rows, and a gap. It now reports every creative row the clock stopped, under its own reason.
- **Nothing is charged for these rows** — nothing was listed, screenshotted or read.
- **Nothing about pricing, events, input or output columns changed.**

### 1.0.64 — 2026-09-05

- **Every creative row a run asks for is now accounted for in the run's own report.** Alongside your rows, each run keeps a private tally of what it set out to collect and what became of every item — that tally is what tells us a run went wrong, often before anyone has to tell us. It was counting targets where it should have been counting rows: an advertiser the Transparency Center refused to answer for was recorded as "one miss" instead of the creatives it was going to cover, so a single blocked advertiser could lose most of a run's tally with nothing naming it. Every number in it is now a creative row, and they add up exactly — rows delivered, plus each row that missed with its own reason, plus each row one of your own settings stopped.
- **A refused setting is now measured in rows too.** When a value this actor cannot accept — an unsupported market, for instance — stops a run before anything is searched, the report now says how many creative rows that setting cost you, instead of how many advertisers you typed. Your rows are unchanged: every target still gets its own uncharged row saying it was not searched and which field stopped it.
- **Creatives your own format filter removes are now named.** If the Center lists creatives for a target and your "Formats" choice filters all of them out, that is now recorded as your filter's doing rather than disappearing from the count. The uncharged summary row explaining it is unchanged.
- **A run now records why it ended** in the same words across this whole family — your maximum cost per run, the run timeout, a move to another server, a refused input, or the Center turning the run away — where before it recorded this actor's own private wording, which nothing downstream could read.
- **Nothing about your rows, your extracted text or your charges changes.** The same rows in the same order, the same uncharged misses with the same wording, the same prices; only the run's report of itself is different.

### 1.0.63 — 2026-09-05

- **A run that waits now always keeps room to finish and report.** When Google's Ads Transparency service turns every request away, the run can pause and walk the whole request ladder again — but the check that decided whether a pause was affordable kept back only one request's worth of time for that second walk, while a walk is three requests and their cooldowns. On a run whose time limit fell in a narrow band (roughly five and a half to six and a half minutes, including a shorter limit you set yourself), the pause was allowed and the walk after it then ran past the end of the run, so the honest uncharged row it was taken for never landed. The check now keeps back a whole walk, measured from the ladder's own retries, timeout and cooldowns, so a pause is only taken when the run can finish the walk the pause was for.
- **Charges are unchanged.** Waiting is uncharged, a blocked advertiser lookup is uncharged, and only delivered creatives are charged.

### 1.0.62 — 2026-09-05

- **The default sample always runs live.** It is no longer answered from the record of creatives your account already has, and its creatives are not added to that record: a bare Start looks them up, reads their text and charges for them like any run, every time. Pressing Start twice therefore pays for the sample's creatives twice — a few cents — and in exchange the sample always shows current ads and always proves the actor is working end to end. This is the change 1.0.61 described but did not apply. Any run that names its own advertisers, domains, dataset or rows is unchanged: creatives your account already has still come back from the run that delivered them, uncharged, and a run that asked for "New creatives only" keeps that comparison.
- No row, price, event or output column changed.

### 1.0.61 — 2026-09-05

- **A Transparency Center that refuses a request is now waited out instead of reported.** When the Center declined every attempt this run made for an advertiser or domain — a refusal, a throttle, a page that contradicted itself — the run gave up in about a minute and a half and told you to wait and rerun, while nearly all of the time you had paid for went unused, and a rerun started moments later usually met the same answer. A run that still has time now waits for it to clear (a minute and a half to three minutes, varied so parallel runs do not retry in step), then asks again. Everything that is an answer rather than a refusal comes back at once, exactly as before: an advertiser the Center does not list, a domain with no verified advertiser, a creative that is no longer in the archive, one bigger than the size limit. The same waiting now covers a creative's screenshot.
- **The default sample can now use that waiting.** Pressing Start with nothing set (or with only settings set) runs a small sample under its own time limit, and that limit was shorter than a refused request plus the wait plus a second try — so on the run most people see first, the waiting above could never have happened. The limit is now sized to hold all three and the time needed to write the results: seven minutes at its very worst, while a sample that is served still finishes in well under two.
- No row, price, event or output column changed.

### 1.0.60 — 2026-09-05

- **A blocked first page now carries `retryable: true`, like every other blocked page.** When the Transparency Center refuses an advertiser or domain before its first page of creatives arrives — or answers the first page with a count of ads and none of the ads — the uncharged row already said to rerun later, but its `retryable` field said `false`, so anyone filtering on that field to feed a re-run skipped exactly the targets a re-run would recover. The field now agrees with the sentence. A permanent answer (no such advertiser, no creatives listed, a domain with no verified advertiser) is unchanged and still carries no re-run flag.

### 1.0.59 — 2026-09-05

- **A run near its time limit now stops cleanly instead of retrying past it.** When the Transparency Center is slow or briefly refuses a request, this actor retries it a few times with a short pause between tries. Those retries now watch the run's own clock, so a run that reaches its time limit ends with the results and summary it already has, instead of being cut off part-way through a retry.

### 1.0.58 — 2026-09-04

- **Change only a setting, name nothing, and the run now goes ahead.** Picking a region, narrowing the creative formats, setting a cap, "New creatives only" or a watchlist name and pressing Start without naming an advertiser, domain or chained run used to turn the run away with a single uncharged guidance row — so the buyer who picked their own market got less than the buyer who touched nothing. Those settings are now applied to the default 3-creative sample and the run delivers, charged like any run. A cap smaller than the sample is honoured exactly; a bigger one never grows it.
- **One uncharged `sample_note` row** says which settings were yours, what the sample supplied for the rest, and how to run your own ask. It is never counted as a miss, and the run status line says the sample ran under your settings.
- Guidance is unchanged where it is the honest answer: a field name this actor does not recognise, a format or a region it does not offer, a cap that is not a number, or a list you sent empty (`advertisers: []`, `domains: []`, `datasetId: ""`) still gets its uncharged row naming the fix — a mistake is never answered with sample creatives.
- Pressing Start with nothing set at all is unchanged: the same 3-creative sample of one advertiser.

### 1.0.56 — 2026-09-04

- **A region this actor cannot search is never quietly swapped for another market.** `region: "CH"` used to run in the US and charge you for US creatives, with only a note to say so. The run now stops before anything is fetched and returns uncharged rows: one naming the `region` field, the regions it can search and which of the three things went wrong (a market it has no Transparency Center region for, a whole-region ask, or a value nothing could place), plus **one row per advertiser and domain you asked for** saying it was not searched. Nothing is charged.
- **Ordinary spellings of a region now just work** — `US`, `us`, `USA`, `United States`, `Germany`, `DEU` all resolve. Only a value nothing can place is refused.
- **Every input problem now names the field it was about** in the run's own record — the region, the formats list, a cap, the target fields, a chained dataset — instead of one undifferentiated "input error". The field NAME only; nothing you typed is stored.
- **Miss reasons are reported one class at a time.** A wall at the Transparency Center, an advertiser with no ads, a creative with no public screenshot and an outage of our own text reader used to share a single number. Each now has its own count, so a problem on our side is visible as one. Your rows and their `statusReason` values are unchanged.
- The run's record now reports what you ASKED for alongside what was delivered, so a run that turned everything away can be seen as one.
- The Changelog tab no longer describes any row as "free" — the word is "uncharged"; running any actor still uses platform time you pay for. No prices, counts or behaviour changed.

### 1.0.54 — 2026-09-04

- **A creative already delivered to your account is never charged a second time.** Re-running the same advertisers, domains or chained rows used to fetch, read and charge for every creative again. The run now recognises a creative your account already has — under its creative ID or its screenshot asset, whichever advertiser lane listed it last time — and hands it back from the run that delivered it: `repeat: true`, `firstSeenAt`, `firstSeenRunId`, `charged: false`, no screenshot fetched and no text read again. The status line counts them and `OUTPUT.repeats` holds the number. Your account keeps this memory in the key-value store `google-ads-watch-account`; delete it to forget everything.
- **New creatives only works without a watchlist name.** It compares against your account's memory; a named watchlist still keeps a separate list per competitor set. Only when neither can be read does the run stop, uncharged, instead of charging you for creatives you may already have.
- **If the memory cannot be read, the run still runs.** It delivers and charges as usual and says on the status line and the charged rows that the repeat check was unavailable.
- **Every row now carries `isNew` and `firstSeenAt`**, not only rows of a watchlist run.
- A watchlist written before this build kept no copies of its rows: the creatives on it are read again once, not charged, and kept from then on.
- **The status line is shorter and can no longer be cut.** The run page truncates it at 500 characters; on a heavy run this one overflowed and lost its tail — the charge statement, the advertiser fallback and the support line. Counts now read as plain English instead of `row(s)`, and where a long run still will not fit, the watchlist and advertiser-widening totals step aside with a pointer to the run's OUTPUT record, which carries them in full.
- A few row outcomes the store page called "free" are named "uncharged", which is what they are, and the page no longer describes the old two-row demo — a bare Start has run a real 3-creative sample since 1.0.37.

### 1.0.53 — 2026-09-04

- **Rows pasted into the "Dataset ID" field are read as your rows.** A paying buyer pasted a dataset's exported rows into that field; the run sent them to the platform as if they were an ID and stopped on the answer. The run now recognises pasted rows there, works them exactly like rows pasted into "Dataset items", and adds one uncharged note saying where they belong next time. Anything else that is not a dataset ID or dataset name — a link, a value with spaces, a very long value — is answered with one uncharged row naming what it looked like, before anything is sent anywhere. Nothing was charged for the runs that stopped, and nothing is charged for these notes.
- **A Dataset ID the platform itself cannot answer for is settled in seconds, not minutes.** When the platform's own dataset lookup fails on its side, the run used to wait through the platform client's long retry schedule — close to seven minutes on one lookup — and could then run out of its own time. The lookup now gets two quick tries plus one short pause, and the run moves on to its honest uncharged row. Nothing about what is charged changed.

### 1.0.51 — 2026-09-04

- **A chained Dataset ID the run cannot read now ends as one uncharged row, whatever the reason.** Until now a refusal the run did not recognise — anything other than "no such dataset" or "no access" — stopped the run within seconds with nothing delivered. Every answer now becomes an honest `input_error` row naming what the platform said, a momentary refusal gets one more try, a read that stops part-way keeps the rows already read and says where it stopped, and the run carries on with the rest of the input. A value that is not a dataset ID at all says so instead of reading as a temporary problem. Rows chained in an unexpected shape were already harmless and now have a test that keeps them so. Nothing was charged in any of these cases before, and nothing is now.
- **A run that stops on a fault of ours now tells us what kind of fault it was**, so it is looked at without anyone needing to share the run. Nothing about what is delivered or charged changed.

### 1.0.50 — 2026-09-04

- **A rejected Transparency Center request no longer changes the route the rest of the run uses.** That answer comes back the same way from every route, so it is now read as an answer about the request itself. A genuine refusal or an outage still moves the run to a fresh route. Nothing about what this actor delivers or charges changed.

### 1.0.49 — 2026-09-04

- **A run that stops before it starts now reports its outcome too** — a run refused for its memory setting, a run stopped by a configuration fault on our side, and a resumed run that could not read back what it had already delivered, now reach us the same way every other run does: counts and reason codes only, never your input or your rows.

### 1.0.48 — 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.
- **Vendor calls on media that yields nothing chargeable are bounded per run** — a small allowance that every charged delivery widens, so a paid run is never cut short; past it, items ship as uncharged `vendor_budget` rows.
- **A chained Dataset ID is looked up read-only.** A mistyped ID creates nothing and returns one uncharged input-error row that names the ID as the problem; a real dataset is read page by page with an honest notice past 10,000 rows.
- **The watchlist clause of the status line no longer splits itself with a semicolon.**
- **Row notes state what happened and what was charged; the support ask lives on the run page.**

### 1.0.47 — 2026-09-04

- **The console's input and output descriptions no longer call rows "free"** — they are uncharged; the run still uses platform time.

### 1.0.46 — 2026-09-04

- **Rows that carry no result fee are now described as uncharged, not free** — running any actor still uses platform time, so no row is ever free.

### 1.0.45 — 2026-09-04

- **The end-of-run summary now fits on the run page.** It was long enough to be cut off part-way through; the wording is tighter and every count, cap and charge statement survives.

### 1.0.44 — 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 run that delivered everything, including one that filled the row cap you set, is left alone.
- **One support promise across this page** — issues are answered in a couple of hours, always within a day.

### 1.0.42 — 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, or stop reading before the end. 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.41 — 2026-09-04

**Internal bookkeeping only — nothing changes in your rows, prices, charges or status line.**

The record this actor keeps of its own running costs now reports itself: if it cannot be saved, the run log says so plainly instead of staying quiet, and every run's OUTPUT record carries whether it was saved. Your dataset, your charges, your prices and the status line are exactly as before.

### 1.0.40 — 2026-09-03

**Documentation only — nothing changes in your rows, prices, charges or status line.**

The example screenshots on this page now load from Apify's own storage, and the references that used to point off Apify are plain text now. The free n8n workflow templates are unchanged and still listed on our profile website. Your dataset, your charges and the status line are unchanged.

### 1.0.39 — 2026-09-02

**Internal accounting fix — nothing changes in your rows, prices, charges or status line.**

A small correction to how our own cost records count very short clips, and our cost records are now complete for every run. Your dataset, your charges and the status line are unchanged.

### 1.0.38 — 2026-09-02

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

Runs started from our own account now record the processing they used, so our cost reports are measured instead of estimated. Nothing is added to your run or its storage, and your dataset, your charges and the status line are unchanged.

### 1.0.37 — 2026-09-02

Start with the default form and get real creatives instead of placeholder rows.

- **A bare Start now runs a 3-creative sample.** Click Start with nothing set (or send an empty input from the API) and the run reads up to three real creatives of one advertiser — `HelloFresh` in the US — and returns their full rows, charged like any other creatives. The two placeholder "sample rows" are gone; what you see is what a real run returns.
- **The sample stays small and quick by design:** one advertiser, three creatives, and a three-minute window. On a bad minute you get one uncharged row saying the Transparency Center did not answer (re-run in a minute), never an empty dataset.
- **The status line says when a run was the sample** and how to run your own advertisers.
- **Nothing else changed:** any input with advertisers, domains or a chained dataset in it behaves exactly as before, and a mistyped field still gets a row naming the field instead of a sample.

### 1.0.36 — 2026-09-02

**Text-heavy creatives are now read in full.**

A creative carrying a lot of on-screen copy used to come back as an uncharged "service problem, please re-run" row, and a re-run gave the same answer. The text reader now gives such a creative a second, larger pass, so it delivers like any other. A creative that still cannot be read reliably ships as an uncharged row that says so, never as a charge.

### 1.0.35 — 2026-09-01

**Watchlists: re-run the same advertisers on a schedule and pay only for the creatives that are new.**

Until now every run was a one-off. Ask for the same advertiser tomorrow and you got — and paid for — the same creatives again. Two new fields change that:

- **Watchlist name** (`watchlistId`) — give the run a name such as `acme-competitors` and the actor remembers every creative it has answered under that name, in a key-value store in **your own** Apify account (`google-ads-watch-<name>`, yours to inspect or clear). Every row now says whether the creative is new to that list (`isNew`) and when it was first seen (`firstSeenAt`).
- **New creatives only** (`newCreativesOnly`) — with a watchlist set, creatives already on the list are skipped **before any screenshot is fetched**: not fetched, not read, not charged — and they no longer use up your per-advertiser cap, so a scheduled run walks past what you already have to the new ones. The run's status line and its OUTPUT summary say how many were new, how many were skipped, and how many the list holds now.

Only a creative the actor actually answered goes on the list — an extracted creative, or a final uncharged verdict such as no public screenshot or a video creative. One it could not answer (a screenshot the host did not serve, an outage on our side) is not remembered, so the next run tries it again. The list is written the moment a row lands in your dataset and before its charge posts, so an interrupted run can never forget a creative you paid for.

Two safety rules: **New creatives only** without a watchlist name does not run at all, and a watchlist the run cannot open stops the run before any fetch. Both ship as one uncharged, clearly-labelled row.

Without a watchlist name nothing changes: the two new columns read `null`, and every input mode, price and charging rule is exactly as before.

### 1.0.33 — 2026-09-01

**Chain any Google Ads scraper run straight into this actor.**

Until now every run started from advertiser names or domains you typed in. A finished run of any Google Ads / Transparency Center scraper — our video-transcript companion included — can now feed this actor directly: pass its dataset ID (Apify's *Connect actor* integration fills it in for you) or paste its rows, and each row's advertiser ID, advertiser name, domain or ad-page link becomes a target, one per row. No re-typing, no second search, nothing paid twice.

- **Honest at every edge.** A dataset that can't be read, a row that names no advertiser, or a dataset larger than one run can take is reported in an uncharged row that says exactly what happened — never a silent empty result.
- **One-click agent setup.** The page now carries a ready-to-paste MCP server entry for Claude, Cursor and other MCP clients.

Nothing about what this actor charges changed.

### 1.0.32 — 2026-09-01

**The step that reads text off a creative moves to our provider's current engine.**

Reading the words out of an ad screenshot is what this actor does, and the engine doing it is being retired by the provider this month, with runs quietly redirected to its replacement two weeks from now. This build makes that move explicitly instead: charged output should never ride a silent switch.

Both engines were run side by side on the same real ad screenshots before this shipped. The same text comes back — a Danish text ad came back word for word identical — and the rule that matters most is unchanged: **a screenshot with no readable text is still an uncharged, honest miss and is never charged.** Nothing about what this actor charges changed.

### 1.0.31 — 2026-08-31

**A field you left unset can now be sent as `null` — the run starts and uses the default.**

If you call this actor from a template — n8n, an agent framework, a chained workflow — the tool usually fills in *every* field it knows about and writes `null` into the ones you left blank. Until this build the platform refused those runs before they started, with an error like `Field input.maxCreativesPerAdvertiser must be integer`. Nothing ran, nothing was charged, and the fix was non-obvious: you had to delete the key entirely rather than leave it empty.

Every optional field now accepts an explicit `null` and reads it as **"use the default"** — identical to leaving the field out. `{"advertisers": ["HelloFresh"], "formats": null, "region": null, "maxCreativesPerAdvertiser": null}` runs exactly like `{"advertisers": ["HelloFresh"]}`: all three formats, the US market, 30 creatives per advertiser. A `null` never counts as a value — a null **Creative formats** is all three formats rather than none, and a null cap is the standard cap rather than the "must be a number of at least 1" refusal it used to be.

Nothing about what this actor delivers, or charges, changed.

### 1.0.30 — 2026-08-31

**A safeguard against ever billing you for a row this actor calls uncharged.** Apify offers a billing basis where every row an actor writes is charged automatically. This actor is not on it and has no plan to be — but nothing in the code checked, so if it were ever switched over, the two sample rows, the input-guidance row, uncharged video-metadata rows and every uncharged "no screenshot" / "please re-run" row would quietly have been billed to you as ordinary extractions. The actor now reads how the run is billed before it writes anything: on a per-row basis those uncharged rows are reported in the run's status line instead of being written, and an extracted row correctly says it was charged. On this actor's real, named-event pricing — one charge per extracted creative, everything else uncharged — absolutely nothing changes.

### 1.0.29 — 2026-08-31

Copy fix on 1.0.28's new status line: when a run opens several same-name advertisers, the line said all of them "were searched" even when a cap or the run timeout stopped the run inside the first one. It now says which advertisers the name queued, largest first, and **how many the run actually reached** before a cap stopped it. Nothing about what this actor delivers or charges changed.

### 1.0.28 — 2026-08-31

**Advertiser names now resolve in your region, to the advertiser that actually runs the ads — and can open more than one of them.**

- **Your region first.** An advertiser name is now matched among advertisers registered in the run's `region` before anywhere else; only when that region holds no match at all does the run fall back to the global one — and the status line says so ("advertiser "X" has no US-registered match … resolved to Y (DE) instead"). A US run used to be able to silently return a German or Kenyan advertiser's ads.
- **The right advertiser, first time.** A brand shares its name with unrelated advertisers: "Nike" matches ten advertisers registered in the US alone, one running about 9,000 ads and the rest a handful each. Names used to resolve to whichever match the Transparency Center happened to list first. A name now resolves to the advertiser running the **most ads** in your region.
- **New "Advertisers to open per name" setting** (`maxAdvertisersPerName`, default 1). Raise it and each brand name also covers its next-largest namesakes, largest first. The status line names exactly which advertisers were opened and roughly how many ads each has. Finding advertisers is never charged; each opened advertiser gets its own "Max creatives per advertiser/domain" allowance, so raising this raises what a run can cost.
- **Plainly stated in the README and the input form:** this searches advertiser **names**, not ad text — the Transparency Center publishes no ad-text or topic search. AR… advertiser IDs and domains are unaffected.
- **Fewer lookups and screenshot fetches fail for a reason a re-run could not fix.** Some hosts advertise an address family this actor's servers cannot reach; those requests now take the reachable route instead of failing.

### 1.0.27 — 2026-08-30

- **A screenshot the host refuses on the first try is now fetched again before the creative is given up on.** A refusal, a throttle or an interstitial page from the screenshot host used to end the creative at once as an uncharged `screenshot_fetch_failed` / `screenshot_not_an_image` row. The run now waits and tries again — over a different route when the host is refusing — and only a creative the host would not serve after those attempts still ships that uncharged row, worded exactly as before, still `retryable`, still uncharged. A screenshot the archive genuinely no longer serves (`screenshot_unavailable`) is unchanged: answered once, never retried.
- **A creative the Transparency Center listed without its display details is now looked up directly before being reported as blocked.** Such a row used to ship straight away as an uncharged `source_blocked` row. The run now asks the Center for that one creative's details: when they arrive, the creative is extracted and charged like any other; when they describe a creative with no public screenshot, it gets the plain uncharged `no_screenshot` row; only a lookup that goes unanswered keeps the uncharged `source_blocked` row with the same re-run advice.

### 1.0.26 — 2026-08-30

A suite link now carries a sibling's updated store title. Nothing about what this actor delivers or charges changed.

### 1.0.25 — 2026-08-30

Clearer release notes — this page's notes now read more plainly. Nothing about what this actor delivers or charges changed.

### 1.0.24 — 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.23 — 2026-08-30

- **An interrupted charge can no longer fail a resumed run.** If a charge left over from an interrupted run cannot be completed the moment the run resumes, the run carries on and settles it later. Nothing is ever charged twice.
- **A resumed run's per-target limit now counts everything it had already delivered — before anything new is fetched.** That includes creatives for advertisers the listing no longer shows. A target whose limit the earlier half of the run had already filled now gets one uncharged row saying so, instead of silently adding nothing; and if the resumed run cannot completely read back its own delivery record, it stops with charging disabled rather than risk delivering — and charging — past a limit already reached.
- **A listing cut short by the source now says so.** When the Transparency Center stops answering partway through a long listing, the run now ends that target with a note: the rows above were delivered and charged as normal, the missing remainder was not charged, and a re-run will fetch the rest. Such a cut used to read as the target being blocked, with a note that wrongly said nothing had been charged.
- **A one-off connection-setup failure at the start of a run no longer weakens every later lookup.** The run now recovers the failed route on its own instead of quietly carrying the failure for the rest of the run.

### 1.0.22 — 2026-08-30

- **The per-advertiser limit is now exact across an interruption for domain searches too.** A domain search covers every advertiser verified for that domain. When the platform moved such a run to another server mid-flight, the resumed run only counted its earlier deliveries for the first advertiser it met again — an advertiser whose earlier creatives were no longer listed could be topped back up past the limit, with the extra rows charged. A resumed domain run now counts what it already delivered for every advertiser it meets, so the limit — and what you pay — stays exact across the move.
- Runs outside the Apify platform now retry a refused lookup directly, instead of attempting a retry path that does not exist there. Platform runs are unchanged.

### 1.0.21 — 2026-08-29

Temporary failures now always say they are temporary, nothing is ever silently dropped, and a garbled read is never billed.

- **A screenshot host that refuses or fails to answer no longer writes the creative off as unavailable.** Refusals, throttles and server errors from the screenshot service now come back as an uncharged `screenshot_fetch_failed` row with re-run advice; only a screenshot the archive genuinely no longer serves keeps `screenshot_unavailable`.
- **If our text-reading service becomes unavailable mid-run, every remaining creative and target now gets its own uncharged row saying so.** Previously the run stopped cleanly but the unprocessed remainder was simply absent from the dataset, and the one creative that hit the outage was described as unreadable. The rows now say plainly that the problem is on our side, nothing was charged for them, and trying again later will retry them.
- **A garbled read can no longer be billed.** If the text reader returns an implausible wall of repeated junk instead of ad copy, the creative ships as an uncharged `ocr_unreliable` row instead of a charged extraction. Genuinely short ad copy ("50% off" is a real ad) is still extracted and charged exactly as before.
- **New `retryable` column.** Rows a fresh run can genuinely recover — source weather, screenshot-host blips, an outage on our side — carry `retryable: true`, so you can filter exactly what a re-run would fix.
- **A resumed run that cannot fully trust its own delivery record now stops instead of guessing.** After an interruption, a run that cannot completely read back what it already delivered and charged stops with charging disabled — never risking a second charge for creatives you already paid for — and a run resurrected after its time window has already ended stops immediately instead of starting partial work.
- The steadyfetch actor directory at the bottom of this page now links every live actor.

### 1.0.20 — 2026-08-29

A blocked lookup no longer reads as an advertiser that does not exist.

- **When the Transparency Center refuses or drops an advertiser-name lookup, the row now says so and asks you to re-run** — instead of reporting that no advertiser goes by that name. A genuinely unknown name still gets the same spelling, advertiser-ID and domain guidance as before.
- **A results page that returns no ads while still reporting that ads matched your target** is now treated as a temporary source problem — an uncharged, re-runnable row — rather than "no creatives currently listed". A page that reports nothing and lists nothing keeps the plain "nothing listed here, try another region" answer.
- **An ad listed without its display details is now reported as re-runnable**, instead of "this creative has no public screenshot to read". Ads that really carry no readable screenshot keep that answer.
- All of the above stay uncharged: nothing is charged for a row that carries no ad text.

### 1.0.19 — 2026-08-29

- **A typo in an input field name now gets a helpful pointer instead of sample rows.** A field the actor does not recognise is ignored by the platform, so an input like `advertizers` used to look exactly like a blank run and came back with the uncharged sample creatives — as if the typo had worked. Such a run now returns one uncharged row that names the field it could not recognise and names the field to use instead (`advertisers`, with the shape to paste). The same is true for settings sent without any advertiser or domain, and for a run whose only problem was a value the actor cannot use (an unknown creative format, a cap below 1) — each now gets its own uncharged pointer row rather than sample rows. Nothing is charged, and a genuinely blank run still returns the two uncharged sample rows exactly as before.

### 1.0.18 — 2026-08-29

Two run-cost fixes — same rows, same prices.

- **An outage of our text-reading service now stops the run cleanly instead of failing creative after creative.** If the service refuses this actor's access mid-run, the creative that hit it ships uncharged with an honest `ocr_service_unavailable` reason, and the run stops right there — no more fetching each remaining screenshot only to fail at the last step. The closing message says the problem is ours and that undelivered creatives were not charged. Ordinary rate-limit blips keep their usual retries.
- **When the Transparency Center soft-blocks a lookup, retries now change route instead of re-asking the same way.** Those retries used to be answered with the same degraded page three times; they now switch to a fresh network route, which recovers real listings more often on busy advertisers.

### 1.0.17 — 2026-08-28

- **Better guidance when a name search finds less than the brand really runs.** A brand name matches exactly one verified advertiser entity, while big brands often run ads under several — searching by `domains` covers every advertiser verified for the domain. The README now says so next to the cost table, the "advertiser not found" row suggests the domain route alongside the AR… ID, and the all-video guidance row notes that a brand's text and image ads may live under other advertiser accounts a domain search would find.
- **The closing status line no longer reads one video creative as two uncharged deliverables.** Guidance and summary rows (the all-video pointer, region notices, no-matching-formats summaries) now have their own count — "N uncharged guidance row(s)" — separate from creatives, and uncharged rows without content name where to look ("see statusReason"). What is charged is unchanged.

### 1.0.16 — 2026-08-28

- **The per-advertiser limit now counts rotated-out creatives too.** The Transparency Center
  constantly re-orders which ads it lists first, so a run resumed after an interruption could
  fail to re-encounter creatives it had already delivered and top the advertiser back up to the
  limit with new ones — more rows in total than the limit asked for. A resumed run now counts
  everything it already delivered for an advertiser, listed again or not, so the limit is exact
  across an interruption.

### 1.0.15 — 2026-08-28

- **An interrupted or resurrected run never delivers or charges the same creative twice.** The
  platform occasionally moves a long run to another server mid-flight, and any finished run can be
  resurrected from the console. Both used to start over from the top — re-extracting creatives the
  run had already delivered and charging for them again. A restarted run now recognises everything
  already in its results: delivered creatives are skipped without a duplicate row or a second
  charge, a charge interrupted mid-settlement is finished exactly once, and only the unfinished
  work runs. The per-advertiser creative limit holds across the interruption too.
- **The uncharged demo can no longer be interrupted by the weather.** With no input, the run used to
  fetch and read two live ads; a hiccup anywhere along that path could fail it. It now returns two
  sample rows — real Amazon text ads this actor extracted, frozen — instantly, fetching nothing.
  Still uncharged, still the same row shape, and the rows now carry a `_demo` marker so chained runs
  can tell them apart from real results.

### 1.0.14 — 2026-08-27

- **A country code we don't support no longer costs you money in silence.** Asking for a region
  like `ZZ` used to be accepted without comment: the run went ahead in a different market and
  charged you for the ads it found there. The run now says so up front — an uncharged row naming the
  code you sent, telling you the run used US instead, and listing every supported code — and
  everything after that is the US market you can see you got.
- **Every row now says whether it was charged.** A new `charged` column is `true` only on rows
  that were actually billed, so a dataset reconciles line by line against your invoice.
- **The uncharged demo run's rows now say they are a demo, in words.** They previously looked like
  ordinary paid extractions.

### 1.0.13 — 2026-08-27

- **A dropped connection while reading a creative no longer ends the run.** If the text-extraction
  service disconnected mid-request, the whole run stopped there and every creative still queued was
  lost. Those failures are now retried, and a creative that still cannot be read comes back as a
  uncharged row giving the reason — the rest of the run continues and delivers.
- **A stalled extraction request now gives up instead of holding the run.** A request that never
  answers is cut off after a minute and retried, rather than sitting until the run hits its own
  time limit.

### 1.0.12 — 2026-08-27

- **Fixed: a run no longer says creatives were "charged" when nothing was charged.** The uncharged demo run — and any run that bills nothing — now ends by saying plainly that nothing was charged, and the number reported as charged is always the number actually billed.
- **Fixed: an incomplete screenshot download is never read as ad text.** A screenshot that arrives short is now retried, and if it is still incomplete the creative comes back as an uncharged row giving the reason. Previously a partial image could be read and charged for.
- **Fixed: a Maximum cost per run that affords exactly one creative now returns that creative** instead of coming back empty.

### 1.0.11 — 2026-08-27

- **Price cut.** An extracted creative now costs $0.015 down to $0.004 depending on your Apify plan tier — the volume price was $0.010. The free-plan price is unchanged, and you are still charged only when content is actually extracted.
- The pricing block in this page now shows the new figures.

### 1.0.10 — 2026-08-27

- Added a plain note that this is an independent actor — unofficial, and not affiliated with or endorsed by Google.
- The directory of other steadyfetch actors now covers every family (ad creatives, trends and keywords, YouTube, Instagram, jobs, Amazon, and general media) and names each one exactly as it appears on the Store, so a link always lands where the name says. It now sits at the end of the page, just above support.
- Added a Feedback & support section, matching the other steadyfetch actors.

### 1.0.9 — 2026-08-27

- The README now has an **Output** section near the top — a sample results table, a screenshot of a real run's output (including an image ad read by OCR), and a link to a permanent live example dataset — so you can see exactly what you get back before spending anything.
- Added a copy-and-paste block for AI agents and LLM clients: actor id, a complete input example, every output field, and the exact event price.
- Added a directory of the other steadyfetch ad-intelligence actors (Facebook & Instagram, Google Ads video, TikTok, LinkedIn, Instagram Reels, Google Trends) with their free n8n templates.

### 1.0.8 — 2026-08-25

- **Fixed: advertisers whose ads are all video no longer return an empty dataset.** Video creatives are now listed as uncharged metadata rows by default, as documented — previously they were skipped unless VIDEO was explicitly included in `formats`. A target whose creatives are all video (or all excluded by your `formats` setting) now also gets one clear uncharged row explaining what happened and what to run instead. Nothing is charged for any of these rows.
- `firstShown` / `lastShown` now arrive as readable `YYYYMMDD` dates (as the README always showed) instead of raw timestamp objects.
- Uncharged rows about a whole target (like `advertiser_not_found`) now carry the advertiser/domain you asked for in the `advertiserName` column, so multi-target runs are easy to audit.

### 1.0.7 — 2026-08-21

- README: added a **Related actors** section — the video-transcript companion for what an advertiser's video ads say, plus the same pipeline on other ad platforms and their free n8n chaining templates. No change to pricing or behavior.

### 1.0.6 — 2026-08-21

- Billing accuracy improvement for runs with a **Maximum cost per run**: capped runs now deliver every creative the cap affords — a rounding quirk could previously stop one creative early.

### 1.0.5 — 2026-08-20

- Initial release: extracts what Google ads actually say — headline, body, CTA, display URL, and all visible text — from Ads Transparency Center creatives (TEXT and IMAGE formats), by advertiser name, advertiser ID, or domain, region-aware.
- Video creatives are listed with their metadata uncharged; you are charged only when a creative's content is extracted. Missing or unreadable screenshots arrive as uncharged rows with the reason stated.
