# Changelog of Podcast Transcript Scraper — Audio & Video to Text, RSS & SRT (`steadyfetch/media-transcriber`) Actor

- **URL**: https://apify.com/steadyfetch/media-transcriber/changelog.md
- **Full Actor documentation**: https://apify.com/steadyfetch/media-transcriber.md

## Changelog

### 1.0.86 — 2026-09-19

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

### 1.0.85 — 2026-09-19

- **An item your run's time limit cannot finish is no longer started at all.** A run used to keep back one minute before starting the next item — less than any single step an item needs, let alone all of them: fetching the file from its host, reading a page for its media link, decoding the audio, and the speech-to-text call itself. An item begun inside that last minute therefore had its file fetched and decoded, and then your run's limit ended it with no transcript to show for the time and transfer already spent. A run now checks that **every** step of an item fits in the time it has left before it starts one. When it does not, the item is handed straight back as the same uncharged row a run already used when it ran out of time: the run's own limit, and the advice to raise it or split the list — nothing is fetched, decoded or charged for it. One exception keeps a short run useful: a run that has delivered nothing yet still tries one item, provided there is enough time left to finish that item. Separately, decoding the audio now leaves room for the speech-to-text call that has to follow it, instead of being allowed to use every second the run had left. Nothing about the price, the charged event, the input form or any output column moved, and a run with time to spare behaves exactly as it did.

### 1.0.84 — 2026-09-16

- **When your run timeout is what stopped an item, the row now says so instead of blaming the file, the site or the speech service.** 1.0.83 gave every long step — the download, the audio decode, each speech-to-text call — the smaller of its own limit and the time your run had left, so a run ends inside the timeout you set. That fix had a side the previous build did not cover: a step we cut short *looks* like a step that failed, so an item stopped by your own timeout was handed back as "the media could not be decoded" or "speech recognition did not complete, please re-run" — which reads as something worth paying for a second run to retry, when a longer timeout is what it actually needs. Those items now ship the same uncharged row a run already used when it ran out of time before starting an item: the run's own time limit, and the advice to raise it or split the list. Nothing else moved — a genuine refusal, a dead link, a file with no audio and a real speech-service outage all keep their own wording exactly as before, and a run with time to spare is untouched.
- **Three more steps now end inside your run timeout.** The two short media checks that measure a file's audio track and its exact length, and the lookup that reads a page for its media link, still had time limits of their own only — the page lookup could hold a run for a minute and a half after your timeout had passed. All three are now bounded by the run as well, and a lookup we stop is ended outright rather than asked politely (asked politely, the tool it uses exits tidily and its half-finished answer reads as "this page has no video"). No input field, output column, charged event or price changed.

### 1.0.83 — 2026-09-15

- **A run now stops on the timeout you set, instead of being killed past it.** Fetching a file from a slow host, decoding it, and each speech-recognition call each had a time limit of their own, measured from the moment that step started — none of them knew how much of your run's own timeout was left. An item that began near the end of the window could therefore keep working long after the run should have ended, and Apify killed the container mid-step: the run showed as timed out, and its status line, its `_summary` row and its final record never arrived. (Transcripts already delivered were still yours and still charged exactly as their rows said.) Every one of those steps is now given the SMALLER of its own limit and whatever the run has left, so the run ends inside your timeout, says on its status line that the run's own limit stopped it, and books the item it could not finish as an uncharged row. A run with time to spare is completely unchanged — every step still gets its full normal limit. Nothing about the price, the charged event, the input form or any output column moved.

### 1.0.82 — 2026-09-14

- **A Facebook or TikTok link only ever transcribes that site's OWN video.** 1.0.81 added this rule for X (Twitter) post links; it now covers the Facebook and TikTok routes as well. Several of the accepted link shapes — an `fb.watch` or `facebook.com/share/…` link, a `tiktok.com/t/` or `vm.tiktok.com` share link, a `tiktok.com/v/<id>` link — are shorteners that are followed to wherever they lead, and the lookup behind them will resolve whatever it finds at the end. If that turns out to be another site's video, the run now hands it back as an uncharged row naming the address to paste instead, rather than transcribing and charging for it. Nothing about the price, the charged event, the input form or any output column moved, and a Facebook or TikTok link that carries its own video is unaffected.

### 1.0.81 — 2026-09-14

- **X (Twitter) post links now transcribe.** Paste `x.com/<user>/status/<id>` (or the `twitter.com`, `mobile.`/`m.`, `i/status` and `i/web/status` forms) and the post's own video comes back as text, timestamps and subtitle files, charged per audio minute like any other link. An X profile, search, hashtag page, Space or live broadcast has no single finished video to pick and comes back uncharged naming the shape that works, and a post whose video lives on another site is not followed from here.

### 1.0.80 — 2026-09-14

- **The other half of the scoped-token fix: a token that can READ your memory but not WRITE it now says so.** 1.0.79 covered a token that could not open the "you never pay for the same item twice" memory at all. A token that carries key-value store **Read** but not **Write** — or Write but not Create on a store your account does not have yet — is quieter and costs the same: the run opens the memory, hands back every repeat correctly, and then remembers nothing it transcribed, so your **next** run is charged for the whole delivery all over again. The run now says so the moment the first write is refused — on its status line, in the run log, and on an uncharged row naming the permission to grant (key-value store **Write** and **Create** under **Settings → API & Integrations**, or Actor runs set to *Full access*). Everything you asked for is still transcribed and charged exactly as before, and nothing about the price, the charged event, the input form or any output column moved.
- **A run moved to another server no longer repeats its own warnings.** The uncharged notes this actor writes about a run — the two token-scope warnings, the note about a Start with only settings set, and the note about a sibling's field name — were pushed a second time when Apify moved a run mid-way to another server. Each is now written once per run, however many servers the run passes through.

### 1.0.79 — 2026-09-14

- **A run started with a scoped API token no longer pays twice in silence.** The "you never pay for the same item twice" memory is a key-value store in your own Apify account, and a run started over the API with a scoped token in *restricted-access* mode cannot open it unless that token has key-value store **Read, Write and Create** permission — so items you had already transcribed were fetched and charged again, with nothing on the run naming the cause. Such a run now says exactly what happened and exactly how to fix it: on the run's status line, in the run log, and on a new uncharged row placed first in the dataset (grant the token key-value store Read, Write and Create under **Settings → API & Integrations**, or set Actor runs to *Full access*, or start the run from the console). Charged rows still carry `repeatCheck: "unavailable"`, and **New items only** now says when it could not filter for the same reason. The run itself is unchanged — everything you asked for is still transcribed, and nothing about the price, the charged event, the input form or any output column moved.

