# Changelog of YouTube Channel Videos Transcript Scraper — Bulk Video to Text (`steadyfetch/youtube-channel-transcripts`) Actor

- **URL**: https://apify.com/steadyfetch/youtube-channel-transcripts/changelog.md
- **Full Actor documentation**: https://apify.com/steadyfetch/youtube-channel-transcripts.md

## Changelog

### 1.0.72 — 2026-09-19

- **The whole "Maximum cost per run" you set is now spent on transcripts.** A capped run used to hold back about a seventh of your cap as a cushion against platform usage — but your cap pays for the transcripts and the speech minutes and nothing else, so that seventh was cap you had asked to spend and did not get: a cap worth twenty-three transcripts delivered twenty. A run now transcribes every video 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. Videos with no captions are unaffected: the speech minutes they bill have always been held against the live room at the moment they are charged, and still are. Nothing about the price, the charged events, the input form or any output column changed.

### 1.0.71 — 2026-09-16

- **The one step left that could hold a run open past your timeout now ends inside it.** Before a run can use a proxied connection it asks Apify for the session, once per connection type, and that request had no time limit at all — not its own, and not your run's. On a slow minute it could sit there after your run timeout had passed, which is the one way a run could still be killed mid-step and lose its closing rows, its receipt and its status line. It is now given the smaller of a twenty-second ceiling and whatever the run has left, and a request that runs out is treated exactly as a session that could not be opened already was: an uncharged, re-runnable row, and the next video asks again. A run with time to spare is unchanged. No input field, output column, charged event or price changed.

### 1.0.70 — 2026-09-15

- **A run that reaches its time limit now stops at it, instead of seconds past it.** Every call this actor makes — the page that lists a channel's videos, the call that fetches a video's captions, the audio download, the speech-to-text request — carried a time limit of its own, measured from the moment that call started, and nothing measured it against the time the RUN had left. So the last call a run began could still be reading after the run's own limit had passed, and on a video with captions that was measured at 14 seconds over. Every read is now bounded by whichever is smaller — its own limit, or what the run has left after keeping back enough to write the rows and finish the receipt. A video the clock did not reach ships the same uncharged, re-runnable row it always did, and now says the run's own time limit is what ended it instead of blaming the connection; a channel listing the clock cut short says the same, rather than reading as a channel that could not be listed. Nothing is charged for either, and a run with time to spare is unchanged — same rows, same limits, same prices.

### 1.0.69 — 2026-09-14

- Nothing you see changed: no input field, output column, row, status line, charged event or price moved, and a run does exactly what it did before. This build fixes our own end-of-run record, which could not tell a run that listed a channel's videos — and so charged nothing on purpose — from a transcript run whose charging had failed. The two wrote the same numbers, so a listing run could hide a billing fault of ours. The record now says which of the two a run was.

### 1.0.68 — 2026-09-14

- **You can now list a channel's videos without transcribing them, and that listing is not charged.** Send `transcripts: false` (or leave the new **Transcribe the videos** switch off) and the run stops at the listing: one row per video with its id, URL, title, `durationSeconds`, thumbnail and the channel grid's own view and age text — "7.8M views", "1 day ago" — and no transcript. Neither of this actor's two charged events fires on a listing run: no transcript, no speech-to-text minute, so a listing costs only the Apify platform usage every run spends, and every row says so in words. The listing is re-listed in full on every run, because what is on a channel now is the whole answer a listing is asked for — the opposite of the transcript mode, where a video you already have comes back uncharged instead of being fetched again. **Max videos per channel** and **Max videos for the whole run** bound a listing run exactly as they bound a transcript run. Two numbers are deliberately absent: YouTube's channel grid gives rounded words and no upload date, so the row ships the words and never a `viewCount` of 7,800,000 or a timestamp worked back from "1 day ago". An AI agent that writes `mode: "videos"`, `videosOnly: true` or `skipTranscripts: true` gets the same listing plus one uncharged row naming the field this actor declares. Nothing changed for a run that leaves the switch alone: same rows, same events, same prices.
- **A long status line no longer loses its tail on the run page.** The run page cuts a status message at 500 characters, and this actor already shortened its own line when one particular cause made it too long — but a line that ran long for any other reason was sent whole and cut by the platform, which takes the reconciliation, the limit that stopped the run and the support pointer off the end, in that order. Every line is now measured and shortened before it is sent, whatever made it long, and what gives way is still only the restatement of counts each row already carries: nothing that names a limit, a charge or an uncharged row is ever dropped.

### 1.0.67 — 2026-09-14

- **A token that can read the memory but not write to it now says so — before your next poll buys the same videos again.** The account memory that lets a weekly run skip everything it has already transcribed is a key-value store in your own Apify account, and permission to read it and permission to write to it are two different grants. A run started with a token that has Read but not Write — or Write but not Create, on an account that has never run this actor — opens the memory, correctly skips or hands back every video you already had, honours "New videos only", and then silently fails to write down anything it delivered. The run looks perfect, and next week's poll of the same channel transcribes and charges every one of those videos over again, and the week after that too. It now says so the moment the first write is refused, in three places: the run page, the run log, and one uncharged row in the dataset carrying the whole instruction — give the token key-value store Write (and Create) permission under Settings → API & Integrations, or set Actor runs to Full access. Nothing about the run itself changed: the same videos are delivered and the same rows charged, and a run with a full-access token, or one started from the console, behaves exactly as before. No input field, output column, charged event or price changed.

- **The run-wide-limit line reads as one sentence again.** When a run stopped at "Max videos for the whole run", the status line said "… 2 videos left; raise "Max videos for the whole run"" — and the semicolon is what this actor uses to separate one fact from the next, so the advice read as a fact of its own rather than as part of the limit it belongs to. Same words, one sentence. Nothing else changed.

- **A run started with a scoped API token now says why it could not skip the videos you already have — and exactly what to change.** The account memory that hands back a transcript you have already paid for lives in a key-value store in your own account. A run started over the API with a token limited to "Restricted access" and no key-value store permission cannot open that store at all, so a weekly poll re-lists the channel and transcribes and charges the whole back catalogue again — the one thing this page promises never happens. The run used to say only that "the check was unavailable", which reads like a passing glitch rather than a permission you can grant. It now names the cause in the run log, points at it on the run page, and carries the full fix in one uncharged row at the top of the dataset: give the token key-value store Read, Write and Create permission under Settings → API & Integrations, or set Actor runs to Full access, or start the run from the console. With **New videos only** on, the run still refuses before it lists anything — but it now tells you which permission to grant instead of telling you to re-run in a minute, which on the same token would have failed the same way. Runs started from the console, or with a full-access token, behave exactly as before, and so does a run whose memory failed for any other reason. No input field, output column, charged event or price changed.

