- 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.
A new retryable column, and two "this video is gone" answers that were not true.
- 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.
- A video is no longer reported as removed because YouTube's service did not answer. When the video service itself returned "not found" — which happens when YouTube moves or gates that endpoint, not because of anything about your video — the run marked the video permanently gone and moved on. YouTube reports a genuinely removed video a different way, and that answer is unchanged. The endpoint one is now a temporary problem you can re-run, still with nothing charged.
- A caption track that exists but is empty now goes to speech-to-text. Videos whose captions came back as a valid but empty track were being answered "please re-run" forever, when the honest next step was to transcribe the audio. They deliver again. A caption fetch that was actually blocked is still a re-run, exactly as in 1.0.13.
- A resumed run cannot bill you twice for the same transcript. When a run is interrupted and picks up where it left off, it reconciles what was already charged. If it could not confirm those numbers, it now charges nothing and leaves the settlement for later, instead of guessing from a count that may be stale — and if the run's own delivery record comes back missing rows it has already charged for, the run stops cleanly rather than re-delivering and re-charging them.
- Store links to the rest of the steadyfetch shelf now point at the actors that are live.
Captions YouTube refused to hand over are no longer reported as captions that do not exist.
- A blocked caption fetch is now honest, and free. When YouTube listed a video's captions but then refused to serve them, the run reported "this video has no captions". It now recognises the refusal, retries on a fresh connection, and if the captions still cannot be served returns an uncharged row asking you to re-run.
- That same refusal can no longer bill you for speech-to-text. With speech-to-text on — the default — a blocked caption fetch used to fall through to transcribing the audio, and the speech minutes were charged. A blocked caption fetch never becomes a paid speech-to-text transcript now.
- Unchanged: a video that genuinely has no caption track still goes to speech-to-text exactly as before, or returns "no captions" when you have speech-to-text switched off.
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 videos under a name
this actor does not have —
videoIds instead of videoUrls, say — or filling in only the options
and no videos, 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 videos are unchanged, and a
run with nothing set at all still returns the sample row.
- 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, 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 your list.
- 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.
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 free 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. 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, the free 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 a free row.
Housekeeping only — no change to how anything runs.
- Removed an internal working note that had been swept into the published source of the
previous build. It affected nothing at runtime; it simply did not belong in the source you
can browse.
- Corrected one line of the 1.0.7 notes below: it described a download change that was
measured, rejected and not shipped. What did ship is the line above it.
A long video with no captions now gets an honest, permanent answer instead of being told to
try again.
- 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. Videos that have
captions are unaffected at any length, 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.
Interrupted runs now resume instead of repeating themselves, and the two per-run limits
became whole-run limits.
- Fixed: when Apify moves a running job to another server — which it does routinely on
long runs — the run used to start its work over. Videos you had already been given came
back a second time as duplicate rows, and were charged a second time. A run now reads
back what it has already delivered and skips exactly those videos: no duplicate row, no
second charge, and the finished dataset looks the same as if it had never been
interrupted. The same protection covers resurrecting a finished run.
- Fixed: if the move landed in the moment between a row being delivered and its charge
being recorded, that row was delivered free and its
charged: true was not true. The
run now settles that one charge on restart, so every row's charged field matches the
invoice.
- Fixed:
Max videos and Max speech-to-text minutes were counted per server, not per
run — so a job that got moved could deliver up to twice what you asked for. Both are
now whole-run limits, counted across the move.
- Fixed: a moved run was also given a fresh copy of its own internal time ceiling, so it
could run far longer than it was meant to. It now stops when the original run was due
to stop.
- Corrected: the input form said to use the channel actor for "a whole channel or
playlist". It does not do playlists. It now says to paste the video links here for a
playlist, matching what the README and the guidance rows already said.
- The YouTube channel actor, Trends Now, Social Trends and Speech to Text are now
published, so they are real links in the actor list at the bottom of the README.
Review-fix build: speech-minute accounting, long-video speech detection, the oversized-audio
limit, subtitle timestamps and the run deadline.
- 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.
Speech density is now judged over at most ten minutes, so sparse-but-real speech is 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.
- 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.
- Corrected: a playlist link used to suggest the channel actor could transcribe the whole playlist.
It cannot — it transcribes whole channels. The guidance row now says to paste the video links here.
- README: output shown first, pricing stated up front, and a plain list of what can fail and why
none of it is charged.
Paste YouTube video links or IDs and get each video's transcript.
- 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.
- Accepts watch links,
youtu.be links, Shorts, /live, /embed, mobile and music
hosts, and bare 11-character video IDs, in any casing.
- 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 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 speech-to-text minutes and Max videos are hard limits: the run stops cleanly and
every skipped row names the limit that stopped it.
- Every row carries
charged, statusReason and chargeEvents, so the invoice reconciles
from the dataset itself.
- A run with no videos returns one uncharged sample row showing the exact output shape.