# Changelog of Google Ads Video Transcript Scraper — YouTube Ads to Text (`steadyfetch/google-ads-video-transcript-scraper`) Actor

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

## Changelog

### 1.0.96 — 2026-09-16

- **A video that came back with nothing now says WHY on the run page, and the row hands you a move instead of quoting our own limit.** A handful of outcomes still share one "uncharged miss" line — a video whose audio is over the per-video data limit this actor will fetch, one with no captions that is too long for speech-to-text, and a run where advertiser lookups were disallowed at the source — and on a run where every video landed there the page said only "1 attempted, all answered uncharged (see the rows)". From this build it names the cause: "1 attempted, all answered uncharged: over the per-video data limit (see the rows)". On a busy run where the line would not otherwise fit, that census is the first thing dropped — never a count, never a cap, never a charge statement — because every row carries the same reason in full. The row for a video over the data limit now also says what it means: the link and title on the row are what was asked for, an ad carrying a caption track is read from the captions and is never stopped by the limit whatever its size, a shorter cut of the same ad is the version that fits, and re-running the same link on its own returns the same answer. Nothing about money moved: the same videos are answered, still uncharged, and no price, input field, output column or charged event changed.

### 1.0.95 — 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 ads again next run.** There are two ways a scoped API token can break the promise that you never pay for the same ad 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 ad you already had is recognised and handed back uncharged exactly as it should be, and then every attempt to write this run's ads 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 when the run page's 500-character limit would cut the sentence about your money, the "open an issue" line now gives way first — four ordinary shapes of run were losing the end of their status line before this build.

- **A run started with a scoped API token now says so, instead of quietly charging you for ads 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 ad 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.94 — 2026-09-13

- **The store card now says what a transcript costs.** The listing description named every way you can point this actor at ads and left the price to the page — so a buyer comparing actors in the search results could not compare this one on price. The card now carries the same headline the page does: from $8.00 per 1,000 video ad transcripts, the cheapest paid rate on the ladder, and no start fee. The list of input routes is shortened to make room; the input form still carries all of them. Nothing else moved: no input field, output column, charged event or price changed.
- **The table of sibling actors now names the Google Ads listing by its current title.** That listing is called Google Ad Copy Scraper — Ads Transparency Center, CTAs & OCR; it was renamed so its name says what it returns — the text of an ad rather than "ads" in general. The link here points at the same actor as before; only the words a reader sees changed.

### 1.0.93 — 2026-09-13

- **Pressing Start with nothing set, or with only a setting or two set, now comes back with the number of transcripts the sample promises — as long as there are enough speech-bearing ads to draw from.** The sample already looked at a couple of extra ads so a run of music-only ones could not leave you empty-handed. What it did not do was use them: while ads were still being worked on, the spare ads were being set aside for good rather than held, so a sample whose first picks turned out to have no speech came back short — two transcripts where it promised three, and one where you had asked for two. The spares are now held until the ads in hand have answered, and used only if they are still needed. Everything around it is unchanged: the sample still looks at no more than two ads beyond its own count, still charges only for transcripts actually delivered, still returns an uncharged row for an ad with no speech, and a run where you name your own advertisers, domains or video IDs is untouched.
- Nothing else moved: no input field, output column, event or price changed.

### 1.0.92 — 2026-09-13

- **Pressing Start without naming an advertiser now comes back with transcripts, not with an empty result.** With nothing named, the run shows you a small sample of one brand's video ads — and it used to look up exactly as many ads as it meant to deliver, whatever those ads turned out to be. Plenty of video ads carry music and no speech, and when the ones it happened to pick were all of that kind, the sample came back with uncharged "no speech" rows and no transcript at all. The sample now looks at a couple of extra ads and stops as soon as it has the transcripts it set out to deliver. It still delivers the same number of ads it always promised, it still charges only for transcripts actually delivered, and an ad with no speech still comes back as exactly that, uncharged. The extra look is strictly bounded: at most two ads beyond the sample's own count, so a sample can never go hunting. A run where you name your own advertisers, domains or video IDs is untouched — "Max items" still counts ads, exactly as before.
- Nothing else moved: no input field, output column, event or price changed.

### 1.0.91 — 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 delivered video ad transcript costs on the Apify free plan and on a paid one with the arithmetic for a thousand ads, lists what is never charged, names the run option that caps the bill, and says where an ordinary media file or a Facebook Ad Library link belongs instead. **Advertisers** now leads with the value shape and says in its second sentence that finding advertisers is never charged; the "leave it empty and press Start" instruction moved to the end, where it was always meant to be for a human and was reading first for a machine.
- Nothing else moved: no input field, output column, event or price changed.

### 1.0.90 — 2026-09-12

- **A run chained to a dataset that turned out to be empty now says so, instead of finishing with nothing in it.** If you pointed **Dataset ID** at a run whose results were empty, this actor read the dataset, found no video ads in it, and finished with an empty dataset and no sentence anywhere saying what had happened — the same ending a broken run gives. You now get one uncharged row saying the dataset was read and held no video ads, and suggesting the two things that fix it: check that the run you chained finished with results, or paste the ad links directly. Nothing was charged for those runs before and nothing is charged now. The run's own summary also records the dataset as the one thing you asked about, so a run like this is no longer invisible in our own records.

### 1.0.89 — 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.88 — 2026-09-12

- **The run now holds its minimum gap before every request, not just the first one.** We keep a small pause between calls to YouTube so a run never arrives as a burst — but it was being applied only to the ad's lookup call, while the subtitle fetch and the audio download went out with no gap at all, so the pause covered one of the three requests an ad makes. All three now read the one clock. Nothing about your input, your rows, your columns or what you are charged changes — this is only about how often we knock.
- **This actor has a new title on the store: Google Ads Video Transcript Scraper — YouTube Ads to Text.** 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.
- **Asking for a bigger cap than the actor can do no longer stops the run before it starts.** "Max video ads to process" refused anything above 5,000 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.
- Nothing else moved: no input field, output column, event or price changed.