### 1.0.66 — 2026-09-13

- **This actor has a new title on the store: YouTube Channel Videos Transcript Scraper — Bulk Video to Text.** The old one said "Every Video to Text", and a plain run does not do that: it takes the newest 50 videos per channel by default, and up to 5,000 when you raise the cap — which is what the store card, the input form and this page have always said. The title now says the same thing as the rest of the listing, so what you read before you click is what the run delivers. Nothing about the actor changed: same id, same URL, same input, same output, same price, and a channel with more than 50 videos is still transcribed in full by raising **Max videos per channel**.
- Nothing else moved: no input field, output column, charged event, default or price changed. The search snippet the store shows under the title now names the 50-video default and the 5,000 cap too.

### 1.0.65 — 2026-09-13

- **An agent or a person who writes the singular field name now gets the transcripts, instead of an error row naming a field they never set.** This actor reads its list from `channels`. A caller that sent `channel`, `channelUrl`, `channelUrls`, `channelId`, `channelIds`, `handle`, `handles`, `url`, `videoUrl`, `video` or `videos` had that list passed straight through by the platform and ignored here, so the run answered the one-channel sample or an error row that named `channels` — a field the caller had not used. Those names are now read as the same list, whether they hold one entry or many, and they take everything `channels` takes: a channel URL, an @handle, a bare channel ID, or a single video link. The entries are used exactly as sent, a list sent under two names is merged and de-duplicated so a channel is still one listing and a video still one transcript and one charge, and one uncharged row and one line in the run log say which name was read and that the field here is `channels`. The input form is unchanged — `channels` is still the one field the schema declares and the one to use — and nothing charged or priced changes.
- **The store description now names the speech-to-text minute, and how many videos a plain run takes.** The card quoted the per-transcript price alone, while a video with no captions is also billed $0.008 per started minute of speech-to-text — which is on by default, and which the input form and this page already disclosed. The card now says so. It also says what a run takes when you leave the caps alone: the newest 50 videos per channel, up to 5,000 if you raise the cap, where before it read as though every video came in one run. No price, event, input field, default or output column changed.
- **The pointer to the single-video actor now names it by its current title.** That listing is called YouTube Transcript API & Scraper — YouTube Subtitles, Speech AI; it was renamed so its name carries the API route as well as subtitles. The link here points at the same actor as before; only the words a reader sees changed.

### 1.0.64 — 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.63 — 2026-09-12

- **The run now holds its own minimum gap before every request, and stops opening a brand-new connection for each video.** We keep a small pause between calls to YouTube so a run never arrives as a burst. Two things meant it was barely being held: the pause was applied to the first of the three requests each video makes and not to the subtitle fetch or the audio download, and every video was fetched on a freshly opened connection, which starts its own clock owing nothing. A run now carries the connection that answered from one video to the next and waits the same minimum before every request on it. A video that is refused or challenged still gets a fresh connection straight away, exactly as before, and a refused connection is never offered to the next video. Nothing about your input, your rows, your columns or what you are charged changes — this is only about how often we knock.
- **New switch: "New videos only".** Leave it off — that is the default, and nothing about your runs changes: a video this account already has still comes back from the run that transcribed it, marked `repeat: true` with nothing charged for it, so a re-run is a complete, current archive. Turn it on and those videos are left out of the dataset altogether: nothing is fetched for them, nothing is charged, no row is written, and the run's status line says how many were skipped. It is the switch for a run on a schedule — your Sheet, your webhook or your workflow receives one row per new upload and nothing else. It changes what lands in the dataset, never what you pay: a video you already have costs nothing either way, and it never uses up your video caps either way. If a run cannot read the record of what you already have, "New videos only" stops it before it lists anything and says so on an uncharged row, rather than charge you for the videos you asked it to skip.
- **The page now spells out the weekly run** — save the channel list as a Task, put the Task on a Schedule, add an integration on *run succeeded* — and says what each run costs you once the first one has paid for the back catalogue.
- **Input schema: the description opens with the call that works, the per-video price with the speech-to-text minute, the arithmetic and the cap; the required field's first sentence is now the value shape, the console sample last.**
- **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.
- **An over-ask on a cap no longer refuses the run.** `maxVideos`, `maxItems` and `maxSpeechMinutes` carried a `maximum` in the input schema, which the platform checks before a container starts — so a caller asking for 10,000 videos got an error and no run at all, rather than the 5,000 the actor can serve. The ceilings now live only in the code: the run starts, continues at the ceiling, and ships one uncharged row saying what you asked for and what bound it. Nothing about the form, the defaults, the output or the price changed.
- Nothing else moved: `channels` is still the one field you have to set, and no output column, event or price changed.

### 1.0.62 — 2026-09-11

- **A video left unread because transcribing it would pass your maximum cost per run is now counted against that cap, not against the video.** When a video has no captions we transcribe its audio, and before we start we check whether those minutes would take the run past your **Maximum cost per run**. If they would, the video is left alone and nothing is charged — that part is unchanged and always was. What was wrong is where it was counted: the run put those videos in with the ones that genuinely produced nothing, so your own spend cap read as a fault in the video, and the run could finish without ever naming the cap as the thing that stopped it. They are now counted where they belong — reported as "left at your maximum cost per run", with your cost cap named as the reason the run ended. Nothing about the money changes: those videos were never billed and still are not.
- **When a run comes back short, we can now see WHY, video by video.** Every uncharged row already told you what happened to that video; our own private run record folded almost all of them into a single number, so a video whose owner has captions switched off, one that has been taken down, an age-restricted one, one blocked where we fetched from, a live stream with no transcript yet and our own speech service failing all looked identical to us — and a problem worth fixing could sit unnoticed. Thirteen causes are recorded separately now. Your rows, their wording and what you are charged are exactly as before.
- **AI agents can now pin this actor from the top of the README** — the pin link, the actor id, the one input field you have to set and how to cap a run's bill are on the first screen of the page instead of far down it.
- **A maintenance detail:** 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 the video limit you set had actually filled it. Nothing a run does, delivers or costs is different.
- The input form is unchanged: `channels` is still the one field, still required, still the name on the Input tab. Nothing else moved — no price, no event, no charged column, and only delivered transcripts are charged.