- **A transcript too big for one dataset row now still reaches you, instead of failing the item.** Apify refuses any dataset row whose JSON is 9 MB or more, and a marathon recording with all four output formats ticked can get there. Such a row is now measured before it is sent: the largest artifacts (the timestamped segments, the SRT, the VTT, the plain text) are saved in full to the run's key-value store, and the row itself carries a note naming each one and the key to read it back from — open the run's **Storage** tab, **Key-value store**. Nothing is dropped, nothing is split across two rows, and the charge for the item is exactly what it always was: per audio minute transcribed. Normal-length transcripts are untouched and still arrive inline.

### 1.0.78 — 2026-09-13

- **Sending `url` instead of `urls` now works.** A run that named its link in `url`, `videoUrl`, `audioUrl` or `mediaUrl` — the obvious singulars, and what an agent or a first API call usually reaches for — used to come back with nothing transcribed and an uncharged row asking for `urls`. Those names are read now, and so is `podcastUrl` for a whole show. Your links are transcribed as if they had arrived in **Media URLs**, with one uncharged row on the run naming the field to use next time; a link sent in both fields is still one item and one charge.
- **A field name we really do not read says so plainly.** The uncharged row and the run's status line now name the field you set *and* the field this actor reads, instead of only ours.
- Nothing charged or priced changed: same per-audio-minute event, same price, same input fields on the form.

### 1.0.77 — 2026-09-13

- **Subtitle files and timestamps now come back by default.** *Formats to return* arrived with only **Plain text** ticked, so a run started without touching the form came back with `srt: null` and `vtt: null` and an empty `segments` — while the listing promises text, SRT, VTT and timestamped segments, and the README sample row showed them filled in. All four are now the default: paste a link, press **Start**, and the row carries the transcript, the timestamped segments and both ready-to-upload subtitle files.
- **It costs nothing extra.** You are still charged per audio minute transcribed, exactly as before — the formats you ask for have never changed the price. A run that wants a smaller row can untick any of the four; **Plain text** is always returned.
- Nothing else moved: no price, no charged event, no new input field, no new output column, and a run that already asked for its formats explicitly behaves exactly as before.

### 1.0.76 — 2026-09-13

- **The listing is now called *Podcast Transcript Scraper — Audio & Video to Text, RSS & SRT*.** The actor takes podcast feeds as of this build, and the name now says so; "video to text" is still in it, and nothing about what the actor does, what it charges or what it delivers changed with the name.
- **Transcribe a whole podcast, not one episode at a time.** A new **Podcast feeds or show links** field (`podcastUrls`) takes a show's RSS address — `https://example.com/feed.xml` — or its Apple Podcasts **show** link (`podcasts.apple.com/…/id1200361736`), finds the feed itself, and transcribes the newest episodes in one run. **Episodes per show** (`maxEpisodesPerShow`) decides how many; the default is 10, newest first, and a show that publishes fewer delivers all it has and says so. Single episodes and files are unchanged: they still go in **Media URLs**.
- **A re-run of the same feed only pays for what is new.** Each episode is remembered by the feed's own id for it, so a second run of the same show hands back everything already transcribed for your account — `repeat: true`, not fetched, not charged — and transcribes only what has been published since. A weekly Task on one feed costs a week of episodes, not the back catalogue. **New items only** works on them too: tick it and the repeats are left out of the dataset entirely.
- Four new output columns on every row: `podcastShow`, `episodeTitle`, `episodeGuid` and `publishedAt`. They carry what the feed said about the episode, and they are `null` on every row that did not come from one.
- Never charged: a feed we cannot reach, a link that turns out to be a web page rather than a feed, an Apple show id the catalogue does not list, a feed with no downloadable audio, and an episode your account already had. Each is one uncharged row naming what was found and the next step, and a host that was simply busy says it is worth running again.
- An Apple Podcasts **show** link pasted into **Media URLs** still comes back uncharged asking which episode you meant — it now names the new field to use instead.
- Nothing else moved: no price, no charged event and no existing input field changed. Four output columns were added, as described above; every column that was already there behaves exactly as before.

### 1.0.75 — 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.74 — 2026-09-12

- **Asking for a bigger minutes limit than one run can do no longer stops the run before it starts.** "Max minutes per item" refused anything above 100,000 and "Max total minutes" anything above 1,000,000 — 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, and a bigger ask runs at the limit with the run's status line saying what you asked for and what was used. `0` still means "no limit" on both.
- Nothing else moved: no price, no charged event, no input field and no output column changed.

### 1.0.73 — 2026-09-11

- **Run it on a schedule and get back only the new episodes.** A new **New items only** switch (off by default, so nothing changes unless you tick it). Leave it off and the actor behaves exactly as it always has: an item your account already has comes back from the run that transcribed it, marked `repeat: true`, not charged — a re-run of the same list is a complete archive that only pays for what is new. Turn it on and those items are left out of the dataset entirely: nothing is fetched, nothing is charged, and the run's status line says how many were skipped — so a Task on a weekly Schedule hands your Google Sheet, Slack, n8n or webhook only the new transcripts. What you are charged is identical either way; this only decides what lands in the dataset.
- Only an item this actor actually delivered a transcript for counts as one you already have. An item that came back without one — unreachable, no speech, or stopped by a cap — does not, so the next run tries it again rather than silently dropping it for 90 days.
- The README now carries a **Run it on a schedule** section: Task → Schedule → integration, how often the data actually changes, and how chaining a scheduled scraper run behaves.
- Input schema: the description now opens with the call that works, what a minute costs, the never-charged list, the 256 MB rule, the tested-sites list and where YouTube/Instagram/ad-library links go — the screen an AI agent reads. **Media URLs** leads with the value shape and one example and then names, in its first three sentences, the size bound and which links belong in another actor; the full tested-sites list stays above it on the form. A run that delivered nothing now says why on its status line, with the fix.
- Nothing else moved: no price, no charged event, no output column changed, and you are still charged only for the audio minutes actually transcribed.

### 1.0.72 — 2026-09-11