### 1.0.87 — 2026-09-11

- **AI agents can now pin this actor from the top of the README** — the pin link, the actor id, the one input field to start from and how to cap a run's bill are on the first screen of the page instead of far down it.
- **Otherwise a maintenance build: nothing about your runs changes.** The private run-report this actor writes for our own support — counts only, never anything you typed — now also records the per-event price your plan was billed at, and whether a run that stopped at your **Max video ads to process** had actually filled it. Nothing a run does, delivers or costs is different.
- Nothing else moved: no price, event, input field or output column changed, and only delivered transcripts are charged.

### 1.0.86 — 2026-09-11

- **Paste a video file that is not on YouTube and you are now told which of our actors does read it.** This actor transcribes Ads Transparency Center video ads hosted on YouTube, so a link to a media file you host somewhere else — your own storage, your own CDN, a file an agent already downloaded — comes back as an uncharged row. It always did. What it did not do was say where that file CAN be transcribed, so the answer was a dead end: correct, and useless. From this build that row names our media transcriber, which reads an audio or video file from any host, and the exact field to paste the link into. YouTube and Transparency Center links are unchanged — watch URLs, bare 11-character IDs and ad page links are all still looked up, and an advertiser page still gets its own answer. Nothing was charged for these rows before and nothing is now.
- Nothing else moved: no price, event, input field or output column changed, and only delivered transcripts are charged.

### 1.0.85 — 2026-09-10

- **A made-up closing line after the end of the audio no longer hides a real transcript — and is never delivered.** Speech recognition sometimes invents a sign-off, most often "Thank you.", and stamps it with a time that falls past the end of the ad. When that stamp landed far enough out, the whole transcript was judged unreliable and the ad came back marked as having no speech: nothing delivered and nothing charged, even when there were 25 seconds of clear speech on it. It was found on a real customer run of our Facebook ads transcriber — a 30-second property ad with 25 seconds of clear voiceover that came back with nothing at all — and the same speech step runs here. From this build the invented line is removed before anything else is decided, so the real speech is delivered and charged exactly as usual — and the invented line is taken out of the transcript, the timed cues and the opening-line field too, so you are never handed, or billed for, a sentence the audio does not contain. The same invention was also slipping through quietly at the end of transcripts that DID deliver; it is gone from those as well. The length we measure for billing is unchanged.
- **An ad that came back empty because of that fault is tried again, instead of being handed back with the same empty answer.** Once an ad has been delivered to your account we never charge you for it twice, and to do that we remember what every ad answered. The trouble was that the memory also remembered the EMPTY answer above — so re-running the same input handed the same nothing straight back from your own record, without looking the ad up at all. It was found on a real customer run of our Facebook ads transcriber, five runs in a row against the same ad, and the same memory works here. From this build, an ad whose remembered answer was "no spoken words" is looked up again the first time you run it on a newer build, and charged once if it then delivers a transcript. It could never have been charged before, because an empty answer is never billed. An ad ALREADY DELIVERED to you is untouched: it still comes back uncharged and is never charged a second time.
- Nothing else moved: no price, event, input field or output column changed, and only delivered transcripts are charged.

### 1.0.84 — 2026-09-10

- **A run that hits a block now waits a length that fits your run, instead of skipping the wait entirely.** When the video platform challenges every session, the run pauses and climbs the whole ladder again on brand-new sessions. 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 block that lifts quickly costs you far less waiting. A short run, a small `maxItems` and the untouched Start form now all get a real second climb where they used to get none. The same change applies to the Transparency Center list step.
- **The waiting is also sized to what is still worth recovering**, so a run with only a couple of video ads left to fetch 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.
- **We can now see WHY a run came back short.** Every uncharged row already told you what happened to that ad; our own run record folded them all into one number, so an advertiser name that matches nothing, a target with no video ads, a video YouTube has taken down, a music-only ad, a Transparency Center that refused the lookup and our own speech service failing all looked identical to us — and a problem worth fixing could sit unnoticed. Each is recorded separately now. Your rows, their wording and what you are charged are exactly as before.
- Nothing else moved: no price, event, input field or output column changed, and only delivered transcripts are charged.

### 1.0.83 — 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, input field or output column changed, and only delivered transcripts are charged.

### 1.0.82 — 2026-09-09

- **A list of ad links sent under another transcript actor's field name is now read instead of turned away.** Our transcript actors do not all call their link list the same thing, and a run that arrived here with `urls` — the name the media transcriber uses — used to come back with a single row saying that is not a field, having transcribed nothing. Those links are now read as **Ad video URLs, IDs, or ad page links** (`videoUrls`), the run goes ahead exactly as if you had used that name, and one uncharged row says which name you used and which one this actor has. Nothing was renamed: every field this actor publishes still means precisely what it meant, and using `videoUrls` simply leaves that note out. A field name belonging to a different platform — an Instagram or TikTok target list — is still declined with one row naming the fields that do work here, because reading it could only produce a long list of refusals.
- **The output page now says how to pick out your delivered rows.** Filter on the transcript itself rather than on a status word: rows with a transcript are exactly the rows charged. Status words are labels for people and this actor adds new ones as it learns to explain more, so a filter pinned to one word can quietly go short over time. The statuses that carry no result fee are named on the output tab, the two uncharged notes included.
- Nothing else moved: no price, event, input field or output column changed, and only delivered transcripts are charged.

### 1.0.81 — 2026-09-08