### 1.0.61 — 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 video. When that stamp landed far enough out, the whole transcript was judged unreliable and the video 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 and the timed cues 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.
- The input form is unchanged: `channels` is still the one field, still required, still the name on the Input tab. Nothing else moved — no price, no event, no charged column, and only delivered transcripts are charged.

### 1.0.60 — 2026-09-10

- **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 — gained room for four figures it does not fill in yet: how many targets a run was given, how long it waited on a blocked source, how many of those waits recovered, and a reason code for an item our own size limit refused. The build carries the shared contract so a later one can report them; on this actor every one of them is left blank, and nothing a run does or costs is different.
- The input form is unchanged: `channels` is still the one field, still required, still the name on the Input tab. Nothing else moved — no price, no event, no charged column, and only delivered transcripts are charged.

### 1.0.59 — 2026-09-09

- **Two runs started at the same time no longer erase each other's memory.** The account's repeat memory is now merged on every write, so an item one run delivered stays remembered, and a later re-run hands it back instead of charging it again.
- **Patience is now priced.** A channel YouTube will not list is waited out only for as long as the transcripts it could still recover are worth, at the price and the memory this run actually has. The first pause is shorter, and a run on a short time limit now gets patience it never got before instead of none at all. A run that has delivered nothing still gets one pause whatever its ask is worth.
- The input form is unchanged: `channels` is still the one field, still required, still the name on the Input tab. Nothing else moved — no price, no event, no charged column, and only delivered transcripts are charged.

### 1.0.58 — 2026-09-09

- **A list sent under another actor's field name now runs, instead of coming back with one row you did not ask for.** The transcript actors in this family do not all call their input list the same thing: it is `channels` here, `videoUrls` on the single-video actor and four others, `urls` on the media one. Send a body written for one of them to this actor and the platform quietly ignores the field it does not know, fills in the defaults, and the run answers a question you never asked — a list of hundreds of links could come back as a single sample. This actor now reads `videoUrls` and `urls` as its own list, uses every entry exactly as sent, and adds one uncharged note row saying the field is called `channels` here so the next run needs no note at all.
- **Everything this actor accepts is accepted under all three names.** Channel URLs, @handles, channel IDs and single video links all work whichever of the three field names carried them, so a body written for another transcript actor runs here unchanged and each link is charged exactly once even if it appears under two of them.
- The input form is unchanged: `channels` is still the one field, still required, still the name on the Input tab. Nothing else moved — no price, no event, no charged column, and only delivered transcripts are charged.

### 1.0.57 — 2026-09-07

- **A music-only or silent video is no longer charged as a transcript.** When a video 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 video's own words. Short videos 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. A video 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 video that really does speak is still delivered.** Speech is now weighed against the part of the video the words actually cover rather than the whole of its length, so a six-second video carrying a three-word line reads as speech and not as silence. Short spoken videos that used to come back as "no speech" are delivered.
- Nothing else moved: no price, event, input field or output column changed.

### 1.0.56 — 2026-09-07

- **"txt" and other everyday spellings of an output format now work instead of ending the run.** "Output format" takes json, text, srt or vtt. Any other spelling used to end the whole run before a single video was listed — so "txt", "plain text", "webvtt" or "subrip" cost you the run even though each of them can only mean one thing. They are now understood, along with the option's own names on the form ("JSON with timestamps", "Plain text", "SRT subtitles", "VTT subtitles").
- **A value that genuinely names no format, or names two, is still refused before anything is charged.** "subtitles" and "captions" describe both srt and vtt, so this actor asks rather than guessing, and a format it does not produce is still named back to you on one uncharged row. Guessing would hand you a file you did not choose and bill you for it.
- Nothing else moved: no price, event, output column or charge changed, and only delivered results are charged.

### 1.0.55 — 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. Channel enumeration and the video lookups used to demand the other kind, get "no such address" back, and answer as though the video or the page 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. It now *prefers* the address it 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 transcripts are charged.

### 1.0.54 — 2026-09-06

- **A block of links pasted into one row is now read as the list you meant, whatever separates them.** "Channels" takes one channel per row. A whole block pasted into a single row was read as one very long address, so a paste of ten channels listed one at best and the rest were silently gone. 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.53 — 2026-09-06

- **Every video your maximum cost per run left behind now arrives as its own row.** When a run reached its **Maximum cost per run** before a video's turn, that video was simply absent from your results: the run page counted how many were left, but nothing named them, so a channel of forty videos whose cap covered ten gave you ten rows and no way to tell which thirty were missing. Each one now ships as an uncharged `skipped_budget` row carrying its video id, its channel and the link you sent, with a reason saying the run reached its maximum cost before that video was started.
- **The row says how to get it:** raise the maximum cost per run, or split the channels across runs, and re-run. The rows are flagged `retryable`, so you can filter exactly what a re-run would bring back.
- **Nothing was fetched and nothing is charged for these rows** — the video was never started, so there is no caption lookup, no speech-to-text and no result fee.
- **A re-run still fetches them.** These rows are deliberately not recorded as delivered, so resuming or re-running the same channels picks them up again.
- **Nothing about pricing, events, input or output columns changed.**

### 1.0.52 — 2026-09-06

- **Every video the run timeout left behind now arrives as its own row.** When a run reached its time limit before a video's turn, that video was simply absent from your dataset: the run page said how many were left, but nothing named them. Each one now ships as an uncharged `skipped_deadline` row carrying its video id, its channel and the link, with a reason saying the run reached its time limit before it was started.
- **A channel the run never got to list now says so too.** If the clock ran out before a channel's videos could be listed, that channel produced nothing at all — no row, no name, nothing. It now gets one uncharged row naming the channel and saying its videos were never listed, and the run's own record counts the whole per-channel cap it did not deliver.
- **Both rows say how to get the rest:** raise the run timeout, lower **Max videos per channel**, or split the channels across runs, and re-run. They are flagged `retryable`, so you can filter exactly what a re-run would bring back.
- **Nothing was fetched and nothing is charged for these rows** — nothing was listed, downloaded or transcribed.
- **A re-run still fetches them.** These rows are deliberately not recorded as delivered, so resuming or re-running the same list picks them up again.
- **Nothing about pricing, events, input or output columns changed.**