- **TikTok video links are transcribed here now, like any other supported link.** Paste `tiktok.com/@<user>/video/<id>`, a `tiktok.com/t/<code>` or `vm.tiktok.com/<code>` share link, or `m.tiktok.com/v/<id>`, and it is fetched and transcribed exactly as a podcast page or a direct file link is. Until this build every `tiktok.com` link came back as an uncharged row pointing at our TikTok **ads** actor, which only reads Creative Center ad links — so a pasted video was refused twice over, and the row closed by suggesting a direct media file link a TikTok buyer has no way to get. TikTok is now the thirteenth tested site on the "Which links work" list and on the input form.
- **TikTok Creative Center ad links are unchanged**: `ads.tiktok.com/business/creativecenter/topads/…` still comes back as an uncharged row naming [the TikTok ads actor](https://apify.com/steadyfetch/tiktok-ads-transcript-scraper) and the field to paste it into, because that actor reads the ad's own creative and does it better. A TikTok profile, hashtag, sound or Discover page has no single video to pick, so it still comes back uncharged — and that row now leads with the link shapes that DO work here instead of sending you away.
- **Links sent in `startUrls` are read too.** If you came here from one of our Instagram actors, or from any scraper that uses Apify's conventional Start URLs field, your list is read and transcribed exactly as if it had arrived in `urls` — both plain links and the `{ "url": … }` crawler shape — with one extra uncharged row naming `urls` as the field to use next time. A creator handle in that list is not a link, so it gets the same honest uncharged row any non-link gets.
- **A row that carries both a platform video link and the media file itself now uses the file.** Chaining a scraper whose rows hold, say, `webVideoUrl` beside `downloadAddr` takes the file: it is one fetch instead of a lookup and a fetch, and it cannot be refused by the platform. Nothing about what you are charged changes — it was, and is, one item and one charge per row.
- A TikTok link that cannot be reached behaves exactly as every other link does: an uncharged row saying so, with a re-run suggested where a re-run can help. Nothing is ever charged for a link we could not fetch.
- Nothing else moved: no price, no charged event, no input field and no output column changed, and you are still charged only for the audio minutes actually transcribed.

### 1.0.71 — 2026-09-11

- **Facebook reel and video links are transcribed here now, like any other supported link.** Paste a reel (`facebook.com/reel/<id>`), a watch link (`facebook.com/watch/?v=<id>`), a `/videos/<id>` link or an `fb.watch` share link and it is fetched and transcribed exactly as a podcast page or a direct file link is. Until this build every `facebook.com` link came back as an uncharged row pointing at our Facebook **ads** actor, which only reads Ad Library links — so a pasted reel was refused twice over. Facebook is now the twelfth tested site on the "Which links work" list and on the input form.
- **Facebook Ad Library links are unchanged**: `facebook.com/ads/library/?id=…` still comes back as an uncharged row naming [the Facebook ads actor](https://apify.com/steadyfetch/facebook-ads-transcript-scraper) and the field to paste it into, because that actor reads the ad's own creative and does it better. A Facebook profile, a photo post or a page's Reels tab has no single video to pick, so it still comes back uncharged — and that row now leads with the link shapes that DO work here instead of sending you away.
- A Facebook link that cannot be reached behaves exactly as every other link does: an uncharged row saying so, with a re-run suggested where a re-run can help. Nothing is ever charged for a link we could not fetch.
- Nothing else moved: no price, no charged event, no input field and no output column changed, and you are still charged only for the audio minutes actually transcribed.

### 1.0.70 — 2026-09-11

- **Every run now tells you how much of your list came back.** The run page reads *Transcribed 7 of 12 items*, and the run's OUTPUT carries the whole count — how many were transcribed, how many were refused, and the reason for each refusal (unsupported site, nothing to transcribe on the page, over the size limit, and so on). You no longer have to read the dataset row by row to work out what a run did and did not cover. They are counts and not percentages, deliberately: *75% covered* reads the same over four links and over four hundred, and only one of those is worth acting on.
- **The 256 MB per-item size limit is now on the input form beside the URL field**, instead of only turning up in the row that refuses an oversized file. A file over it still comes back as an uncharged row saying so, before any of it is transcribed — long meeting and conference recordings are the ones that cross it, and an audio-only version of the same session is usually far smaller.
- **The store listing leads with what buyers actually type.** The actor is now called **Audio & Video Transcriber — Speech to Text Transcription & SRT**, and the top of the page carries the one-click MCP pin, this actor's id, the one required input field with a real example, and the field that caps what a run can spend. An AI agent reads only the first part of a listing, and until this build those sat below the cut. Every word about what you are charged is unchanged.
- **The private run-report this actor writes for our own support** — counts only, never anything you typed — now also records whether a run that stopped at a cap you set had actually filled it, and the per-event prices the run was billed at.
- Nothing else moved: no price, event or dataset column changed, and you are still charged only for the audio minutes actually transcribed.

### 1.0.69 — 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 file. When that stamp landed far enough out, the whole transcript was judged unreliable and the file 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 subtitle output 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.
- Nothing else moved: no price, event, input field or output column changed, and only minutes actually transcribed are charged.

### 1.0.68 — 2026-09-10

- **A run that hits a busy host now waits a length that fits your run, instead of skipping the wait entirely.** When a host refuses a file on every route, the run pauses and walks the whole ladder again from a fresh route. 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 host that frees up quickly costs you far less waiting. A short run and the untouched Start form now get a real second walk where they used to get none.
- **The waiting is also sized to what is still worth recovering**, measured from the audio minutes this run has actually billed — while a run that has not delivered anything yet always gets one pause whatever its size, because the first thing you see should not be an empty result.
- Nothing else moved: no price, event, input field or output column changed, and only minutes actually transcribed are charged.

### 1.0.67 — 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.
- **Rows that say a link is gone now say what to do about it.** A media link taken from a scraped page or an earlier run's row is not a permanent address: the file is often still published while that link is not. An `unreachable` row now names that — conditionally, never as a claim we cannot back — and where the link carries its own expiry stamp the row says whether it had lapsed, and when. A link whose stamp has NOT lapsed is told so, so nobody re-scrapes for nothing. The same wave: a page that answers "not found" says how to check it, a file with no audio track says a re-run cannot help and what can, and a link that returned a web page names the link that would have worked.
- **The input form now names the sites that work and the ones that go elsewhere, before you start.** The **Media URLs** field lists all eleven tested page sites (Apple Podcasts included — it was missing) and says that YouTube, Instagram, TikTok, Facebook, LinkedIn and Google Ads Transparency Center links belong in their own steadyfetch actor, with the row naming the actor and the field. The README's "Which links work" section opens with the same pointer. A run that would have come back all-uncharged `unsupported_site` is now avoidable before it is started, and if you already hold the direct media file link for one of those platforms, it still works here.
- Nothing else moved: no price, event, input field or output column changed, and you are still charged only for the audio minutes actually transcribed.

### 1.0.66 — 2026-09-09

- **Apple Podcasts episode pages now transcribe, and they are on the tested list.** Paste an episode link — the kind with `?i=` and an episode id in it — and the actor reads the podcast's own audio address straight out of the page and transcribes it, exactly as it already did for Libsyn, Buzzsprout, Megaphone, Acast and Spotify for Creators. This has been quietly working for a while; it is now measured, kept as a test fixture and advertised, which is the only reason to rely on it. A **show** page is a different thing and still comes back uncharged: it lists every episode in the feed, so rather than guessing which one you meant, the row says so and asks for the episode link. Apple **Music** (`music.apple.com`) is unchanged — it streams only inside the app and there is no file to fetch.
- **You can no longer be charged twice for one episode that reached us two different ways.** An episode fetched through its page and the very same episode pasted as its own audio address were remembered as two different things, so the second run transcribed and billed it again. They are now one item: the file itself is what your account remembers, whichever link brought it. Everything remembered before still counts — nothing was forgotten to make this work — and the change can only ever charge you less, never more.
- **A transcript can no longer be handed back for the wrong file.** Where a link resolved through a general-purpose reader rather than a site we have measured, the only name that reader gives a file is its filename — so two unrelated recordings both called `1.mp3` looked like the same recording, and the second could have come back with the first one's transcript. It cannot now: a filename on its own is never treated as an identity, and a stored transcript is never returned unless the file it was made from matches the file you asked about.
- **The field name from our other transcript actors is accepted.** If you were pointed here from our YouTube, Instagram, TikTok, Facebook, LinkedIn or Google Ads actor and sent your links in `videoUrls`, `reelUrls` or `adLibraryUrls`, those links are now read and transcribed as usual instead of the run reporting that you set none. One extra uncharged row names `urls`, this actor's own field, so you can switch when convenient. Nothing was renamed: `urls` is unchanged and every existing input keeps working exactly as before.
- **An over-sized file now suggests something you can actually act on.** The row already said the file was over the per-item limit; it now points at the audio-only version that long meeting and conference recordings usually publish alongside the video, which is far smaller, before falling back to splitting the file.
- Nothing else moved: no price, event, input field or output column changed, and you are still charged only for the audio minutes actually transcribed.

### 1.0.65 — 2026-09-08

- **A link that had simply expired no longer reads as a website blocking you.** When a file came back refused on every route, the row said the host was protecting it from direct download and that re-running was unlikely to help. That is right for a site that gates its files, and wrong for the commonest case by far: a signed link from a scraper — Instagram, Facebook, TikTok and the like — that quietly aged out between the scrape and the transcription run. Those links carry their own expiry stamp, so the row now reads it. If the stamp had passed, the row says the link **expired**, says when, and tells you the one thing that fixes it: re-run the scraper that produced the links and transcribe the fresh ones straight away (chaining this actor onto that run avoids it entirely). A signed link whose stamp we cannot read names both possibilities and both fixes rather than picking one. A plain link with no signature reads exactly as it did before. Nothing was charged for these rows before and nothing is now.
- Nothing else moved: no price, event, input field or output column changed, and you are still charged only for the audio minutes actually transcribed.

### 1.0.64 — 2026-09-07

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

### 1.0.63 — 2026-09-06

- **"txt" now means plain text.** "Formats to return" takes text, segments, srt and vtt, and anything else stopped the whole run before a single item was started — nothing transcribed, nothing charged. That is still right for a value we cannot place: returning only the formats we do recognise would hand you a different set of files than you asked for, and you would pay twice to get once what you asked for. It was wrong for a value that can only mean one thing. The everyday ways of writing those same four are now simply understood — txt, plaintext and plain for text, subrip for srt, webvtt for vtt, segment for timestamped segments, and the labels the dropdown itself shows — in any capitalisation, with or without a leading dot, exactly as spacing and capitalisation have always been. Nothing is announced, because nothing about your result changed: you get the files you asked for. A value that could honestly mean two of them — "subtitles", "timestamps" — still stops the run, and now says which ones it could have meant instead of choosing for you. A value nothing could mean still stops the run in the same words it always used, and one that is a letter away from a real one is named back.
- **A chained run no longer charges twice for an item your account already has.** Every row carries both the link you gave us and the address the file was actually served from. Chaining one run into the next followed the served address instead of your link — and your link is what your account remembers an item by. On most items the two are the same and the second run hands the transcript straight back, uncharged, which is what it did. When the host serves the file from a different address than the one you passed, the second run did not recognise it as something already transcribed for you, so it transcribed and charged for it again. A chained run now follows the link you gave, so an item you have already paid for comes back uncharged whichever way it reaches us.
- Nothing else moved: no price, event, output column or status changed, waiting is still uncharged, and only delivered transcripts are charged. The only accounting that moved is a repeat that used to be missed, so this build charges less, never more.

### 1.0.62 — 2026-09-06

- **A host we cannot reach directly is no longer reported as a host that does not exist.** Some media servers publish only one kind of internet address, and it is a kind our machines cannot dial straight out. The run used to demand the other kind, get "no such address" back, and file your link as a permanent mistake — so the row blamed the link, and the run's own retry through a different route never happened, because "does not exist" is not something a different route can fix. The run now *prefers* the address it can dial instead of demanding it, and a host it still cannot reach directly is retried through another route like any other refusal.
- **The "too large" row now reads like the other two.** A file that ran past the 256 MB per-item limit while it was still downloading came back with a raw byte count and nothing else — no size in MB, no note that nothing was charged, no suggestion. It now names the limit in MB, says nothing was charged, and tells you what to try instead: an audio-only version, or the file split up. That is what the rows for files whose size is known up front have always said.
- Nothing else moved: no price, event, output column or charge changed, waiting is still uncharged, and only delivered transcripts are charged.

### 1.0.61 — 2026-09-06

- **A block of links pasted into one row is now read as the list you meant, whatever separates them.** The links box takes one link per row. Pasting a whole block of links into a single row was read as one very long address instead of the separate links — so a paste of fifty links became a single item, and because most media hosts sign their addresses, that one item was refused by the host and nothing was transcribed. 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 link is then looked up, transcribed 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.** A player or redirect address holding a second address in its query or its path is left whole, not broken in two.
- **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 still comes back as a single uncharged row saying so, rather than one row per word.
- Nothing else moved: no price, event, output column or charge changed, waiting is still uncharged, and only delivered transcripts are charged.

### 1.0.59 — 2026-09-05

- **A busy site is now waited out before the lookup gives up.** Every page link goes through two steps: looking the page up to find the media, then fetching it. The fetch already waited a busy host out for minutes; the lookup did not — one try, one retry two seconds later, and then "the site refused this lookup temporarily, please re-run", about three minutes into an hour you had paid for. Because the lookup comes first, a refusal there also meant the patient fetch below it never ran at all. A run that still holds time now pauses (a minute and a half to three minutes, varied so parallel runs do not retry in step) and looks the page up again, sharing one waiting budget of at most eight minutes with the fetch so a storm on one item cannot use up the wait for the rest.
- **Everything that is an answer still comes back at once**: a removed video, a paywall, a live stream, a page with no media, a link that is not media at all, and your own time limit.
- Nothing you see changed otherwise: no price, event, output column or charge moved, waiting is uncharged, and only delivered transcripts are charged.

### 1.0.58 — 2026-09-05

- **The run record now adds up.** At the end of every run the items you sent 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 of items you asked for. Four things could break that sum before. A run that stopped early never wrote down *why*, so items it did not reach were unexplained. An uncharged miss was counted once as a miss and a second time under the kind of miss it was. A link stopped by a setting we could not accept was counted twice the same way. And items an earlier part of a moved run had already delivered were missing from the count altogether.
- **A run that ends on one of your own settings now says which one.** Your total-minutes cap, your maximum total charge, the run timeout, a move to another server, an unusable pricing setup and a resumed run that could not read back its own earlier rows are each written down under their own name, so a short run can be told from a run that simply finished the list.
- **Items left behind when the platform moves a run are counted.** They already came back as uncharged rows; now they are also counted as what they are — items this run planned and handed to the container that continued it.
- Nothing you see changed: no row, price, event, status or output column moved, and no run is charged differently.

### 1.0.57 — 2026-09-05

- **Podcast episode pages on Libsyn, Megaphone, Buzzsprout and Acast are read for the episode's own audio file, and are now tested that way.** All four have been on the tested-sites list from the first build, but only their direct audio addresses were ever measured — a page link on them fell to the same general "read any page once" path an untested site gets. Each is now a tested page surface with a real public episode page kept as a fixture, so a page link is read for the file it plays and transcribed as usual, and the list can no longer promise more than the actor does. Their audio addresses keep working exactly as before, and now take the direct route without reading a page first.
- **A Megaphone episode page used to come back empty.** Its page names the same audio file twice, once under each spelling of the web address, and the two read as two different files — so the run answered "this page lists 2 audio or video files and does not say which one you meant" and charged nothing. One file written two ways is now one file, on any page that does that.
- **The tested-sites table says what it means.** S3 and CloudFront links were listed under "page links on tested sites"; they are direct file links, which work from any host, and they are named as that instead. The page-link list is ten sites, and every one of them is checked against a real saved episode page each time this actor is built.
- **Uncharged rows are told apart in the run record.** An item over your size or minute limit, a file with no audio track, this run's own no-speech allowance and the two ways transcription itself can stop each carry their own reason now, instead of landing in one total. Nothing a buyer sees changed: no row, price, event or output column moved.

### 1.0.56 — 2026-09-05

- **A host that is simply busy is now waited out instead of reported.** When a file could not be fetched on any network route this run had — a rate limit, a server error, a dropped connection, a challenge page in front of the file — the run gave up in seconds and asked you to re-run, while nearly all of the time you had paid for went unused, and a re-run started moments later usually met the same answer. A run that still has time now waits for that to clear (a minute and a half to three minutes, varied so parallel runs do not retry in step), then tries the whole route ladder again. Everything that is an answer rather than weather comes back at once, exactly as before: a link that is gone, a page instead of a file, a live stream, a paywalled player, a file with no audio, and a host that answered 401, 403 or 451 from every route — no waiting can change those.
- **The default sample can now use that waiting.** Pressing Start with no links (or with only settings set) transcribes a fixed sample clip under its own time limit, and that limit was shorter than a blocked fetch plus the wait plus a second try — so on the run most people see first, the waiting above could never have happened. The limit is now sized to hold all three and the time needed to write the result: seven minutes at its very worst, while a sample that is served still finishes in seconds.
- **The default sample always runs live.** It is no longer answered from the record of items your account already has, and its clip is not added to that record: a bare Start fetches, decodes and transcribes the sample clip and charges for it like any run, every time. Pressing Start twice therefore pays for one audio minute twice — cents — and in exchange the sample always proves the actor is working end to end. Any run that names its own links, dataset or rows is unchanged: items your account already has still come back from the run that delivered them, uncharged.
- No row, price, event or output column changed.

### 1.0.55 — 2026-09-05

- **Spotify for Creators episode pages transcribe again — including the current `creators.spotify.com` links.** A page link on Spotify for Creators (formerly Spotify for Podcasters / Anchor) came back as an uncharged "the link returned a web page" row instead of a transcript: the page lookup this actor used no longer read those pages, and the site's current address was not on the list at all. The episode page is now read directly for the audio file it plays — on `creators.spotify.com`, `podcasters.spotify.com` and `anchor.fm` alike — and that file is transcribed as usual. Nothing was charged for the rows that came back empty.
- **Any other page link is now read once for the media file it plainly embeds.** If the page carries exactly one audio or video file — a podcast host's episode page, for example — that file is transcribed. A page with none, or one listing several files without saying which, comes back as an uncharged row that names what to paste instead: the direct file link, which works from any host, or a page on a tested site. The tested-site list itself is unchanged.
- **Spotify's own player pages say so plainly.** An `open.spotify.com` episode or show link streams only inside Spotify's app, so there is no file to transcribe. It now comes back, before anything is fetched, as an uncharged `drm_or_paywall` row saying exactly that and where to paste instead. Amazon Music and Apple Music player pages are answered the same way.
- **Empty rows now say why, in their own words.** A page on a site outside the tested list is reported as `unsupported_site` with the working route, never as "not a media file", and the run's outcome record keeps the kinds of uncharged miss apart.
- **A file refused on every network route is now called what it is.** When a host answers 401, 403 or 451 for a direct file, this actor retries it through a second network route before giving up. If the second route was refused too, the uncharged `unreachable` row used to say the refusal was temporary and to re-run — but a hotlink check or a geo gate answers every direct fetch the same way, so a re-run walked into the same answer. That row now says the host is refusing the file to us on every route this run has, suggests a link from the host's own feed or player, and carries `retryable: false`. A refusal seen on only one route (the run ran out of time before the second, or a throttle) keeps the temporary wording and `retryable: true`.
- **The speech-to-text service's own pacing is honoured.** When the transcription service asks this actor to wait a given number of seconds before retrying, the retry now waits that long (capped at a minute) instead of a fixed short pause, so a rate-limited item is retried when it can succeed rather than into the same limit.

### 1.0.54 — 2026-09-05

- **A media item near the run's time limit now stops cleanly instead of being cut off mid-transcription.** Looking up the media, downloading it and transcribing it each take time; if the run does not have enough left, that work is not started — the item comes back as an uncharged "please re-run" row (you can also raise the run timeout) rather than a run cut short. A run that reaches its limit ends with the transcripts it already has.

### 1.0.53 — 2026-09-04

- **Narrowing one option no longer costs you the sample run.** Press **Start** with nothing set and this actor transcribes its fixed 25-second public-domain clip so you see real output. Change one thing first — a format, a minutes cap — leave **Media URLs** empty, and you used to get a single row asking for links instead of any output at all: the buyer who touched nothing was served better than the buyer who narrowed an option. Now your settings are kept and the same clip runs under them, charged like any run. One extra uncharged row names which settings were yours and how to run your own media.
- **One option still answers with guidance instead of a transcript, and says why.** The sample clip is an English recording, and **Language hint** tells the model what to decode, not what to filter — set to another language it would invent words that are not in the clip and charge you for them. So a hint naming another language keeps its uncharged row, and the row names the field, the reason and both ways out. `auto` and `en` run the sample as usual, and a hint this actor cannot read at all is still simply ignored with a note.
- **Two other things still answer with guidance**, unchanged: a field name this actor does not recognise, and a **Dataset ID** or **Dataset items** you set and left blank — your list, answered with a sample, would read as if it had worked.
- A row on such a run no longer opens by saying no links were given — it says which half was missing.
- An unrecognised value in **Formats to return** still refuses the run before anything is fetched, exactly as before.

### 1.0.52 — 2026-09-04

- **A format this actor does not recognise no longer swallows your list.** Setting "Formats to return" to a value the actor cannot deliver refuses the run — that part is unchanged, because returning only the formats we recognise would hand you a different set of files than you asked for. What changed is the answer: you used to get one row for the whole run, however many links you had pasted. Now the row that names the value comes first, and then every link you sent gets its own row saying it was not transcribed and which field stopped it. Nothing is fetched, nothing is transcribed and nothing is charged, exactly as before.
- **Every refusal now names the input field it is about** — "Media URLs", "Formats to return", "Dataset ID" or "Dataset items" — in the run's own record as well as in the row you read. That is how a field that is turning people away gets noticed and fixed quickly instead of looking like a scatter of unrelated typos.
- **The run record now reports what you asked for, not what survived.** A run where every item came back from your account's repeat memory, or where a field stopped all of them, used to report an ask of zero and could pass unnoticed. It now reports the number of items you sent.
- **The closing line no longer calls a good link unusable.** It now keeps three facts apart: entries that were not readable links, links that were fine until a setting stopped the run, and a setting or chained dataset answered with its own uncharged row.
- 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.49 — 2026-09-04

- **An item already transcribed for your account is never charged a second time.** Re-running the same links used to fetch, decode, transcribe and charge for every one of them again — on this actor the most expensive repeat in the store, because a long file is many charged audio minutes. The run now recognises an item your account already has and hands it back from the run that delivered it: `repeat: true`, `firstSeenAt`, `firstSeenRunId`, `charged: false`, nothing fetched and nothing sent to speech recognition. The status line counts them and `OUTPUT.repeats` holds the number. Your account keeps this memory in the key-value store `media-transcriber-watch-account`; delete it to forget everything.
- **It works across link shapes.** A page link is also remembered under the site's own id for the media, so the same track pasted as a share link today and as its full permalink tomorrow is one item and one charge.
- **If the memory cannot be read, the run still runs.** It transcribes 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`, `firstSeenAt`, `repeat` and `firstSeenRunId`.**
- A repeat comes back even when our speech service is down or your cost cap is spent — it was already paid for, so nothing stands between you and the row.
- A handed-back row now carries the same hidden delivery key every other row carries, so a run the platform moves to another server mid-way cannot write it twice.
- **The status line is shorter.** The run page cuts it at 500 characters and the tail — the caps, the reconciliation and the support line — is the half that must survive; several clauses now say the same thing in fewer words, and where a long run still will not fit, the "values this run could not use" note steps aside with a pointer to the run's OUTPUT record, which carries it in full.

### 1.0.47 — 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.45 — 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.44 — 2026-09-04

- **A problem inside the speech recognition service is no longer reported as a problem with your file.** Every refusal from that service used to read the same way — a permanent, uncharged miss saying the media held nothing we could transcribe — so "the service changed something on its side" and "this file has no usable audio in it" looked identical in your dataset. A fault on the service's side now ships as an uncharged row marked `retryable` that asks for a re-run, and a file we genuinely cannot transcribe keeps the honest, uncharged miss it always had. Nothing about what is charged changed.

### 1.0.43 — 2026-09-04

- **A long file is no longer turned away before it is tried.** A single item over an hour — a podcast, a webinar, a recorded call — was refused with a note about a spent allowance even on a run that had transcribed nothing yet, and a list of long files stopped after the first one. The allowance now counts only the minutes that produced nothing chargeable, so long files transcribe normally; a run of silent or unreadable media still stops at the same limit, and nothing is charged for what is not transcribed.

### 1.0.42 — 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.41 — 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.
- **Speech recognition on media that yields nothing chargeable is bounded per run** — a small allowance that every charged minute widens, so a paid run is never cut short; past it, items ship as uncharged `vendor_budget` rows.
- **A chained Dataset ID is looked up read-only.** A mistyped ID creates nothing and returns one uncharged input-error row that names the ID as the problem; a real dataset is read page by page with an honest notice past 10,000 rows.
- **Row notes state what happened and what was charged; the support ask lives on the run page.**

### 1.0.40 — 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.39 — 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.37 — 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.36 — 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.35 — 2026-09-03

**An output format we do not recognise now stops the run instead of quietly changing what you get back.**

An unrecognised value in "Formats to return" used to be dropped, plain text returned in its place, and the loss mentioned only in the run summary — so a run that asked for subtitles could be charged in full for a transcript in a format you never asked for. That value now returns one uncharged guidance row naming it and the values the field accepts, and the run does nothing else: nothing is fetched, nothing is transcribed, nothing is charged. Fix the list and start again. Valid formats, an empty field and a null are all unchanged, and a Start with nothing set still transcribes the sample clip.

### 1.0.34 — 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.
- The free n8n workflow templates still exist; the listing now says where to find them (our Apify profile) instead of linking out.

### 1.0.33 — 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.32 — 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.31 — 2026-09-02

**See what you are buying before you run it: real console screenshots in the listing.**

- The listing now shows the input form exactly as it appears in the console, and the dataset table of a real run — the 25-second sample clip with its language, word count, transcript and source link. You can see the fields you fill in and the columns you get back before starting anything.
- Every link to another steadyfetch actor now carries that actor's current store name.

### 1.0.30 — 2026-09-02

**Start with nothing set and get a real transcript instead of a placeholder row.**

Clicking Start with no links used to return one demo row and no transcript. It now transcribes a fixed 25-second public-domain sample clip (the Gettysburg Address, read for LibriVox), charged like any run — one audio minute — so the first thing you see is real output: the text, the timestamps, the SRT and VTT. The status line and the row say it was the sample and how to run your own links. If the clip cannot be fetched at that moment, you still get one uncharged row explaining it, never an empty result.

### 1.0.29 — 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. Every optional field now accepts `null` and reads it as "use the default": the dataset ID, pasted dataset rows, the output formats, the language hint and both minute caps. `urls` is the one exception — it is required and needs a real value.

**A format we do not recognise is answered, not refused.** "Formats to return" still offers the same four choices in the console, but a name outside that list no longer stops the run before it starts — the run goes ahead on the formats it understood and the status line names the one it dropped. Nothing about what is delivered or charged changed.

### 1.0.27 — 2026-08-31

**A file that could not be reached is no longer handed back as "please re-run".** On some runs the download opened a network route the run cannot actually use, and every retry opened the same one — so the file came back as a retryable row that re-running could not fix, while the same link downloaded fine minutes later. Downloads made from the run's own address now take the route that works. Those rows were never charged, and nothing about what is charged changed.

### 1.0.26 — 2026-08-30

**A file 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 recordings — or one whose timing does not fit the measured audio, now ships as an uncharged no-speech row. Genuinely spoken recordings, in any language, are delivered and charged exactly as before.

### 1.0.25 — 2026-08-30

This page's pricing copy now states the price plainly: $0.003 per audio minute — the same price on every Apify plan. Nothing about what this actor delivers or charges changed.

### 1.0.24 — 2026-08-30

Very large files no longer risk running out of memory.

- **Memory use no longer grows with file size.** A downloaded file is now handled on disk from the first byte instead of being held in memory, so a long or very large recording — a multi-hour podcast, a big video — takes the same memory as a short clip. Runs with several big files in a batch are the ones that gain.
- **Nothing lingers between items.** Every item's working files are removed the moment its audio is ready, whether the item delivered or was refused.
- Nothing about what this actor delivers or charges changed: a transcript is charged per audio minute exactly as before, and every uncharged row stays uncharged.

### 1.0.23 — 2026-08-30

A blocked or stalled stream is now retried — and reported honestly — instead of being called a broken file.

- **A page whose stream is refused or cut off is retried before any row ships.** When a site's stream refuses to play — its parts answer 403 or 429, time out, or stop partway — the item is now tried again on the site's next-best rendition, and then once more over a fresh connection, before anything is reported. Most such items now come back transcribed.
- **When it still fails, the row says what happened and that a re-run is worth it.** Those rows used to read "the media could not be decoded" with `retryable: false` — a permanent-sounding verdict for what was usually a temporary block. They now name the host's answer (for example HTTP 403) and carry `retryable: true`. A stream that really is gone (404 or 410) still comes back as a permanent answer.
- **A stream that stops partway is never delivered as a shorter transcript.** A stream cut off mid-way could previously come back as a short, silently incomplete result. It is now retried and, failing that, reported as cut off — uncharged.
- **A host that goes silent is given up on after about half a minute of silence**, instead of holding the item until its full time-out.
- **A blocked or throttled direct file link gets one fresh retry before its row ships.** A host that served a block page, or answered 401/403/429 or a server error, is tried again over a fresh connection; the uncharged row only ships if that fails too.
- **A page lookup that was refused temporarily is looked up once more** over a fresh connection before the item is reported. Permanent answers — removed, account-only, no media on the page — are unchanged and never retried.
- Nothing about pricing changed: none of the rows above is ever charged; a transcript is charged per audio minute exactly as before.

### 1.0.22 — 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.21 — 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.20 — 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 now says so plainly on the run's status line — how many rows this run processed, and how to get the remainder.

### 1.0.19 — 2026-08-29

- Clearer wording on uncharged no-speech rows: an item whose audio turns out to be music-only or silent now says exactly that in its row note, in plain language. The same items are recognised as before, and they are still never charged.

### 1.0.18 — 2026-08-29

Chain it after any scraper, and hear about a setting the run could not use.

- **New: chain a scraper run straight into this actor.** Put a finished run's **Dataset ID** into the new field — with Apify's *Connect actor* integration it fills itself — and every row is deep-scanned for one audio or video link, whatever the scraper calls it (`videoUrl`, `audioUrl`, `mediaUrl`, `playAddr`, `downloadAddr`, a bare file link, nested or top-level). Prefer to paste? The new **Dataset items** field takes the rows directly.
- **One media link per row, and never a double charge.** A row's video and its own audio track are the same item, so they bill once; a link you also listed in *Media URLs* is transcribed once; thumbnails, covers and profile pictures are never mistaken for media. Rows carrying no media link are counted on the run's status line and cost nothing.
- **A dataset this actor cannot read is answered, not swallowed.** A wrong ID — or one whose read access was never granted — comes back as a single uncharged row telling you which ID failed and what to do, and the good links in the same run still run.
- **A setting the run could not use is now said out loud.** An output format that is not one of `text`, `segments`, `srt`, `vtt`, or a language hint that is not a two-letter ISO 639-1 code, used to be dropped in silence. The run's status line now names it and says what happened instead — and a run with nothing to report stays exactly as quiet as before.
- Nothing about pricing changed: chained items are charged per audio minute transcribed, exactly like a pasted link.

### 1.0.17 — 2026-08-29

A host that blocked us is no longer reported as a file that is gone.

- **A host that answers 401, 403 or 451 — a hotlink-protected, geo-gated or bot-walled CDN — is now reported as a temporary block you can re-run**, not as a file that is unavailable at that address. 404 and 410 still come back as a permanent answer.
- **When a host serves a web page or a short message where the audio file should be**, that is now reported as a block on the link you gave us rather than as "you pasted a page". A link that really is a page still gets the old, plain answer.
- **Page links on supported sites no longer come back as "this page exposes no audio or video"** when the site simply refused us or changed its markup. Those now say so and ask for a re-run; a page that genuinely has nothing to transcribe is unchanged.
- **A bot check worded "sign in to continue" is no longer mistaken for a paywall.** Pages that really do need an account, a subscription or an age confirmation still say exactly that.
- **Every row now carries a `retryable` column**: `true` means the host blocked or throttled us and running the same input again is worth it, `false` means the answer will not change. Nothing about pricing changed — none of these rows is ever charged.

### 1.0.16 — 2026-08-29

- **A typo in an input field name now gets a helpful pointer instead of the demo row.** If you send a field this actor does not have — `url` instead of `urls`, say — the run now returns one uncharged row that names the field it did not recognise, names the one you meant, and shows the shape to send. Before, that run looked exactly like a run with no input at all and answered with the demo row, which made a typo look like it had worked. Nothing is fetched and nothing is charged either way.

### 1.0.15 — 2026-08-29

A speech-recognition outage on our side now stops the run cleanly instead of failing every item one by one.

- **If the transcription service refuses this actor's access mid-run, the run stops paying for work it cannot deliver.** The item that hit the refusal comes back as an uncharged `asr_unavailable` row saying plainly the problem is ours, and every remaining item is answered instantly with the same uncharged status — no more downloading and decoding each file first only to fail at the last step. The run status names the outage and says to try again later. Ordinary rate-limit blips keep their usual retries and are unaffected.

### 1.0.14 — 2026-08-27

A blank line in your list now gets a straight answer instead of being called a duplicate.

- **An empty or whitespace-only entry in `urls` now returns the uncharged `input_error` row
  this listing promises**, with a note saying what to pass. It used to return no row at all,
  and the run status reported it as an item "already in this run's results" — a duplicate of
  something that was never there.
- **The already-delivered count in the run status now counts only genuine duplicates.**
  Blank entries, and any other entry that is not a usable link, are answered once each and
  never inflate it.
- The run status now says "1 entry was not a usable link" where it used to mismatch its own
  count.

### 1.0.12 — 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 and counted those charges in the
  run summary, even though nothing reached your invoice. Such a run now delivers the transcripts
  uncharged, marks every row `charged: false`, and says so in the run log. 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
  "max total minutes" and cost caps you set were not applied to it. The 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 — misses, refusals, the sample — the run now fetches nothing,
  delivers nothing and charges nothing, and tells you plainly that the fault is ours. A pricing
  mistake on our side can no longer put a charge on an uncharged row.

### 1.0.11 — 2026-08-27

Interrupted runs now pick up exactly where they left off, and long or awkward files cost less time.

- Fixed: a run that was interrupted and restarted — moved to another server by the platform, or
  resurrected by you from the Runs tab — could deliver the same item a second time and charge for it
  again. A restarted run now recognises everything it had already delivered, skips it, and never
  charges twice for it. Anything that was delivered but not yet billed when the interruption hit is
  settled once, so no row is charged twice and no delivered row goes unbilled.
- Fixed: on a restarted run, "Max total minutes" started counting again from zero, so a run could
  transcribe up to twice the limit you set. The limit now covers the whole run.
- Fixed: the actor's own run-length ceiling also restarted on an interruption. It now runs from when
  the run started.
- Items that were never started when a run is interrupted carry no key, so a resumed run picks them
  up normally — you never lose the tail of a list to a restart.
- Faster on hosts that ignore range requests (large archives and some podcast CDNs): those files were
  being downloaded twice, once to identify them and once to transcribe them. They are now fetched
  once.
- Faster on long recordings: the decoded audio is written once and each window is cut from it, rather
  than rewriting the whole decoded file for every window. A multi-hour recording spends noticeably
  less time before its first transcript.
- How many items run at once is now sized from the run's memory, so a batch of very large files can
  no longer exhaust it mid-run.

### 1.0.6 — 2026-08-27

Review-fix build: charge settlement, live-stream refusal, transcript stitching and subtitle timestamps.

- Fixed: on a run with a total-minute or charge cap, an item trimmed to fit what was left could
  silently shrink the budget for everything after it. Later items were transcribed and delivered but
  came back marked as uncharged while real chargeable room remained. The remaining budget is now
  exact, so every item that gets a transcript gets billed for it.
- Live radio streams are now refused the moment the server's reply headers arrive, rather than after
  waiting for a body that never ends. The modern kind of stream — which answers like a normal file
  and simply never stops — used to burn the full 30-second timeout and come back as "please re-run".
  It now returns the same uncharged live-stream row as the classic kind, immediately.
- A slow host that does state its file size keeps the honest "temporary, try again" row, and neither
  case changes how the rest of the run connects: the server answered, so nothing is wrong with the
  connection.
- Fixed: in a very long recording, a final window that the previous window had already covered
  entirely could repeat its last few words in the plain-text transcript. The timestamped segments
  were always correct; the plain text now matches them.
- Fixed: a subtitle time landing a hair under a whole second (59.9996s) could render the malformed
  stamp `00:00:59,1000`. It now rolls over correctly to `00:01:00,000`, including across minutes
  and hours.

### 1.0.5 — 2026-08-27

- Live radio streams are now caught in both of the forms servers use: the newer kind that answers
  normally, and the classic kind whose reply an HTTP client cannot parse at all. Either way you get
  one uncharged row saying it is a live stream, instead of a message asking you to try again.

### 1.0.4 — 2026-08-27

- Fixed: a run whose charge cap covered exactly one audio minute returned no transcript at all —
  every item came back as a "budget stop" row. A one-minute cap now delivers that minute.
- Fixed: on a run with no pricing attached, rows said they had been charged when nothing had been.
  Rows now report `charged: false` and `chargedMinutes: 0` unless a charge was really made.
- Live radio streams (ICY / SHOUTcast / Icecast URLs) are now refused on the first few kilobytes
  with a row that says so, instead of downloading up to the size limit and reporting "too large".
- A web address whose domain does not exist is now reported as a permanent problem with the
  hostname named, instead of asking you to re-run something that can never succeed.
- Very long recordings no longer spend an extra transcription pass on a final sliver of audio the
  previous window had already covered.

### 1.0.3 — 2026-08-27

- Transcribe any direct audio or video file link, from any host — signed CDN links and links with no file extension included.
- Page links supported on 11 tested sites: Libsyn, Megaphone, Buzzsprout, Acast, Spotify for Podcasters (Anchor), S3/CloudFront, Archive.org, SoundCloud, Loom, Twitch VODs and Wistia.
- Output formats: plain text, timestamped segments, SRT and VTT subtitle files.
- Automatic language detection across about 90 languages.
- No length limit: long recordings are transcribed in overlapping windows and stitched back together with continuous timestamps.
- Charged per audio minute actually transcribed. Unreachable links, music-only audio, oversized files, live streams and unsupported pages carry no result fee, and every one of them comes back as a row explaining why.
- Two run limits, both optional: max minutes per item and max total minutes.