- **An ad you re-run keeps the answer it actually got.** When an ad comes back to you from the account memory — one this account already asked about — the row used to say "Already delivered to your account", whatever the first run had found. On an ad nobody speaks in, which was never transcribed at all, that was simply wrong, and it wiped out the sentence explaining why. Those rows now keep their own reason word for word and add one honest line saying it is the same answer as before and nothing was charged again. An ad that really was transcribed still says it was already delivered. Re-running now shows you at least as much as running once.
- Nothing else moved: no price, event, input field or output column changed, and only delivered transcripts are charged.

### 1.0.80 — 2026-09-07

- **A music-only or silent ad is no longer charged as a transcript.** When an ad carried no speech, the transcriber sometimes invented a short caption for it — "Outro Music", "Música", "The End" — and that invented caption was delivered and billed as if it were the ad's own words. Short ads were where it happened: below fifteen seconds the only thing standing between an invented caption and your bill was a list of exact phrases, and anything not on the list went straight through. An ad whose transcript is nothing but a sound or a card is now recognised as such at any length and ships as an uncharged no-speech row — no result fee, exactly as the store page says.
- **A short ad that really does speak is still delivered.** Speech is now weighed against the part of the ad the words actually cover rather than the whole of its length, so a six-second ad carrying a three-word line reads as speech and not as silence. Short spoken ads that used to come back as "no speech" are delivered.
- **An ad in any language is delivered in every market.** A Russian-, Ukrainian-, Chinese-, Japanese-, Korean- or Arabic-language ad running in the US, UK, Canada, Australia, Ireland or New Zealand used to be treated as unreliable, returned with no transcript and no result fee, and remembered that way for the next run. That rule is gone. An ad is transcribed and delivered in whatever language it speaks, wherever it runs.
- Nothing else moved: no price, event, input field or output column changed.

### 1.0.79 — 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 video-file download and the ad-library lookups used to demand the other kind, get "no such address" back, and answer as though the ad or the file were gone. 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 comes back with what actually happened instead of a verdict that was never true. Nothing was ever charged for either row.
- Nothing else moved: no price, event, output column or charge changed, and only delivered transcripts are charged.

### 1.0.78 — 2026-09-06

- **A block of links pasted into one row is now read as the list you meant, whatever separates them.** "Ad video URLs, IDs, or ad page links" and "Advertisers" each take one entry per row. A whole block pasted into a single row was read as one very long address, so a list of fifty ads came back as a single row saying it was not a YouTube watch link. A pasted block is now split back into its individual links whether they are separated by spaces, line breaks, tabs, commas, semicolons, pipes or nothing at all, so a column copied straight out of a spreadsheet works. Each one is then read, deduplicated and charged on its own, exactly as if you had pasted them one per row.
- **A link that carries another link inside it is still one link.** An address holding a second address in its query or its path is left whole, not broken in two, and a single link pasted on its own is never rewritten.
- **Text pasted around a link no longer breaks it.** A number, a bullet or a note sitting beside a link is ignored and the link itself is used. A row holding no link at all is still answered as one row, not one row per word.
- Nothing else moved: no price, event, output column or charge changed, and only delivered results are charged.

### 1.0.77 — 2026-09-06

- **Every advertiser, domain and ad link the run timeout never reached now gets its own row.** When the run hit its time limit part-way through looking up the targets you named, the ones behind that point simply vanished — no row, no reason, nothing in the count. The wave stop was fixed for exactly this last build; the time limit was not. 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.
- **Every ad the run timeout left untranscribed now gets its own row too** — both the ads the clock never started and the ones whose transcription could not finish inside the run. They were counted on the run page and absent from your dataset.
- **The rows say how to get the rest:** raise the run timeout, or split the targets across runs, and re-run. They are flagged `retryable`, so you can filter exactly what a re-run would bring back.
- **Nothing is charged for these rows** — nothing was looked up, downloaded or transcribed.
- **The run's own record now counts them**, so a run cut short by the clock reports what it asked for, what it delivered, and every unit the clock stopped, instead of leaving the difference unexplained.
- **Nothing about pricing, events, input or output columns changed.**

### 1.0.76 — 2026-09-05

- **A blocked advertiser lookup is now waited out, not reported.** Every advertiser, domain and ad link this actor works on has to be looked up in the Transparency Center first, and when the Center refused those lookups the run gave up on that target in a few seconds and moved on — while nearly all of the time you had paid for went unused and the transcription step below it never ran at all. A run that still holds time now waits for the refusal to lift (a minute and a half to three minutes, varied so parallel runs do not retry in step) and asks again, sharing one waiting budget with the video step so a run can never spend more than eight minutes waiting in total. Everything that is an answer rather than a refusal still comes back at once: an advertiser the Center does not list, a domain with no verified advertiser, an advertiser with no video ads.
- **Every advertiser and domain you name now gets its own row.** When the Center stopped answering three targets in a row, the run stopped asking — and the targets behind that stop simply vanished: no row, no reason, nothing in the count. Each of them is now answered with its own uncharged row saying it was not attempted and that a later run can retry it, so a list of ten is reported as ten. Nothing extra is asked of the Center for them, so this costs nothing.
- **A run cut short this way now says so.** The run record used to claim the target list had simply run out; it now names the refusal as what ended the run, which is what tells a short run apart from a finished one.
- Nothing you see changed otherwise: no price, event, output column or charge moved, waiting is uncharged, and only delivered transcripts are charged.

### 1.0.75 — 2026-09-05