### 1.0.51 — 2026-09-06

- **A video whose transcript was already in hand when your cost cap was reached now reaches you.** If a run hit its **Maximum cost per run** in the moment a video's transcript came back — a caption mint, or a speech-to-text pass already made — that transcript was thrown away and you got an empty "cost cap reached" row in its place. You paid nothing for it, but the answer you had waited for was gone, and only a re-run at a higher cap could bring it back. That video now arrives complete, with `charged: false` and a row saying the cap was reached and nothing was charged for it.
- **Your maximum cost is still never exceeded** — the cap takes the charge, not the result.
- **Videos the cap stopped before any work was done are unchanged:** they are still reported as `skipped_budget` naming the cap.
- **Nothing else moved:** no price, event or output column changed, and you are still charged only for transcripts that actually deliver.

### 1.0.50 — 2026-09-05

- **A channel YouTube will not list is now waited out instead of ending the run in seconds.** Listing a channel is the gate: if the handle cannot be looked up or the video grid will not load, nothing else in the run happens. Until now the run tried each of its connections once, gave up within seconds and handed back "please re-run" — with almost all of the time you had allowed still unspent. It now waits a minute or two for the block to pass and lists the channel again, once, before reporting anything. Blocks like this usually clear inside the minutes the run already had, and the per-video patience added in an earlier build only ever helped after this gate was passed.
- **A channel row now says the run already waited.** When the block outlasts the wait, the row explains what was tried and for how long, so "please re-run" is advice given after the run took its own advice, not instead of it.
- **An answer is still immediate.** A channel that does not exist, a link that is not a channel, and a channel YouTube says is unavailable from the locations this run went out from are answers, not blocks: they are reported at once, with no waiting. A run too short to wait behaves exactly as before, and nothing about your rows, your transcripts or your charges changes.

### 1.0.49 — 2026-09-05

- **Every video 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 channels where it should have been counting videos: a channel whose listing YouTube refused was recorded as "one channel" instead of the videos it was going to cover, so a two-channel run could lose five videos out of the tally with nothing naming them. Every number in it is now a video, and they add up exactly — transcripts delivered, plus each video that missed with its own reason, plus each video one of your own limits stopped.
- **A channel that does not exist is now told apart from a channel that could not be listed.** Both were recorded the same way, so a mistyped handle looked exactly like YouTube turning us away. Your rows always said which of the two it was; now the run's own record does too, so a real blockage is noticed while it is happening instead of being averaged out with typos.
- **A run now records why it ended** — your run-wide video limit, your maximum cost per run, the run timeout, or the platform moving the run to another server — where before it recorded nothing at all.
- **Nothing about your rows, your transcripts 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.48 — 2026-09-05

- **A run that waits now always keeps room to finish and report.** When YouTube walls every connection for a video, the run can pause and climb the whole connection ladder again — but the check that decided whether a pause was affordable kept back only half a minute for that second climb, while a climb here is four connections, each with its own thirty-second limit. On a short run — the five-minute limit the daily health check uses among them — the pause was allowed and the climb after it then ran into the time the run keeps back for writing your rows and the summary. The check now keeps back a whole climb, measured from the ladder's own connection count and timeout, so a pause is only taken when the run can finish the climb the pause was for. A run with room to wait waits exactly as it did before.
- **Charges are unchanged.** Waiting is uncharged, a walled video is uncharged, and only delivered transcripts are charged.

### 1.0.47 — 2026-09-05

- **A run started with no input at all now has room to wait out a block.** Press Start on the untouched form and this actor transcribes one video from one real channel, charged like any run. YouTube sometimes turns a video's connections away for a few minutes at a time, and one round of attempts could use up most of the sample's own three-minute limit — so the pause-and-try-again this actor gained in the last build could never happen on the most common first run of all: it reported "please re-run" while the block was still lifting. The sample's time limit is now six minutes, sized to hold one full round of attempts, one pause, one more round, and enough time left over to stop cleanly and report. A sample YouTube answers still finishes in seconds; only a blocked one uses the extra time, and nothing is charged unless a transcript is delivered.
- **A no-input run was also measuring its pause against the wrong clock.** It checked the pause against the run's overall time limit instead of the sample's own shorter one, so a blocked sample could start a pause its own limit had no room for. The pause is now measured against the limit the run is actually working under. Nothing changes for a run with your own channels in it.
- **How much of its remaining time a blocked run may spend waiting now follows the size of what you asked for.** A run with more videos still to transcribe keeps exactly the limit it had — no more than half the time left — so a wide block still leaves the later videos their share. A run with a single video to fetch may spend what is left on its one pause, because nothing else is waiting for that time.
- **The no-input sample now always transcribes for real.** It used to be handed back from this account's own history of past runs, so a second Start returned the earlier copy uncharged and nothing was actually fetched. The sample now ignores that history in both directions: it fetches and transcribes every time, and it does not add its own video to the history. A run with your own channels in it is unchanged — a video this account already has is still handed back to you uncharged.

### 1.0.46 — 2026-09-05

- **A video YouTube walls is now waited out inside your own time budget, instead of being handed straight back for a re-run.** Until now the run tried a walled video on four connections in a row — with no pause between them, so the whole ladder burned in well under a minute — and then shipped an uncharged "please re-run" row while most of a long run's time went unused. That wall is a per-connection bot check that usually lifts within minutes. Now, while the run still holds real time and the videos still to collect can pay for the idle minutes, it waits a minute and a half to three and walks the same four connections again. A video that answers after the wait is delivered and charged exactly as if the first try had answered.
- **What never waits:** a video that is removed, private, members-only, age-restricted, region-blocked, a live stream still being processed, and the run's own row, time and cost limits — those are answers, and a wait cannot change them. A run already at its time limit waits for nothing.
- **Short runs are unchanged.** A wait plus a fresh walk has to fit inside what the run has left, with time kept back to finish and report, so a 60-second run makes its single walk exactly as before, and the no-input sample keeps its own three-minute window.
- **One run's waiting is capped in total, not per video or per channel.** A wall storm across a whole channel shares one budget for patience — a few minutes at most — so a long channel can never sit waiting video after video. Because the wall is per connection, the minutes the first walled video waits are minutes every video after it also gains.
- **A video still walled after the waits keeps its usual uncharged row**, which now says what was tried: how long the run waited and how many passes it made.
- **Charges are unchanged:** waiting is uncharged, a walled video is uncharged, and only delivered transcripts and the speech minutes they use are charged. A video's own data limit is untouched by a wait, so a patient run can never buy more data for one video than an impatient one.

### 1.0.45 — 2026-09-05

- **The default sample can fall back to speech-to-text again.** Since 1.0.43 a video's audio is only fetched and transcribed when the run has enough time left for that step's 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 that worst case — so whenever the sample channel's one video had no captions available, the sample came back as an uncharged "the run reached its time limit" row within seconds instead of a speech-to-text transcript. 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 work. Runs that name their own channels or videos were never affected, and a sample answered from captions never was.

### 1.0.44 — 2026-09-05

- **A channel whose lookup was dropped is no longer given up on in seconds.** Turning a channel handle or URL into its channel ID takes one call to YouTube; when that single call was dropped or answered unreadably, the channel ended at once with an uncharged "please re-run" row while the run still had almost all of its time. That lookup now gets up to two more tries, each from a fresh network route, before the row ships — and never past the run's own clock. A channel YouTube says does not exist, or a link that is not a channel, is still answered on the first try with no retry.

### 1.0.43 — 2026-09-05

- **A video near the run's time limit now stops cleanly instead of being cut off mid-transcription.** Fetching a video can take several attempts, and transcribing its audio takes longer still. Those steps now check the run's own clock and never start work they cannot finish in time, so a run that reaches its limit ends with the transcripts it already has. Any video it did not reach comes back as an uncharged "please re-run" row (you can also raise the run timeout) rather than a run cut short.

### 1.0.42 — 2026-09-04

- **Settings with no channels now run the sample under your settings, instead of returning one guidance row.** Choosing a different **Output format**, a preferred caption language, turning speech-to-text off, or setting a limit and clicking **Start** with **Channels** empty used to end the run with a single uncharged "no channels were provided" row — so changing one dropdown was answered worse than changing nothing at all. That run now transcribes the sample channel with your settings applied, charged like any run.
- **A limit is a ceiling, never a request.** On one of these runs "Max videos per channel", "Max videos for the whole run" and "Max speech-to-text minutes" are honoured as *at most* — the sample's own one video and one minute are used when they are smaller, so narrowing a setting can never widen the bill.
- **One uncharged row explains it**, naming every setting you did set, that everything else used the sample's own values, and the field to fill in for your own run. It is never counted as a miss. When you set a preferred caption language it says plainly that the sample is one fixed channel with one video, so that language is used only if the video carries a track for it — the transcript row always names the language it actually carries.
- **A field this actor does not recognise, or a channel list you actually emptied, still gets the guidance row** — answering a typo with a sample row would read as if it had worked. An output format this actor does not support is still refused by name.

### 1.0.40 — 2026-09-04

- **An output format this actor does not recognise no longer swallows your list.** Setting "Output format" to a value it cannot produce refuses the run — that part is unchanged, because delivering json to someone who asked for subrip bills them for data they did not ask for. What changed is the answer: you used to get one row for the whole run, however many channels you had pasted. The row that names the value comes first, and then every channel and video you sent gets its own row saying it was not read and which field stopped it. Nothing is listed, nothing is fetched and nothing is charged, exactly as before.
- **Every refusal now names the input field it is about** — "Channels" or "Output format" — in the run's own record as well as in the row you read. A field that is turning people away then gets noticed and fixed quickly, instead of looking like a scatter of unrelated typos.
- **A YouTube wall is now told apart from a video that simply had nothing to give.** When YouTube answers with "confirm you're not a bot", asks for a signed-in account, or says the video is still processing, that row is counted as the wall it is rather than as an ordinary uncharged miss. It was always uncharged and always retryable; now a spell of walls is visible to us as a problem on our side, which is what gets it fixed. The closing line names them separately too.
- **The run record now reports what you asked for, not what survived.** A run where a setting stopped every channel, or where the run clock or your cost cap left them all, used to report an ask of zero and could pass unnoticed. It now reports the number of videos your input asked for.
- **The closing line says the same things in fewer words** so the run page stops cutting it: repeats, uncharged misses and channels that could not be listed are shorter. The charged unit is still named exactly as the Pricing tab names it.
- A few row and page outcomes this listing called "free" are named "uncharged", which is what they are — running any actor uses platform time you pay for. No facts, prices or charges changed.

### 1.0.39 — 2026-09-04

**Poll a channel every week and pay only for what it published since last time.**

A transcript this account has already been charged for now comes back from the run that produced it, with nothing charged for it a second time. Nothing is fetched and no speech-to-text minute is spent — the check happens before any of that — and the whole channel still lands in the dataset, so a weekly run stays a complete, current archive. Until now, re-running a channel re-bought its entire back catalogue.

The same video reached two ways is one charge too: pasted as a link today, listed by its channel tomorrow. Those rows say so — `repeat: true`, `charged: false`, `firstSeenAt` and `firstSeenRunId`, plus a note naming the day and the run — and they do not use up **Max videos** or **Max videos for the whole run**, so your caps buy new videos.

The memory lives in **your own account**, in a key-value store called **`yt-channel-transcripts-account`** on your Storage tab — delete it to start over and be charged again. Transcripts drop out of it after 90 days on their own. On a run where that store cannot be read, the run still delivers and charges exactly as it did before, and the status line says the check was unavailable so you know a repeat could have been billed.

Also: run status lines are shorter, so the counts, the named caps and the support ask survive on the run page.

### 1.0.38 — 2026-09-04

- **A problem on the transcription service's side is no longer reported as a problem with your video.** When that service refuses for a reason of its own, the video 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.

### 1.0.37 — 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 a key on our side is missing, now reach us the same way every other run does: counts and reason codes only, never your input or your rows.

### 1.0.36 — 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.
- **Row notes state what happened and what was charged; the support ask lives on the run page.**

### 1.0.35 — 2026-09-04

- **Run status lines fit the run page again; sample-run wording shortened.**
- **Rows that carry no result fee are now described as uncharged, not free** — running any actor still uses platform time.

### 1.0.34 — 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.33 — 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.32 — 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.31 — 2026-09-03

**Output format: an unrecognised value is now refused instead of quietly delivered in json.**