- **The run record now adds up.** At the end of every run the ads it planned are accounted for one by one: transcribed, or answered with a named uncharged reason, or explained by the setting that stopped the run — and those three always add back to the number the run asked for. Several things could break that sum before. An advertiser or domain search recorded its ask as your **maxItems** rather than the ads the Transparency Center actually listed, so a search that listed eighty ads under a limit of a hundred left twenty ads named by nothing, and a limit of seven recorded an ask of seven against the thirty-three ads the limit trimmed. A music-only ad was counted twice, once as an uncharged miss and again as a silent one. An advertiser a refused country setting stopped kept its row but no reason of its own. Ads your "new ads only" setting cut, ads left behind when the platform moved the run to another server, and ads an earlier part of a moved run had already delivered were all missing from the count. And a run that stopped early never wrote down *why*.
- **A run that ends early now says which setting or event ended it** — your maximum ad count, your maximum total charge, the run timeout, a move to another server, a resumed run that could not read back its own earlier results, and a "new ads only" setting the run could not honour are each recorded under their own name, so a short run can be told from a run that simply finished the list.
- **An advertiser or domain a country setting stopped now carries that field as its reason.** It already came back as its own uncharged row saying it was not searched; now the run record names the field too.
- Nothing you see changed: no price, event, output column or charge moved, and the rows a run writes are the same rows.

### 1.0.74 — 2026-09-05

- **A run that waits now always keeps room to finish and report.** When YouTube walls every session for a video, the run can pause and climb the whole session ladder again — but the check that decided whether a pause was affordable kept back only one session's worth of time for that second climb, while a climb is three sessions and their cooldowns. On a run whose time limit fell in a narrow band (roughly seven and a half to nine minutes, including a shorter limit you set yourself), the pause was allowed and the climb 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 climb, measured from the ladder's own sessions, request timeouts and cooldowns, so a pause is only taken when the run can finish the climb the pause was for.
- **Charges are unchanged.** Waiting is uncharged, a walled video is uncharged, and only delivered transcripts are charged.

### 1.0.73 — 2026-09-05

- **A blocked video is now waited out instead of reported.** When the video platform challenged every connection this run tried, the run gave up about three minutes in and asked you to re-run — while nearly all of the time you had paid for went unused, and a re-run started minutes later usually met the same block. A run that still has time now waits for the block to lift (a minute and a half to three minutes, varied so parallel runs do not retry in step), then tries again on brand-new connections. Everything that is an answer rather than a block comes back at once, exactly as before: a removed video, an ad with no fetchable media, an advertiser the Transparency Center does not list, a creative bigger than the per-item limit.
- **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 single blocked attempt — so on the run most people see first, the waiting above could never have happened. The limit is now sized to hold a blocked attempt, the wait, a second attempt and the time needed to write the results: ten minutes at its very worst, while a sample that is served still finishes in well under two.
- **The default sample always runs live.** It is no longer answered from the record of ads your account already has, and its ads are not added to that record: a bare Start looks the ads up, transcribes them and charges for them like any run, every time. Pressing Start twice therefore pays for the sample's ads twice — a few cents — and in exchange the sample always shows current ads and always proves the actor is working. Any run that names its own advertisers, domains, ad links, dataset or video URLs is unchanged: ads your account already has still come back from the run that delivered them, uncharged.
- No row, price, event or output column changed.

### 1.0.72 — 2026-09-05

- **The default sample no longer refuses an uncaptioned ad as unfinishable.** An ad without captions is transcribed from its audio, and since the run-clock checks were added that leg only starts when the run has enough time left for its worst case. The bare-Start sample (nothing set, or only a setting changed) runs under a short three-minute window, and that window was shorter than the worst case — so on an account that did not already hold the sample's ads, an uncaptioned one was silently skipped instead of transcribed. The worst-case check now reads the run's real time limit; the three-minute window still bounds how long the sample keeps starting new ads. Runs that name their own advertisers, domains or ad links were never affected; captioned ads never were.

### 1.0.71 — 2026-09-05

- **A blocked video now says what to do, and every "re-run" row carries `retryable: true`.** When the video platform challenged a fetch on every connection this run tried, the uncharged `blocked` row stated the fact and stopped; it now says to re-run (a later run is usually let through). The same row, and every other row whose note says to rerun — an advertiser, domain or ad link the Transparency Center did not answer for, a creative preview or video that could not be fetched right now — now carries `retryable: true`, so you can filter on that field to feed a re-run. A permanent answer (no such advertiser, no video ads listed, a removed video, an ad with no fetchable media) is unchanged and carries no re-run flag.
- **A run near its time limit no longer retries the Transparency Center past it.** When the Center is slow or briefly refuses a request, this actor retries a few times with a short pause between tries. Those retries now watch the run's own clock, so a run that reaches its limit ends with the transcripts and rows it already has instead of being cut off part-way through a retry.

### 1.0.70 — 2026-09-04

- **Change only a setting, name nothing, and the run now goes ahead.** Picking a region, a cap, "Advertisers to open per name", "New ads only" or a watchlist name and pressing Start without naming an advertiser, domain or ad 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-ad 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 region it cannot place, or a list you sent empty (`advertisers: []`, `domains: []`, `datasetId: ""`) still gets its uncharged row naming the fix — a mistake is never answered with sample ads.
- Pressing Start with nothing set at all is unchanged: the same 3-ad sample of a well-known advertiser's US video ads.

### 1.0.69 — 2026-09-04

- **A region this actor cannot search is never quietly swapped for another market.** `region: "CH"` used to enumerate advertisers in the US and charge you for US ads, with only a note to say so. The advertiser and domain walks now stop before anything is fetched and return uncharged rows: one naming the `region` field, the regions that can be searched and which of the three things went wrong (a market with no Transparency Center region here, a whole-region ask, or a value nothing could place), plus **one row per advertiser and domain** saying it was not searched. Ad links you pasted carry their own ad and need no market — they are transcribed exactly as asked.
- **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 ad links, the advertiser field, a watchlist name — instead of one undifferentiated "input error". The field NAME only; nothing you typed is stored.
- 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. It previously reported the number of ads the run got as far as attempting, which is 0 on exactly the runs worth looking at.
- **The status line fits the run page again.** A bare Start whose ads your account already had came out at 524 characters and the run page cuts at 500, so the closing support line was lost. The same facts are now said in fewer words.
- The store page and the Changelog tab no longer describe 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.66 — 2026-09-04