- **A format we do not recognise no longer costs you a run.** Sending something that is not `json`, `text`, `srt` or `vtt` in the Output format field used to list your channels and transcribe their videos in `json` anyway and mention it on a row — so you paid for transcripts in a format you had not asked for. Now nothing is listed, nothing is transcribed and nothing is charged: you get one row naming the value you sent and the four formats that exist, and you re-run with one of them.
- **Nothing else moved.** Leaving Output format blank, or sending `null`, still means `json` and still runs normally, a bare Start still transcribes the real sample channel, and every valid format behaves exactly as before.

### 1.0.30 — 2026-09-03

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

- The input-form and dataset screenshots now load from Apify storage instead of an outside host, so they render on the listing itself.
- Every link that pointed outside Apify has been removed from the listing text.

### 1.0.29 — 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.28 — 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.27 — 2026-09-02

Documentation build: nothing about what runs, what is delivered or what is charged changed.

- **Real console screenshots on this page** — the input form exactly as it arrives, and the dataset table a real run delivers.
- **One-click MCP link** — pin this actor in Claude, Cursor or any MCP client straight from this page.
- **Suite links refreshed** to the current names of the other steadyfetch actors.
- **Input-form copy corrected.** The Channels field still said a bare Start returns one uncharged sample row. It transcribes a real channel's video, charged like any run — the field now says so, as this page already did.

### 1.0.26 — 2026-09-02

Start with the default form and get a real channel transcript instead of a placeholder row.