- **An ad already delivered to your account is never charged a second time.** Re-running the same video links, advertisers, domains or chained rows used to look up and charge every ad again. The run now recognises an ad your account already has — by its YouTube video ID or its Transparency Center creative ID, however it arrived last time — and hands it back from the run that delivered it: `repeat: true`, `firstSeenAt`, `firstSeenRunId`, `charged: false`, not looked up or transcribed again. The status line counts them and `OUTPUT.repeats` holds the number. Your account keeps this memory in the key-value store `google-ads-video-watch-account`; delete it to forget everything.
- **New ads only works without a watchlist name.** It compares against your account's memory, inside the advertiser walk too; a named watchlist still keeps a separate list per advertiser set. Only when neither can be read does the run stop, uncharged, instead of charging you for ads 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 ads on it are processed again once, not charged, and kept from then on.
- **The run's status line fits the run page again.** It was long enough to be cut off, losing the cap guidance and the support line at the end; the wording now says the same things in fewer words, and a few row outcomes the page called "free" are named "uncharged", which is what they are.

### 1.0.65 — 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.63 — 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.62 — 2026-09-04

- **A problem on the transcription service's side is no longer reported as a problem with your ad.** When that service refuses for a reason of its own, the ad stays alive and its row says plainly to re-run it. A file this actor genuinely cannot transcribe still ends as an honest, uncharged miss that says so.
- **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.61 — 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.60 — 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.
- **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.59 — 2026-09-04

- **The console's input description no longer calls the no-speech verdict "free"** — it is uncharged; the run still uses platform time.

### 1.0.58 — 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.57 — 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.55 — 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.53 — 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.52 — 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.51 — 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.50 — 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.49 — 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.48 — 2026-09-02

**Start with the default form and get a real 3-ad sample instead of a placeholder row.**

Clicking Start with nothing set used to return one uncharged "demo" row and no transcript. It now runs a small real sample — a well-known advertiser's US video ads, up to 3 — transcribed and charged like any run, so the first thing you see is the actual output. The status line says it was the sample and how to run your own search. If the Transparency Center does not answer at that moment, you still get one uncharged row explaining it, never an empty result.

### 1.0.47 — 2026-09-02

**Watchlists: re-run the same advertisers on a schedule and pay only for the ads 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 ads again. Two new fields change that:

- **Watchlist name** (`watchlistId`) — give the run a name such as `meal-kits` and the actor remembers every ad it has answered under that name, in a key-value store in **your own** Apify account (`google-ads-video-watch-<name>`, yours to inspect or clear). Every row now says whether the ad is new to that list (`isNew`) and when it was first seen (`firstSeenAt`).
- **New ads only** (`newAdsOnly`) — with a watchlist set, ads already on the list are skipped **before anything is fetched**: a known video never becomes a candidate, so it is not looked up, not transcribed and not charged. 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 an ad the actor actually answered goes on the list — a delivered transcript, or the final uncharged no-speech verdict. An ad it could not answer (a removed video, a blocked lookup, a media-less creative, an ad your cost cap left out) 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 an ad you paid for.

Two safety rules: **New ads only** without a watchlist name does not run at all (the actor cannot tell a new ad from one you already paid for), and a watchlist the run cannot open stops the run before any spend. 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.

The README was refreshed at the same time: what you get and the price up front, a scheduling recipe for the watchlist, the one-click MCP link, and the suite links carry the actors' current names.

### 1.0.45 — 2026-08-31

**Long video ads no longer come back as `failed_processing`.**

On the branch where this actor has to pull audio out of the ad's own media file, it gave itself a fixed two minutes — no matter how long the creative was. It was set once, for short ads, and never grew with the video, so a long-form creative was stopped partway and reported as a broken file. YouTube ad inventory is full of long-form creatives, so this was live exposure. A sibling actor on Facebook inventory measured it on a real run: every creative past roughly twelve minutes was lost, and nothing under five minutes ever was.

- **The time budget is now earned by the video's own length**, up to a generous ceiling, and it is also held inside what is left of your run.
- **A silent creative is recognised before any extraction work starts** — the uncharged no-audio row is reached from the file's own stream list rather than after a failed attempt.
- **"Ran out of time" and "this file is broken" are now two different answers.** A run that ran out of time ships an uncharged `failed_processing` row whose reason names the lever: re-run it with more run memory, which on this platform also buys more CPU.
- A stopped extraction can no longer leave a half-finished audio track behind, so a partial transcript can never be delivered or billed as a whole one.

Nothing about short creatives changed, and no failed row was ever charged.

**Follow-up in the same wave:** the first cut of this change computed the new time budget as a fraction of a millisecond, which the underlying process launcher refuses. Only durations between the fixed lower and upper bounds could produce a fraction, so the very short and the very long creatives were unaffected — but a creative in the middle of the range came back as a download failure, and its media was re-downloaded twice more before the run gave up. The budget is now always a whole number, and anything that stops the extraction from starting is reported as an extraction problem rather than a network one: one attempt, one honest uncharged row.

### 1.0.43 — 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.maxItems 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"], "region": null, "maxItems": null}` runs exactly like `{"advertisers": ["HelloFresh"]}`: the US market, the standard 100-ad cap. A `null` never counts as a value — in particular a null **Region** is plain `US`, not an unrecognised region, so a templated run can no longer collect a "not a supported country code" row on every ad it returns.

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

### 1.0.42 — 2026-08-31

**The opening hook is never blank again, and it now tells you when it starts.**

`hook3s` is the first 3 seconds of speech in an ad. Plenty of ads — brand films, long title cards, anything that opens on music or a logo sting — say nothing at all in those 3 seconds, and until now those ads came back with the hook column **empty**: exactly what you would see if the actor had simply failed to get the line. On a charged row, that was our headline field arriving blank.