- **A bare Start now transcribes a real channel.** Click Start with nothing set (or send an empty input from the API) and the run lists one real channel — `@jawed`, YouTube's first channel, with its single public video — and returns that video's full transcript row, charged like any other transcript. The stored placeholder row is gone; what you see is what a real run returns.
- **The sample stays small and quick by design:** one channel, one video, at most one speech-to-text minute, and a three-minute window. On a bad minute you get one uncharged row saying the channel could not be listed this time (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 channels.
- **Nothing else changed:** any input with channels in it behaves exactly as before, and a mistyped field still gets a row naming the field instead of a sample.

### 1.0.25 — 2026-08-31

**Runs sent from a template or an agent no longer fail before they start.** Tools that build the input for you — n8n, MCP clients, agent frameworks — send `null` for every option you left unset. The platform used to refuse those runs outright, before this actor ever saw them. Now every optional field accepts `null` and reads it as "use the default", so the run just works. "Output format" included: it is still the same dropdown with the same four choices, but a value it does not recognise no longer stops the run — you get the transcript in `json` plus one uncharged row naming the formats that exist. The only field that still needs a real value is `channels`. Nothing about what is delivered or charged changed.

### 1.0.23 — 2026-08-31

**Listing a channel's videos is no longer stopped by an unusable network route.** Reading a channel's video list is done from the run's own address, and on some runs that read opened a network route the run cannot actually use — every retry opened the same one, so the run could end with a retryable row that re-running could not fix. That read now takes the route that works. Nothing about what is charged changed.

### 1.0.22 — 2026-08-30

Maintenance build: internal source tidying only. Nothing about behavior, output, or pricing changed.

### 1.0.21 — 2026-08-30

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

### 1.0.20 — 2026-08-30

- **Transcripts now come back in the video's own language.** A plain paste picks the video's original-language caption track instead of whichever translation YouTube happened to list first — an English video no longer comes back in Arabic. An explicit language choice still wins exactly as before, and the row still tells you which language you got.
- **Cleaner transcript text.** Repeated rolling-caption lines (the same line printed again as YouTube scrolls its caption window) and invisible zero-width characters are now removed from the text and its segments.
- Nothing about what this actor charges changed.

### 1.0.19 — 2026-08-30

- **Videos that YouTube challenges on busy shared networks now deliver far more often on the first run.** When YouTube answers a lookup with a "confirm you're not a bot" wall, this actor climbs to its cleanest network route — and that route now gets two full attempts instead of one, caption-fetch trouble now climbs to a better route instead of using up the run's tries where it was stuck, and a rare setup hiccup that could quietly leave those retries on the wrong network is fixed. Videos that used to come back as an uncharged "please re-run" row now come back as transcripts.
- Caption reading is marginally faster on videos with very long transcripts.
- Nothing about what this actor delivers or charges changed otherwise.

### 1.0.18 — 2026-08-30

- **A channel whose video list would not come down now gets two more ways in before you are asked to re-run.** If YouTube's Videos tab would not list the channel, the run now reads the channel's uploads list instead (up to 200 videos, and the channel row says the listing may be partial). If the channel was refused from the location the run went out from, the run now goes out again from two other locations before it reports the channel as unavailable from here. Channels that used to come back as an uncharged "please re-run" row now list — and transcribe — on the first run.
- **A busy speech-to-text service no longer costs you the video.** The speech-to-text route now tries the audio it already holds three times, with a growing pause between attempts — and when the service says how long to wait, the run waits exactly that long (never more than a minute). Videos that used to come back as an uncharged "please re-run" row during a busy spell now deliver on the first run. Captioned videos never touch this route and are unaffected.

Nothing about what this actor charges changed.

### 1.0.17 — 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.16 — 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.15 — 2026-08-30

The whole-run cap now survives a channel that changed underneath a resume, three billing edge cases around interruptions and the cost cap are closed, and `null` in a numeric option no longer means zero.

- **`Max videos for the whole run` holds across a resume even when the channel changed in between.** A resumed run counts the transcripts it already delivered against the cap — but it used to recognise only the ones still present when the channel was listed again. New uploads pushing older videos out of the listing, or a listing cut short, made already-delivered transcripts invisible to that count, and the run could deliver and bill more than the one number you set. What was delivered now counts in full, whatever the fresh listing looks like.
- **A resumed run can no longer count a delivered transcript twice.** When an interrupted run picks up, it reads back what it already delivered. If that read-back itself hit a snag partway and had to start over, rows read before the snag could be counted a second time — and settling the books against an inflated count could take a charge for a transcript you had already paid for. Each read-back attempt now starts from a clean slate and every row counts exactly once, so a resume can only ever settle what is genuinely owed.
- **An interrupted charge that still cannot be taken on resume is left for later — never guessed at.** When a resumed run owes a charge from before the interruption and taking it fails right now, the run says so, adds nothing further to that settlement, and carries on with its work; the amount is settled on a later resume against the platform's own record. A charge that may have landed despite the error is never issued a second time.
- **Two videos finishing in the same moment can no longer both claim the last room under `Maximum cost per run`.** Near the cap, the room a just-finished video's charge is about to take is now counted before the next video is allowed to bill. The cap holds exactly: the video that no longer fits ships as the usual uncharged `skipped_budget` row telling you to raise the cap and re-run.
- **`null` no longer means zero in the numeric options.** Sending `Max videos per channel`, `Max videos for the whole run` or `Max speech-to-text minutes` as `null` or an empty string — the shape many API templates produce for "not set" — was read as the number zero and clamped to the smallest value the field allows, quietly shrinking the run. Those values now mean what leaving the field out means: the default.

### 1.0.14 — 2026-08-30

A row can no longer claim it was charged when the charge did not land.

- **`charged: true` now always means the charge actually happened.** Near the run's maximum cost, a delivered transcript could be marked `charged: true` even though the charge itself no longer went through — a row in your dataset overstating what you paid. Whether a transcript can still be billed is now confirmed before its row is written: once the run's maximum cost is reached, that video returns an uncharged `skipped_budget` row telling you to raise "Maximum cost per run" and re-run, the remaining videos are skipped with the usual count in the status line, and no row ever states a charge that was not taken. Runs comfortably under their cost limit are unchanged.

### 1.0.13 — 2026-08-29

- Clearer wording on uncharged no-speech rows: a video whose audio turns out to be music-only or silent now says exactly that in `statusReason`, in plain language. The same videos are recognised as before, and they are still never charged.

### 1.0.12 — 2026-08-29

One cap for the whole run, a new `retryable` column, and a channel that is only blocked where we stood.

- **`Max videos for the whole run` caps the bill across every channel.** `Max videos per channel` multiplies — ten channels at the default 50 was 500 transcripts — and there was no single number to hold that down. The new `maxItems` is that number (default 500, so nothing that works today delivers less). Channel reports, skipped videos and uncharged misses carry no result fee and never count against it, and when a run stops on this limit the status line says so and names how many videos were left.
- **Every row now says whether re-running it is worth your time.** The new `retryable` column is `true` when YouTube refused us this time and the same input is worth another run, and `false` when the answer will not change. It always agrees with the sentence in `statusReason`, so your code can branch on the column instead of parsing the note.
- **"This channel is not available in your country" is no longer read as a dead channel.** That message is about where the run fetched from, not about the channel. It now returns the new `channel_region_blocked` status, uncharged, asking you to re-run — runs can go out from different locations, and another one may be allowed to list it.
- **A video is no longer reported as removed because YouTube's service did not answer**, and **a caption track that exists but is empty now goes to speech-to-text** instead of answering "please re-run" forever. A caption fetch that was actually blocked is still a re-run, exactly as in 1.0.11.
- **A resumed run cannot bill you twice for the same transcript.** If it cannot confirm what was already charged 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.
- Store links to the rest of the steadyfetch shelf now point at the actors that are live.

### 1.0.11 — 2026-08-29

A channel that could not be read is no longer reported as a channel that does not exist.

- **Videos already listed are never thrown away.** If a page of a channel's video listing came back unreadable partway through, the run said "this channel could not be found on YouTube" and discarded every video it had already listed. Videos already found are now always transcribed, and the channel report says the listing was cut short.
- **"This channel could not be found" is now reserved for channels YouTube says are gone.** A listing page that simply could not be read is reported as a temporary problem to re-run.
- **A failed handle lookup no longer blames your link.** When the lookup that turns a handle or channel URL into a channel ID failed, the run answered either "this channel could not be found" or "this link points to a single video or a playlist, not a channel". A failed lookup now says so and asks you to re-run; those two messages are kept for links that really are wrong.
- **A blocked caption fetch is now honest, and uncharged.** When YouTube listed a video's captions but then refused to serve them, the run reported "this video has no captions" — and with speech-to-text on, the default, it transcribed the audio instead and charged the speech minutes. Such a refusal is now recognised, retried on a fresh connection, and if the captions still cannot be served the video returns an uncharged row asking you to re-run. A video that genuinely has no caption track still goes to speech-to-text exactly as before.

### 1.0.10 — 2026-08-29

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

- **A misspelled field name is answered with the field you meant.** Sending your channels under a
  name this actor does not have — `channelUrls` instead of `channels`, say — or filling in only the
  options and no channels, used to come back as the uncharged sample row, which reads as though the
  run had worked. Those runs now return one uncharged row that names the field the actor did not
  recognise, names the one it does, and shows the shape to send. Runs that do carry channels are
  unchanged, and a run with nothing set at all still returns the sample row.

### 1.0.9 — 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, caption-less videos now come back immediately as uncharged `asr_unavailable` rows that say plainly the problem is ours — instead of each one downloading its audio first and then failing one by one. Videos with captions keep delivering normally, channel listing is unaffected, and ordinary rate-limit blips keep their usual retries.
- **A "not found" answer from YouTube's video service is now final for that video.** It ships as an uncharged `not_found` row instead of being retried as if it were a temporary block — which also kept those retries from slowing down the rest of the channel.

### 1.0.8 — 2026-08-28

- **Single video links are counted honestly, and the dedicated actor is named.** A video link pasted into `Channels` is still transcribed directly as a convenience — one transcript, charged exactly as before — but the run summary now says how many transcripts came from single video links rather than channel work, and points to steadyfetch/youtube-transcript-scraper, the purpose-built actor for one-off videos. The `Channels` field description says the same.
- **The "still processing" answer now shows whose claim it is.** When YouTube declines a video with "We're processing this video. Check back later.", the row relays it as a quote — `YouTube says: "We're processing this video. Check back later."` — the same attribution rows for removed videos already carry. YouTube gives that answer even for some long-unavailable videos, so the row now makes clear the claim is YouTube's; the verdict stays temporary, uncharged, and worth a re-run.

### 1.0.7 — 2026-08-27

Review-fix build: a row's `charged` column now always matches what the run could actually bill.

- Fixed: a run that could not bill — an unmonetized copy of the actor, or a price that had not
  taken effect yet — still marked delivered transcripts as `charged: true` and counted those
  charges in the run summary, even though nothing reached your invoice. Such a run now delivers the
  transcripts uncharged and marks every row `charged: false`. The dataset reconciles against your invoice
  in every case, not just the normal one.
- Fixed: the same run also budgeted itself as though it had unlimited billable room, so the
  speech-to-text minute limit and the cost cap you set were not applied to it. On a whole-channel
  run that is the difference between a bounded job and one that walks every upload — both caps now
  hold on every run.
- If this actor's billing is ever mis-set in a way that would charge you for the rows this listing
  promises carry no result fee — videos we could not transcribe, channels we could not list, the
  the sample — the run now fetches nothing, delivers nothing and charges nothing, and says plainly
  that the fault is ours. A pricing mistake on our side can no longer put a charge on an uncharged row.

### 1.0.6 — 2026-08-27

A long video with no captions now gets an honest, permanent answer instead of being told to try
again — and the README links a real example dataset.

- Fixed: a caption-less video 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 videos 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. On a channel run that matters more
  than on a single video: one wrong verdict used to repeat across every caption-less upload in
  the channel. Videos that have captions are unaffected at any length, Shorts and short
  caption-less videos transcribe as before.
- Changed: when YouTube refuses a video'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.
- The README now links a live example dataset — a real `@mkbhd` run, four transcripts exactly as
  delivered, so you can read the output before you run anything.

### 1.0.5 — 2026-08-27

Migration-safe billing: a run that is moved to another server, or that you resurrect, no longer
repeats work you already paid for.

- Fixed: when Apify moved a run to another server mid-way — or when you resurrected a finished run —
  the run started again from the top. Videos it had already transcribed were transcribed again,
  written to the dataset a second time, and **charged a second time**. Every delivered row now
  carries an invisible marker, so a resumed run skips what it already delivered: no duplicate rows,
  no duplicate charges, and the sample row of an empty run stays a single row.
- Fixed: if the move landed between a transcript being written and its charge being recorded, the
  row said it had been charged when it had not. That one missing charge is now settled when the run
  resumes, so `charged: true` always matches your invoice — and it can never be settled twice.
- Fixed: channels that could not be listed, videos that could not be transcribed, and bad pastes
  each shipped a second identical row after a move. They are now reported exactly once.
- Fixed: `Max speech-to-text minutes` restarted after a move, so a moved run could spend up to twice
  the limit you set. The limit now covers the whole run, whatever happens to it.
- Fixed: the actor's own one-hour run ceiling also restarted after a move, quietly doubling the
  compute a single run could use. It is now measured from when the run started.
- When a run is moved, it now stops claiming new videos immediately, finishes the one in flight, and
  says in its status line how many were left for the resumed run.
- Safety rail: if a run cannot read back its own results **and** has already been charged, it stops
  instead of guessing — nothing further is charged, and the status line says why.

### 1.0.4 — 2026-08-27

Review-fix build: channel-listing honesty, speech-minute accounting, long-video speech detection,
the oversized-audio limit, subtitle timestamps and the run deadline.

- Fixed: when YouTube refused to serve a channel's video listing, the run reported that channel as
  having no public videos — a permanent verdict for a temporary problem. A listing that fails is now
  reported as temporary, says to re-run, and is still not charged.
- Fixed: when only part of a channel's listing failed — the Videos tab refused while Shorts and live
  recordings still answered — the run now delivers every video it did find and records the partial
  listing in its channel report, instead of contradicting itself with a "no videos" row.
- Fixed: a video that had to be retried reserved its speech-to-text minutes again on every attempt.
  That ate the run's minute budget, so later videos — and sometimes the retry itself — were skipped
  for a limit that had not really been reached. A video now holds its minutes once, and gives all of
  them back if it never delivers.
- Fixed: a long video with real but thinly spread speech (a four-hour stream carrying twenty minutes
  of talking) was discarded as "no speech" after the speech-to-text had already run.
  Sparse-but-real speech is now recognised and delivered. Music-only and
  silent uploads are still caught, and still cost nothing.
- Fixed: a video whose audio stream does not declare its size (live replays, some still-processing
  uploads) was downloaded whole before the per-video data limit could refuse it. The download now
  stops at the limit and the video is reported as skipped. Nothing is charged either way.
- Fixed: an SRT or VTT timestamp landing a hair under a whole second rendered a four-digit
  millisecond field (`00:00:59,1000`) that no subtitle player accepts. It now carries into the next
  second.
- Fixed: an unreadable platform timeout could switch off both the platform deadline and our own
  one-hour cap. The run's own cap now always applies, so a run still stops cleanly.

### 1.0.3 — 2026-08-27

- Fixed: on a run with no pricing attached, rows said they had been charged when nothing had been.
  Rows now report `charged: false` and an empty `chargeEvents` ledger unless a charge was really made.
- README: output shown first, pricing stated up front, and a plain list of what can fail and why
  none of it is charged.

### 1.0.2 — initial release

Paste a YouTube channel and get every video's transcript in one run.

- Accepts a channel URL, an `@handle`, a `/channel/UC…` ID, a `/c/` or `/user/` URL, or a bare
  channel ID, in any casing. A single video link works too and transcribes just that video.
- Enumerates the channel's Videos, Shorts and live recordings and de-duplicates them into one
  list. Listing the channel adds no separate fee — you are charged only for transcripts delivered.
- `Max videos per channel` caps how many of the newest videos are transcribed.
- Captions first: manual captions are preferred over auto-generated ones, and you can ask for a
  specific language (regional variants like `pt-BR` match `pt`).
- Built-in speech-to-text for videos that have no captions, so caption-less videos still return
  real text. Each row says which route produced it.
- Output as JSON with timestamped segments, plain text, SRT or VTT.
- Charged once per delivered transcript, plus speech-to-text minutes only when speech-to-text
  actually ran. A captioned video never charges a minute.
- Nothing is charged for a channel that cannot be found, a channel with no public videos, or a
  video that is private, removed, age-restricted, region-blocked, still live, silent, music-only,
  or that YouTube refused to serve. A temporary refusal is reported as temporary and says to
  re-run; only a clean answer from YouTube is reported as permanent.
- `Max videos` and `Max speech-to-text minutes` are hard limits: the run stops cleanly and every
  skipped row names the limit that stopped it.
- Every row carries `charged`, `statusReason` and `chargeEvents`, plus the channel it came from
  and its position, so the invoice reconciles from the dataset itself.
- A run with no channels returns one uncharged sample row showing the exact output shape.