- **A late opening line is delivered, not dropped.** When nothing is spoken in the ad's first 3 seconds, `hook3s` now carries the first 3 seconds of speech from wherever speech actually begins — the real opening line.
- **New `hookStartSeconds` column.** The second that line starts, so a late hook can never be mistaken for one that opened the ad. `0` means the ad opens speaking. Sort by it to see which competitors make you wait for the pitch.
- **No more blank strings.** An ad with no speech at all leaves both fields empty (`null`), so "this ad opens silent" and "we lost the line" can no longer look identical in your spreadsheet.

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

### 1.0.41 — 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 demo row and every uncharged "no speech" / "removed ad" / "please re-run" row would quietly have been billed to you as ordinary results. 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 and OUTPUT record instead of being written, and a delivered row correctly says it was charged. On this actor's real, named-event pricing — one charge per delivered transcript, misses uncharged — absolutely nothing changes.

### 1.0.40 — 2026-08-31

Copy fix on 1.0.38's new status line: when a run opens several same-name advertisers, the line said all of them "were searched" even when `maxItems` or your max charge 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.39 — 2026-08-31

Maintenance build, finishing 1.0.38's download fix: the same reachable-route rule now also covers the lookups this actor makes from its own servers — advertiser and ad-page lookups, and the fetches that find an ad's video. Before, a host that advertised an address family our servers cannot reach could turn three attempts into one and end an ad with a "please re-run" row that a re-run could not fix. Nothing about what this actor delivers or charges changed.

### 1.0.38 — 2026-08-31

**An advertiser name now resolves to the advertiser that actually runs the ads — and can open more than one of them.**

- **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 verify or list first, which for "Nike" is a private individual with one ad. 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 — useful for brands that split their ads across several advertiser accounts. The run's status line names exactly which advertisers were opened and roughly how many ads each has. Finding advertisers is never charged, and an ad listed by two of them is still transcribed and charged once.
- **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 downloads fail for a reason a re-run could not fix.** Some video hosts advertise an address family this actor's servers cannot reach; those downloads now take the reachable route instead of failing and asking you to re-run.

### 1.0.37 — 2026-08-30

The long-video surcharge is now spelled out in the input form. The **Max video ads to process** field's description now says what the Pricing tab and this page already did: the first 3 minutes of every video are included in the transcript price, and each started minute beyond that is charged as a "Long-video minute (surcharge)" at $0.005, only on delivered transcripts. Nothing about what this actor delivers or charges changed.

### 1.0.36 — 2026-08-30

Maintenance build: the `no_speech` row's explanation is now a proper second sentence after the outcome (1.0.35 ran the two together). Nothing about what this actor delivers or charges changed.

### 1.0.35 — 2026-08-30

**Advertiser names resolve in your region first, miss rows carry the ad's title, and the status line reconciles against maxItems.**

- **Region-scoped advertiser lookup.** An advertiser name is now matched among advertisers registered in the run's `region` first; only when no such advertiser exists does it fall back to the global match — and the status line says so ("advertiser "X" has no US-registered match … resolved to Y (DE) instead").
- **Titles on uncharged rows.** `no_speech`, `blocked` and every other miss now carry the video's `title` whenever YouTube exposes it, so a miss is never a bare video ID.
- **A status line you can reconcile.** Every attempted ad is counted exactly once against `maxItems` (an uncharged miss counts as an attempt; rows answered before any attempt are told apart), and the cap that bound the run is named — `maxItems (N)` or your max charge amount.
- **Silent ads explained.** `no_speech` rows now state the shape: music-only and silent creatives are common for big-brand video ads that carry their message in on-screen text, which this actor does not read; the row ships uncharged with the ad's title, advertiser and video link.

### 1.0.34 — 2026-08-30

Maintenance build: the no-speech safeguard introduced in 1.0.33 is now the same shared machinery as our other transcript actors, and one no-speech reason reads slightly differently ("no reliable speech was recognised in the audio"). Nothing about what this actor delivers or charges changed.

### 1.0.33 — 2026-08-30

**A video with no real speech is never charged.** A transcript that is only the speech engine's own filler — including the subtitle-credit lines it sometimes invents on music-only ads — one whose timing does not fit the audio, or one in a language that cannot belong to the ad's market, now ships as an uncharged no-speech row. Genuinely spoken ads, in any language, are delivered and charged exactly as before.

### 1.0.32 — 2026-08-30

Steadier memory use on large video creatives, and two download honesty guards. Nothing about what this actor charges changed.

- **Very large video creatives no longer tax the run's memory.** A downloaded video now flows straight to disk and is removed the moment its audio has been extracted, so a batch with long or heavy creatives runs in steady memory instead of growing with the largest file.
- **An oversized video is stopped mid-download** instead of being downloaded whole and then refused — the same per-item data limits apply, they just bind before the bandwidth and memory are spent. This also covers the speech route's audio downloads.
- **A video download that ends partway is retried, and never transcribed as if it were the whole ad.** If the retry also fails, the row ships uncharged and says the download was incomplete.
- Caption reading is marginally faster on long videos.

### 1.0.31 — 2026-08-30

More ads deliver on the first run. Four situations that used to ship an uncharged "please re-run" row now get a proper second look before anything is given up. Rows that still cannot be delivered stay uncharged and still say so plainly, and nothing about what this actor charges changed.

- **A busy transcription service is retried on the audio already in hand** — three attempts with a growing pause between them, and when the service says how long to wait, the run waits exactly that long (never more than a minute).
- **A YouTube bot challenge gets a third fresh attempt**, with a short pause between attempts instead of re-asking straight away.
- **An unreadable answer from the video service is retried the same way** instead of ending the ad on the first one.
- **A media link that briefly did not answer gets a short pause before its second attempt.**

### 1.0.30 — 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.29 — 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.28 — 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.27 — 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 chained dataset is read in full, and a cut is always announced.** Chained datasets larger than one page no longer lose rows beyond the first page: everything up to the 10,000-row ceiling is read, and a dataset holding more than that gets one uncharged row saying how many rows this run processed and how to get the remainder.

### 1.0.26 — 2026-08-29

- **A resumed run cannot bill you twice for the same transcript.** When an interrupted run picks up where it left off, it reconciles what was already charged. If it cannot confirm those numbers, it now charges nothing and leaves the settlement for later, instead of guessing from a count that may be stale — and if the run's own delivery record comes back missing rows it has already charged for, the run stops cleanly rather than re-delivering and re-charging them.
- **A run revived after its own processing window has already ended now says so and stops clean.** Nothing further is fetched and nothing further is charged — instead of a stall that suggested raising a timeout that could not have helped.
- Clearer wording on uncharged no-speech rows: an ad whose audio turns out to be music-only or silent now says exactly that in `statusReason`, in plain language. The same ads are recognised as before, and they are still never charged.

### 1.0.25 — 2026-08-29

Ad previews are checked before they are read, so a blocked preview no longer reads as an ad with no video.

- **A preview that comes back as a block page, a consent page, an error page or a cut-short download is now reported as a temporary fetch problem you can re-run** — instead of "this ad exposes no fetchable media", a permanent-sounding verdict that told you not to bother with that ad again.
- **An ad whose listing arrives without its preview details is likewise reported as re-runnable.** Ads that really are screenshot or iframe ads keep the straightforward "no video to transcribe" answer.
- **A results page that returns no ads while still reporting that ads matched your target** now ships an uncharged, re-runnable row rather than "no video ads currently listed for this target".
- All of the above stay uncharged: nothing is charged for a row that carries no transcript.

### 1.0.24 — 2026-08-29

- **A typo in an input field name now gets a helpful pointer instead of a sample row.** A field the actor does not recognise is ignored by the platform, so an input like `advertiser` used to look exactly like a blank run and came back with the uncharged demo row — as if the typo had worked. Such a run now returns one uncharged row that names the field it could not recognise, names the field to use instead (`advertisers`, with the shape to paste) and the other ways in. Nothing is charged, and a genuinely blank run still returns the demo row exactly as before.

### 1.0.23 — 2026-08-29

- **A speech-to-text outage on our side no longer makes the rest of the run pay for it.** If the transcription service refuses this actor's access mid-run, ads that would need speech-to-text now come back immediately as uncharged `asr_unavailable` rows that say plainly the problem is ours — instead of each one downloading its video first and then failing one by one. Ads with captions keep delivering normally, and ordinary rate-limit blips keep their usual retries.

### 1.0.22 — 2026-08-28

- **An interrupted or resurrected run never delivers or charges the same ad 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-transcribing ads the run
  had already delivered and charging for them again. A restarted run now recognises everything
  already in its results: delivered ads 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.
- **`maxItems` now counts the whole run, not the current attempt.** A run moved to another server
  mid-flight could previously deliver up to twice the limit; the limit now holds across the
  interruption.
- **Runs stop cleanly at the timeout instead of being cut off.** An ad started close to the run
  timeout could blow through it mid-download or mid-transcription, ending the run in a TIMED-OUT
  state with no closing summary. Such ads are now left unstarted — uncharged, counted in the
  "stopped before the run timeout" tally — and the run finishes with its usual honest summary;
  re-run, or raise the run timeout, to pick them up.

### 1.0.21 — 2026-08-27

- **A capped run now looks at more candidate ads than its budget strictly pays for.** Removed, music-only and media-less ads are uncharged and hand their budget straight back, so a run that lined up exactly as many candidates as its cap covered could finish with money unspent and nothing left to spend it on. It now keeps a cushion of candidates in hand — the uncharged ones cost nothing either way — so a $0.05 cap reliably delivers the three transcripts it pays for. Anything past the cap is still reported as left over, and the cap is still never exceeded.

### 1.0.20 — 2026-08-27

- **Your max total charge is now spent in full.** A run capped at $0.05 delivered a single transcript and then stopped; the same cap now delivers three. The cap is still never exceeded — the run simply stops holding back money it was never going to need.
- **The closing line no longer says the cap was reached when it wasn't.** When uncharged results hand budget back and ads are still left over, the run now says how much room is left and that a re-run will pick them up, instead of telling you to raise a cap you never hit.
- **A dataset ID the actor can't read now tells you so.** Chaining a run with a mistyped or unreachable Dataset ID used to finish quietly with an empty result and no explanation at all. The run now returns one uncharged row naming the ID and what to do about it — check the ID, pick it with the "Dataset ID" field (which grants read access), or paste the rows into "Dataset items". Nothing is charged either way.
- A long ad that finishes first can no longer take budget another ad in the same run still needs, so a capped batch of long videos stays inside your max charge.

### 1.0.19 — 2026-08-27

- **A long ad whose speech sits in one stretch now delivers a transcript.** A three-minute talking
  section inside a fifteen-minute ad could be mistaken for too little speech to count, and the ad
  was handed back as "no speech" — uncharged, but with no transcript either. Those ads now
  deliver. Ads that really are music or silence are still caught and still cost nothing.
- **A nonsense reported length is now ignored everywhere, not only in billing.** Some videos — live
  recordings especially — report a length of years. That figure could still steer the no-speech
  check even after it had been ruled out of the surcharge; it is now discarded outright and the
  ad is judged on what was actually transcribed.
- **A row's charge summary never lists long-video minutes that were not billed.** On a run where the
  long-video surcharge is not in effect, rows no longer record surcharge minutes against
  themselves, so the per-row summary always matches the invoice.

### 1.0.18 — 2026-08-27

A long ad with no captions now gets an honest, permanent answer instead of being told to try again.

- **Fixed:** a caption-less ad longer than a few minutes came back as a temporary problem that a
  re-run would clear. It will not clear. Past roughly a few minutes YouTube stops releasing a
  video's audio to anything but its own player, so speech-to-text cannot reach enough of it for a
  complete transcript — and a partial transcript is never sold as a whole one. Those ads now come
  back as `audio_too_long_for_speech`: permanent, uncharged, and saying so plainly rather than
  sending you to re-run something that can never succeed. Short caption-less ads transcribe as
  before, and ads that carry captions — the large majority, and nearly every spoken one — are
  unaffected at any length.
- **Changed:** when the video service refuses an ad's audio, the run now works out whether it is
  refusing that particular video or simply throttling this run, and reports the two differently. A
  throttle is still reported as temporary and still worth re-running — and it no longer spends a
  second attempt on a refusal that a second attempt cannot fix.
- The page now says plainly which claims belong to the caption route and which to the speech-to-text
  fallback, instead of promising "any length" for both.

### 1.0.17 — 2026-08-27

- **Ads without captions transcribe again.** The speech-to-text fallback's audio download was being refused by the video service, so ads with no caption track came back as uncharged misses instead of transcripts. Those ads now deliver.
- **`language` reads the same on every row.** Rows from the speech-to-text route reported a language name ("English") while caption rows reported a code ("en"). Both now report the code, so the column can be filtered without knowing which route produced the row.
- **A wrong reported duration can no longer inflate a surcharge.** Some videos — live recordings especially — report a nonsense length (one real example claims 3.8 years). Durations are now sanity-checked before billing, and no single row can bill more than an hour's worth of surcharge minutes however long the video is. Videos of any length still transcribe in full.
- **A max cost per run that affords exactly one transcript now delivers one** instead of stopping with nothing delivered and nothing charged.
- **The `charged` field reports what the run actually billed.** On a run with no pricing in effect, a delivered row now correctly says `charged: false`.
- Runs near your max cost now stop cleanly on their own at the new tier prices.

### 1.0.16 — 2026-08-27

- **Price cut.** A delivered transcript now costs $0.020 down to $0.008 depending on your Apify plan tier — the volume price was $0.015. The free-plan price and the $0.005 long-video surcharge are unchanged, and you are still charged only when a transcript lands in your dataset.
- The pricing block in this page now shows the new figures.

### 1.0.15 — 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.

### 1.0.14 — 2026-08-27

- The README now opens with an **Output** section — a sample results table, a screenshot of a real run's output, and the full JSON row — 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 prices.
- Added a directory of the other steadyfetch ad-intelligence actors (Facebook & Instagram, TikTok, LinkedIn, Instagram Reels, Google Ads creative text, Google Trends) with their free n8n templates.
- The example dataset linked from the README is now permanent, so it will not expire.

### 1.0.13 — 2026-08-25

- Pasted ad links now honor the link's own `?region=`, so the ad is looked up where you actually saw it rather than in the run's default region.
- Ads from very large advertisers no longer get a false "no media" verdict when the ad sits beyond the newest listings this actor searches — the row now says exactly that, uncharged.
- Capped runs acknowledge every pasted link you asked for: links left unattempted are counted in the run summary, a link whose video another input already delivered comes back as an uncharged `duplicate` row, and an outage on ad-page lookups no longer stops advertiser and domain enumeration (or vice versa).
- Web links pasted into the Advertisers field get clear guidance (YouTube ad links are re-routed automatically) instead of being searched as an advertiser name.

### 1.0.12 — 2026-08-25

- Pasted Transparency Center links now work whatever their casing (chat apps and link shorteners often lowercase a URL), and Transparency Center links that don't point at an ad get the same clear guidance in either input field instead of being searched as an advertiser name.
- A lookup interrupted by a temporary Transparency Center outage or the run's time limit is now reported as retryable, instead of stating the ad has no video — both cases stay uncharged.

### 1.0.11 — 2026-08-25

- You can now paste Ads Transparency Center ad page links directly — the URL you get browsing [adstransparency.google.com](https://adstransparency.google.com/) (`…/advertiser/AR…/creative/CR…`, any region) works in the video-URLs field and is looked up and transcribed on the spot. Advertiser page links pasted into the Advertisers field are understood too, and mixed-up pastes come back as clear uncharged guidance.
- A pasted ad is tried every way it can render — each of its preview variations, then its entry in the advertiser's public listing — so it delivers a transcript whenever a playable video exists, and comes back as an honest uncharged row when none does.

### 1.0.8 — 2026-08-21

- Added a ready-made n8n workflow and a live example dataset to the README.
- Capped runs now always say how many ads were left when your max charge stopped the run.

### 1.0.7 — 2026-08-21

- Billing accuracy: the same creative reached through different targets is delivered and charged once; long-video surcharge charges always match the per-row ledger, and batches near the spend cap stop honestly; improved music-only detection (still never charged).
- Reliability: some longer video ads that were wrongly skipped are now transcribed; captions with very short spoken text are delivered instead of reported as music-only; broken media files come back as uncharged `failed_processing` rows; sturdier retries and timeouts throughout.

### 1.0.6 — 2026-08-21

- Initial release: transcripts + first-3-seconds hooks for Google Ads Transparency Center video ads — by advertiser name, AR… advertiser ID, domain, YouTube ad video URL/ID, or chained after any Google Ads scraper run (dataset ID or pasted rows), region-aware.
- Charged only on delivery: removed videos, music-only creatives, media-less creatives, and empty lookups arrive as uncharged rows with the reason stated. The first 3 minutes of each video are included; longer videos add a small per-started-minute surcharge.
