Audio Transcription - Speech to Text, Audio to Text, Whisper
Pricing
from $4.80 / 1,000 audio minutes
Audio Transcription - Speech to Text, Audio to Text, Whisper
Speech to text for audio, video, YouTube and podcast RSS, using Whisper. Get text with timestamps plus SRT, VTT, TXT and Markdown files. $0.006 per started audio minute; optional speaker labels $0.015/min. Failed and silent files are free. Up to 1,000 links per run.
Pricing
from $4.80 / 1,000 audio minutes
Rating
0.0
(0)
Developer
Yukai Lin
Maintained by CommunityActor stats
0
Bookmarked
221
Total users
164
Monthly active users
5 hours ago
Last modified
Categories
Security
Limited permissionsShare
Audio Transcription: Speech to Text, Video to Text, Podcast Transcription
What it does
This Actor turns audio files, video links and podcast RSS episodes into text. You get the text with timestamps, plus downloadable SRT, VTT, TXT and Markdown files. It works for MP3 to text, video to text, YouTube video transcription and podcast transcription. It can also generate SRT/VTT subtitles.
Example input
{"urls":["https://webcapture-api.yukailin.workers.dev/samples/speech-sample.wav"]}
What you get back: one row for the 8.5-second sample, with:
- non-empty
text durationSecondsof about8.5charged: truesrtUrl,vttUrl,txtUrlandmdUrldownload links
The run's SUMMARY.status is SUCCESS.
Price: $0.006 per started audio minute on the Free and Bronze plans. The sample above is one billed minute, so it costs $0.006. Files that fail, are blocked or have no speech are not charged.
Hundreds of file links in one run: put up to 1,000 public media links in urls, for example {"urls":["https://example.com/audio/001.mp3","https://example.com/audio/002.mp3","https://example.com/audio/003.mp3"],"maxTotalMinutes":1000} (placeholder links; maxTotalMinutes is an optional cap on the audio minutes of this run). Each file gets its own row with charged: true/false, and failed files are free. Failed rows with retryable: true go into the run's RETRY_INPUT record: your input with only those links, ready to use as the input of a new run, so finished files are not transcribed or charged again. Rows with retryable: false stay out of it; their error says what to change. If a limit stops the run, the REMAINING_INPUT record holds the files left.
From the API or a schedule: this request starts a batch and returns the run's IDs at once.
POST https://api.apify.com/v2/acts/tidytools~audio-transcriber/runsAuthorization: Bearer <your Apify API token>Content-Type: application/json{ "urls": ["https://example.com/audio/001.mp3", "https://example.com/audio/002.mp3"], "resumeStore": "my-batches" }
Check GET https://api.apify.com/v2/actor-runs/<run id>?waitForFinish=60 until status is SUCCEEDED, then read GET https://api.apify.com/v2/datasets/<defaultDatasetId>/items. To retry, read GET https://api.apify.com/v2/key-value-stores/<defaultKeyValueStoreId>/records/RETRY_INPUT (the record is missing when no link is worth retrying) and send it as the body of the same POST. With resumeStore, a file sent again with the same settings returns its saved transcript with reused: true and is not charged again. Send each batch from your own script, n8n or Make, or give an Apify Schedule this input with "newEpisodesOnly":true and a monitorName, so later runs skip links already transcribed under that name. More in n8n, Make and Zapier and retries.
Check the cost first: set "estimateOnly":true for a free, metadata-only preview of podcast RSS episodes, YouTube playlist/channel videos or files in a public Google Drive folder. Each row has source, title, durationSecs, estimatedMinutes and estimatedUsd; SUMMARY totals known minutes and flags exceedsMaxTotalMinutes. Unknown durations stay null with estimate:"unknown". This mode does not download media, transcribe or emit charge events. The estimate covers audio-minute events at the current pricing tier; successful transcription and optional extras can change actual cost.
Start with the link you have:
- Direct audio or video file URL (such as
.mp3or.mp4): transcribe a file link. - Google Drive / Dropbox / OneDrive share link that opens and downloads without signing in: transcribe a shared file.
- YouTube video link: transcribe a YouTube video.
YouTube without captions: Example input: {"urls":["https://www.youtube.com/watch?v=CKRxRE7qRzc"]}. For a public video with accessible audio and speech, the Actor tries to transcribe the audio, not YouTube captions. A successful row includes text, timed segments and downloadable transcript and subtitle files (with the default save settings). Audio costs $0.006 per started minute, rounded up, only on success; an unreadable video or one with no transcribable speech gets a free failed/no-text row. Live streams still in progress and private or restricted videos are not guaranteed to work.
For medium or long recordings or several links, process multiple files in one run. Each file gets its own result row; successful transcripts provide txtUrl and mdUrl download links when transcript files are saved, plus srtUrl and vttUrl when subtitles are saved in those formats (both by default).
Optional podcast content pack
Set "contentPack":true to draft contentPack.chapters, contentPack.showNotes, contentPack.socialPosts.x, and contentPack.socialPosts.linkedin for each successful transcript. Add "contentPackSubtitleLanguages":["Spanish"] for one translated SRT/VTT language; its download links are in contentPack.translatedSubtitles. These are drafts to review against the transcript before publishing. The option is off by default; with it off, output and billing remain the same. A failed pack sets contentPackError and has no pack event. On paid Apify plans the pack is written by Gemini (with fallbacks); on the free plan a smaller Workers AI model writes it, so chapters and posts are simpler and a pack with mis-timed chapters is dropped (not charged).
The pack also has contentPack.clipCandidates: up to 3 suggested 30–60 second passages worth cutting as short clips, each with startSeconds, endSeconds, start, end (e.g. "4:05") and a short title. They are timestamp suggestions only, not cut audio or video files and not video links; check them in your editor. A candidate is kept only when it starts on a timestamp from the transcript and fits inside the recording. Untimed transcripts (for example a plain-text publisher transcript) and recordings under 5 minutes always get [], and so does a pack whose suggestions are all invalid; the other pack fields are delivered as usual. Clip candidates are part of the same content-pack event below, with no extra charge. When the pack is on, SUMMARY.clipCandidatesReturned counts them.
The pack can also include contentPack.newsletterDraft with a subject (up to 120 characters) and a plain-text body (up to 2,000 characters) for an episode-update email: a short intro, key points from the transcript and a closing line asking readers to listen or read. This is a draft for you to review, not a sent email. The Actor sends no email and has no mailing list. Its only allowed link is the episode page from your RSS feed (episodeUrl). Without that page it has no link. A draft that is empty, too long or contains any other link, web address or email address (also without https://, such as sponsor.example/deal) is left out, and the other pack fields are delivered as usual. The draft comes from the same AI request and is part of the same content-pack event, with no extra charge. When the pack is on, SUMMARY.newsletterDraftsReturned counts the drafts that were delivered.
Charge starts 2026-11-06 or later, after the 14-day notice: one content-pack event at $0.10 per successful episode, in addition to existing audio-minute charges. Until the operator sets CONTENT_PACK_CHARGE_FROM to the announced date and that date arrives, the text pack is free; the configured date cannot be earlier than 2026-11-06. Optional translated subtitles use the existing subtitle-language-minute event at $0.002 per started minute, charged only on success, including before the pack charge date. summarize also remains separate. For long transcripts, AI prompts sample the whole recording, including the final paragraph; the complete transcript stays in text and TXT/Markdown files.
Subtitle files are saved to the run's key-value store and linked in each timed transcript row as srtUrl and vttUrl. To save only one format, set "outputFormats":["srt"] or "outputFormats":["vtt"]; omitting outputFormats keeps both. "outputFormats":[] or "saveSubtitles":false saves neither original-language subtitle file. The text, segments and audio-minute charge stay the same. speakerLabels is optional and off by default; it uses the existing Deepgram nova-3 path and $0.015/min speaker-label event for supported file inputs. YouTube links and some languages do not support it.
Try one short recording
The input form starts with one public, 8.5-second spoken WAV file; Podcast RSS feeds is empty. Click Start to get one transcript row, then replace the sample URL with your own file. The sample needs no login.
Input: {"urls":["https://webcapture-api.yukailin.workers.dev/samples/speech-sample.wav"]}
Output: one successful row with nonempty text, durationSeconds of about 8.5, charged: true, and srtUrl, vttUrl, txtUrl and mdUrl download links. The run's SUMMARY.status is SUCCESS.
Price: $0.006 per started audio minute (this sample is one billed minute, $0.006); failed, blocked and speechless files are not charged. Speaker labels are $0.015/min instead, and optional AI summary is $0.01/file.
WhatsApp / Telegram voice notes: pass direct .ogg / .opus download URLs from n8n, uazapi or Evolution API in urls. The link must still work without a login when this Actor starts downloading it. One voice note of up to a minute is $0.006, so the Apify Free plan's $5 monthly credit covers about 830 of them; when a flow needs more, a paid Apify plan keeps it running (once the credit is used up, Apify does not start new runs until the monthly reset), and a row that hit your limit says so in its error.
n8n HTTP Request JSON body (replace voiceNoteUrl with your previous node's direct media URL field): {"urls":["{{$json.voiceNoteUrl}}"]}
How to transcribe an audio or video file link
- Paste the file links, one per line, into Audio or video file URLs (a direct
.mp3/.mp4link, or a Google Drive / Dropbox / OneDrive share link set to "anyone with the link"). Check that the file downloads in a signed-out or private browser window and that any temporary link is still valid. If your company does not allow public sharing, use Or upload audio / video files (audioFiles) instead. - Optional: set the Language and turn on Speaker labels ($0.015 per minute instead of $0.006) or AI summary and chapters ($0.01 per file).
- Click Start. Each file becomes one row with the text, timestamps and
srtUrl/vttUrl/txtUrl/mdUrllinks to its subtitle and transcript files. - Download the files from those links, or call the Actor from the API. A 60-minute recording costs $0.36; a file that fails or has no speech costs nothing.
Stereo phone recordings
For a direct audio or video file with exactly two audio channels, set audioChannelMode to left or right when each caller is recorded on a separate side. Only the chosen side is transcribed; the other side is discarded. Run each side separately if you need both, with one transcript and one audio-minute charge based on the original file duration per run. This does not identify speakers or change the speakerLabels price.
The default auto keeps the normal left/right mix unless a short sample shows one side is nearly silent or both sides nearly cancel when mixed. mix always combines both sides. Ordinary two-speaker stereo, mono files and files with more than two channels keep the existing mix in auto; left or right on files without exactly two channels returns a free unsupported row. The setting applies to file audio sent to ASR; YouTube service audio and publisher transcripts are handled by their existing paths.
Multiple files in one run
- For three recordings, paste three public links, one per line, into Audio or video file URLs (
urls), or upload three files under Or upload audio / video files (audioFiles). Exampleurlsvalues:https://example.com/recording-1.mp3,https://example.com/recording-2.mp3,https://example.com/recording-3.mp3(placeholders; replace them with your own links). Links must be downloadable without signing in. - Remove the prefilled speech sample from
urlsif you do not want it in this run; it can be transcribed and charged. Podcast RSS feeds starts empty. Before Start, set Apify's Maximum cost per run in Run options or Max total minutes per run (maxTotalMinutes) in Advanced settings. - Each file gets one result row; failed or silent files are free. After the batch, files with a temporary
service_busy,timeoutornetworkfailure wait 45 seconds and get one more try, one at a time, when at least two minutes remain before the Actor timeout.SUMMARY.secondPassreportstriedandrecovered. Only the final result produces a row; recovered transcripts use the existing audio-minute event once and still respect the run's maximum cost. Quota, size, no-speech, missing-file and limit failures are not retried in this pass. A YouTube daily limit or paused access may still need a later run: the free row says when the daily speech-to-text capacity resets (00:00 UTC). With at least two saved transcripts,SUMMARY.combinedTranscriptlinks to the combined TXT and Markdown files. For a failed row withretryable: true, start a new run with that run's key-value storeRETRY_INPUTrecord; after a spending or minute limit, useREMAINING_INPUTfor files still to process. Do not rerun the full original input: completed files can be transcribed and charged again.
First run: try the prefilled example
The input form already holds only the short speech sample in Audio or video file URLs. Click Start without changing anything for one result; the 8.5-second recording is billed as one started minute ($0.006). Each result row shows the text and charged: true or false. Then replace the example with your own links (a Google Drive / Dropbox / OneDrive share link must be set to "anyone with the link").
Choose a file or video with spoken words. If the only result is no_text, the run can still say SUCCESS: no speech was detected, no transcript was produced, and the row is free. Check that you selected the track with spoken words, or try another file or link with speech; SUMMARY.nextStep and the run status repeat this hint.
How to transcribe a podcast to SRT subtitles
- Paste the podcast's RSS feed (or an Apple Podcasts link) into Podcast RSS feeds, or episode file links into Audio or video file URLs. Optional: Episodes per podcast feed and date or title filters pick the episodes.
Captivate episode pages and their direct or tracking-prefixed MP3 links can be used when the public player supplies a working file. If Captivate briefly answers 404, the Actor tries another download route. If its episode redirect has no target on every route, the failed row is free and asks you to retry later or use the current MP3 enclosure URL from the show's RSS feed. A genuinely missing file stays a free
not_foundrow. - Optional: set the Language and turn on Speaker labels ($0.015 per minute instead of $0.006).
- Click Start. Each episode becomes one row with the text, timestamps and
srtUrl/vttUrllinks to its subtitle files. - Download the SRT files from those links, or call the Actor from the API for a whole feed. A 60-minute episode costs $0.36.
How to transcribe a YouTube video to text
- Paste YouTube video links (watch, youtu.be or shorts links), one per line, into Audio or video file URLs. Videos up to 4 hours work without changing any setting.
- Click Start. The spoken audio is transcribed (not the captions), so videos without captions work too; each video becomes one row with the text, timestamps, the video title and channel, and SRT / VTT / TXT / Markdown files.
- Same price as files: $0.006 per audio minute (a 30-minute video costs $0.18). Private, removed or silent videos are free rows.
Video to text
Paste a public video file link (MP4, WEBM, MOV or MKV, or a Google Drive / Dropbox / OneDrive share link) into Audio or video file URLs. The spoken words become text with timestamps, plus SRT and VTT subtitles, TXT and Markdown files. Example input, a 59-second public-domain news interview on Wikimedia Commons:
{ "urls": ["https://upload.wikimedia.org/wikipedia/commons/6/68/Straight_Talk_Africa_-_Exclusive_Interview_With_South_Sudan_Vice_President_James_Wani_Igga.webm"] }
Result: one row with text, segments, srtUrl, vttUrl, txtUrl and mdUrl; 1 started minute, $0.006. A video with no speech is a free no_text row. For YouTube links, see How to transcribe a YouTube video to text.
MP3 to text
Paste a public MP3 link (WAV, M4A, OGG, OPUS and FLAC work the same way) into Audio or video file URLs, or upload the file under Or upload audio / video files. Example input, a 115-second public-domain spoken MP3 on Wikimedia Commons:
{ "urls": ["https://upload.wikimedia.org/wikipedia/commons/5/5b/Wikipedia_-_Lawmaking_%28spoken_by_AI_voice%29.mp3"] }
Result: one row with the transcript and timestamps, plus subtitle and transcript files; 2 started minutes, $0.012. A link that needs a login or no longer exists gives a free failed row whose error says what to change.
Why this one
- $6 per 1,000 audio minutes (Gold and above $4.80), with no start fee and no per-file fee. The two most used file transcribers on Apify Store charge $48 (memo23) and $300 on the Free plan ($9 on Gold, sian.agency) per 1,000 minutes (comparison, checked 2 October 2026).
- Failed, silent and skipped files are free. Every row says
charged: true/false, and the run log shows the cost plan before anything is charged. - Apify Free plan ($5 monthly credit) = about 833 audio minutes, e.g. about 13 one-hour podcast episodes. A run that reaches your limit stops cleanly: files left are free and listed, ready to run again (details).
- Subtitles and transcript files included: SRT, VTT, TXT and Markdown for every file, plus paragraphs and timestamps, at no extra charge.
- No 25 MB limit, no API key, no proxy: files up to 2 GB and any length, Google Drive / Dropbox / OneDrive share links, podcast RSS feeds, YouTube video links and Loom, Wistia, Streamable and Reddit (v.redd.it) video links in the same run.
- Find every mention (
searchTerms): brands, names or topics with the time, the sentence, the speaker and a link that opens the recording at that moment, plus one CSV of all mentions, at no extra charge. - Paste the input you already use: field names of other transcription Actors (
audioUrls,mediaUrls,datasetItems,speakerDiarization, ...) are mapped automatically.
Input (copy and paste)
{"urls": ["https://archive.org/download/gerald-ford-inaugural-address-august-9-1974-720p/Gerald%20Ford%20inaugural%20address_%20August%209%2C%201974%20%28720p%29.mp4"],"summarize": true}
Output (real result, shortened)
The 8-minute video above, transcribed in 33 seconds and billed 9 minutes ($0.054) plus the AI summary ($0.01):
{"success": true,"durationSeconds": 500.1,"billedMinutes": 9,"language": "en","text": "Mr. Chief Justice, my dear friends, my fellow Americans, the oath that I have taken is the same oath that was taken by George Washington...","segments": [{ "start": 1.36, "end": 20.4, "text": "Mr. Chief Justice, my dear friends, my fellow Americans, the oath that I have taken ..." }],"chapters": [{ "start": "0:00", "title": "Introduction and Compact with the Nation" }, { "start": "5:04", "title": "Message of Hope and Unity" }],"srtUrl": "https://api.apify.com/v2/key-value-stores/.../records/0000-Gerald-20Ford-...mp4.srt","txtUrl": "https://api.apify.com/v2/key-value-stores/.../records/0000-Gerald-20Ford-...mp4.txt"}
All fields, speaker labels and word timestamps: Output example.
How much does it cost?
| Event | Price |
|---|---|
| Audio minute | $0.006 per minute ($0.36 per hour) |
| Audio minute with speaker labels (optional) | $0.015 per minute ($0.90 per hour), instead of the regular audio minute, only when speakerLabels is on |
| Translated subtitles (optional) | $0.002 per minute per language (e.g. a 30-minute episode in Spanish and German = $0.12), only for languages that succeed |
| AI summary and chapters (optional) | $0.01 per file, only when summarize is on |
- No start fee. You pay only for transcribed audio.
- SRT and VTT subtitles, paragraphs and timestamps are included.
- Billed per started minute of audio (a 500-second video is 9 minutes).
- Word-level timestamps are included in the regular $0.006 per minute (Whisper word timings). With speaker labels on, words come from nova-3 and the speaker-label price applies, not both.
- Translated subtitles (
subtitleLanguages, up to 5 languages): SRT + VTT per language with the original timings and speaker labels, made right after the transcript. A language that fails is free and reported intranslatedSubtitles. - YouTube video links are billed like files: the transcribed audio minutes only, no per-video fee. Videos that cannot be transcribed (YouTube access paused, private or removed video, over your limits) are free.
- Not charged: files that cannot be downloaded or decoded, files with no speech, files skipped by your limits (
maxMinutesPerFile,maxTotalMinutes), files skipped bynewEpisodesOnly, episodes removed by the feed filters, and the audio minutes of episodes that use the publisher's own transcript (withsummarizeon, their AI summary is still $0.01). - Higher Apify plans get volume discounts (Silver 10%, Gold and above 20%).
How far does the free plan go?
Apify's Free plan includes $5 of credit every month. If you spend it only on this Actor, at $0.006 per audio minute that is about 833 audio minutes:
| What you transcribe | Cost each | With $5 |
|---|---|---|
| 60-minute podcast episode | about $0.36 | about 13 episodes |
| 30-minute video | about $0.18 | about 27 videos |
| 60-minute recording with speaker labels ($0.015 per minute) | about $0.90 | about 5 recordings (333 minutes) |
Minutes are billed per started minute of the full source recording in each file, including trailing silence; 180 seconds is 3 billed minutes and 61 seconds is 2. Options cost extra: speaker labels replace the $0.006 minute with $0.015, the AI summary adds $0.01 per file, and translated subtitles add $0.002 per minute and language.
What happens at the limit. A file is only started when the run's Maximum cost per run still covers all its minutes (and Max total minutes per run, if you set it). A file that does not fit gets a free row with errorType: "limit_reached", and the shorter files after it can still run. New files stop once the limit cannot pay for another minute, or after 5 files were skipped for the maximum cost. The run ends normally with SUMMARY.status LIMIT_REACHED. YouTube videos are checked the same way once their length is known. The row's error itself says what the file would cost, what is left of the limit and what to change (raise Maximum cost per run, or Max total minutes per run; on the Free plan also that the monthly credit may be used up) and to run again with only the files left (the REMAINING_INPUT record), for automations that read only the dataset. Files in progress at that moment are finished; minutes are charged only after a transcript row is saved.
What to do next. The status message and SUMMARY.limitReached.message say how many files were processed, how many were left and that those were not charged. The links left are in SUMMARY.remainingInputs, and the REMAINING_INPUT record in the run's key-value store is your input with only those links. Then either:
- raise Maximum cost per run (Run options) or Max total minutes per run and start a run with the
REMAINING_INPUTrecord as input, or - if your monthly credit is used up, upgrade your Apify plan or wait for the monthly reset, then run the
REMAINING_INPUTrecord (or paste the links fromSUMMARY.remainingInputs).
Files that were already finished are not in that input, so they are not transcribed or charged again.
After a successful Free-plan run with a transcribed file, the status message and SUMMARY.nextStep say you can keep using this Actor for batches. If your Apify monthly credit is actually used up, upgrade your plan or wait for the monthly reset. The Actor does not read your account's remaining credit.
Features
It turns audio files, videos and podcast episodes into text with timestamps, readable paragraphs, and ready-to-use SRT and VTT subtitle files. It uses OpenAI's open Whisper large-v3-turbo model, detects the language automatically and supports 90+ languages.
- 🎙️ Podcasts, interviews, meetings, lectures, webinars, audiobooks, voice notes
- 🎬 Video to text: MP4, MOV, MKV, WEBM and more. The audio track is extracted automatically
- 📡 Podcast RSS feeds: paste a feed (or an Apple Podcasts show link) and the newest episodes are transcribed, with episode title and publish date
- 📝 Publisher transcripts, no audio minutes: when a podcast publishes its own transcript in the feed (Podcasting 2.0
<podcast:transcript>: VTT, SRT, JSON, HTML or text), it is used instead of transcribing, with the same output andtranscriptSource: "publisher" - 🗓️ Feed filters: only episodes published since / until a date, or whose title contains (or does not contain) a text
- 📤 File upload: upload recordings from your computer in Apify Console (
audioFiles) - ▶️ YouTube video links: the video's audio is transcribed at the same $0.006 per minute, no per-video fee (details)
- 🎥 Loom and Wistia links: paste a Loom share link (sales demos, async meetings, onboarding videos) or a Wistia media link; the file that the host's public player uses is transcribed at the same per-minute price (details)
- 🎞️ Streamable and Reddit videos: a Streamable link (streamable.com/...) or a v.redd.it video link is transcribed from its audio track at the same per-minute price (details)
- 🔎 Keyword search (
searchTerms): every mention of a brand, name or topic with its timestamp, sentence, speaker and a link to that moment, counts per term, andKEYWORD_MATCHES.csvfor the whole run, e.g. to monitor podcasts for your brand on a schedule (no extra charge, details) - 🔁 Retry only what may work later: every failed row says
retryable: true/false, and the run saves a ready input with only those links (RETRY_INPUT) for n8n, Make, Zapier or a webhook (details) - 🌐 Web pages with one player: a page with exactly one audio or video file (e.g. a news, lecture or archive.org page); the file is found and transcribed
- 🔗 Google Drive, Dropbox and OneDrive share links work if the file is shared publicly
- ⏱️ Timestamped segments, paragraphs and full plain text in one result
- 🗣️ Speaker labels (
"speakerLabels": true): who said what, as Speaker 1, Speaker 2… on every segment, paragraph and subtitle line, plus the number of speakers - 🏷️ Your names for the speakers (
"speakerNames": {"1": "Host", "2": "Guest"}, with speaker labels): addsnamedSpeakerTranscript(the speaker transcript with your names and the same times) andnamedSpeakers; everything else keeps Speaker N. Names are only what you give, never guessed (no extra charge, details) - 🔠 Word-level timestamps (
"wordTimestamps": true): start and end time of every word, for video editing and precise subtitles (included in the regular price) - 🌍 Translated subtitles in one step (
"subtitleLanguages": ["Spanish", "German"]): SRT and VTT in up to 5 extra languages, timings and speaker labels kept - 🔁 Only new episodes (
"newEpisodesOnly": true): scheduled runs skip episodes and files that were already transcribed, and only take episodes released after the newest one already done, so a day without a new episode costs nothing (backfillOlderEpisodesworks through the back catalogue instead) - 🧾 SRT and VTT subtitles saved for timed transcripts (no extra charge). Each result with saved subtitles includes
subtitleTimingSource:segmentwhen a cue keeps its segment boundaries,wordwhen a longer segment uses valid word times,estimatedwhen a longer segment is split with interpolated times or a cue's end is adjusted to avoid overlap,publisherwhen a publisher cue keeps its original boundaries, ormixedwhen cues use different sources. This describes the actual subtitle cues, which can use different timing data from the publicsegments. Untimed publisher transcripts and results without saved subtitles omit the field. - ⏱️ Subtitle timing check (
"subtitleQc": true, off by default, no extra charge): addssubtitleQcto each row, a mechanical check of the saved SRT/VTT cues.countsgives the number ofoverlap(a cue still on screen when the next starts),too_short(under 1 s),too_fast(over 20 characters per second; 11 for Chinese, Japanese, Korean and Thai),line_too_long(over 42 characters; 16 for those scripts) andestimated_timing(times spread by text length, not heard) warnings;warningslists up to 100 of them as{ cue, start, end, rule, value, limit }andwarningsTotalcounts all. It does not proofread the wording. WithsaveSubtitles: false(or no SRT/VTT format) or a transcript without timed cues, it is{ "status": "skipped", "reason": … }; if the check itself fails, the row hassubtitleQcErrorand the transcript is unaffected. - 🎧 Cleaned MP3 (
"cleanAudio": true, off by default, no extra charge): for a file, cloud-storage link or podcast episode this run transcribed, also saves a basic cleaned copy of the original audio (light noise reduction and loudness levelling to podcast level, 96 kbps MP3, files up to 2 hours) linked ascleanedAudioUrl(size incleanedAudioBytes). This is basic processing, not professional mixing, and it does not make the transcript more accurate: the transcript is always made from the original audio. YouTube links, social and video platform pages (TikTok, Instagram, Loom, …) and publisher transcripts are not processed, and nor are failed or silent files. When the copy cannot be made (too long, too little disk, ffmpeg or storage error), the row keeps its paid transcript and says why incleanAudioError. The price per audio minute is the same. - ♻️ With
resumeStore, a reused transcript (free) follows this run'ssubtitleQcandcleanAudio: off drops an earlier run's report or MP3 link;subtitleQcon gives the report of the reused subtitles, or "skipped" when this run saves no subtitle file (saveSubtitles: falseor no SRT/VTT format);cleanAudioon gives the MP3 the earlier run made, orcleanAudioError"skipped" when it made none (a reused transcript downloads no audio). - 📝 Meeting notes (
"meetingNotes": true): summary, decisions, action items (task, owner, due date) and, with speaker labels, talk time and main points per speaker, as JSON and a Markdown file (no extra charge) - 🧠 Optional AI summary and chapters (
"summarize": true), with no API key needed - 🌍 Automatic language detection, or set the language yourself
- 🔤 Vocabulary hints: give names and terms (e.g. "Apify, Kubernetes") so they are spelled correctly
- ✏️ Term corrections (
"termCorrections": {"Cloud flare": "Cloudflare"}): when a name still comes out wrong, each result also getscorrectedTextwith your spellings andcorrectionCounts;textstays as transcribed (no extra charge, details) - 🀄 Traditional or Simplified Chinese (
chineseOutputScript):traditionalorsimplifiedfor Chinese speech in files and podcast audio;auto(default) follows the feed language and your vocabulary. Same price (details) - 🎚️ Fast or accurate (
transcriptionEngine):fast(default) uses Cloudflare Workers AI Whisper for the quickest results;accurateuses Whisper on our own server, with fewer errors but about twice the time. Same price (details) - 📏 Large files: up to 2 GB per file, any length. Long recordings are cut in pauses and transcribed in parallel
- 🔕 Silent files are free: files with no speech are reported but not charged
- ⚡ Fast: in our tests an 8-minute MP4 video took 33 seconds and a 26-minute MP3 took 109 seconds
- 📄 TXT and Markdown transcripts for every file (
txtUrl,mdUrl), plus one combined file per run, ready for Google Docs, Notion or an LLM (no extra charge) - 🔌 n8n, Make and Zapier: one HTTP request, no 25 MB file limit and no OpenAI key (examples below)
What you can rely on:
- Files, podcast feeds, YouTube, TikTok videos, Instagram Reels, Loom, Wistia, Streamable and Reddit (v.redd.it) videos. The Actor downloads media files, share links and RSS feeds. YouTube video links go through our own paced YouTube service (no login, no cookies); when YouTube pauses it, those rows are free and say to retry later. Loom and Wistia links use the file of the host's public player, only for public media. Public TikTok video links are transcribed from the video's original sound, and public Instagram Reels from the reel's video (no login, no cookies). Streamable videos are transcribed from their public player MP4, and Reddit's v.redd.it videos from the audio track of their public stream (requests carry our own user agent and only use paths the host's robots.txt allows; Reddit post pages are not read). It does not list TikTok or Instagram profiles, and it does not collect account data.
- No start fee, no per-run or per-file fee. You pay per audio minute. Files that fail, have no speech or are skipped by your limits are free, and every row says
charged: true/false. - Stable output. New fields may be added; existing fields are not renamed or removed, so your parser or automation keeps working.
Cost control and price comparison
Control your cost
- What is charged: the minutes of each transcribed file (rounded up per file) and, with
summarizeon, one AI summary per file. Minutes are charged only after the transcript row has been saved. - What is free: lines that are not a URL, duplicate links (also the same YouTube video in two link forms), Spotify / Vimeo / SoundCloud page links, TikTok and Instagram profile links, private Loom and Wistia media, private, removed and licensed-music-only TikTok videos, Instagram photo posts and private or removed reels, Streamable videos that are still uploading or processing, Reddit post links (not read: pass the video's v.redd.it link instead), YouTube list lookups and failed videos, web pages without exactly one media file, files that cannot be downloaded or decoded, files without speech, files skipped by your limits, and publisher transcripts (
billedMinutes: 0). Every row hascharged: true/false, and failed rows say "(not charged)". - Plan at the start: the run's status message and log start with the cost plan (audio minutes known from podcast feeds, summaries, and how many files are measured when downloaded); it is also saved as
SUMMARY.costPlan. - Caps: Max minutes per file and Max total minutes per run (Advanced settings) skip files before they are transcribed, using the duration read from the file (or from the podcast feed, before downloading).
- Max charge per run: set Maximum cost per run in the run options (or
maxTotalChargeUsdin the API). A file is only started when the limit still covers all its minutes. A file that does not fit is skipped with a free row and the run goes on with the files after it; once the limit cannot pay for another minute, or 5 files were skipped this way, no new file starts.SUMMARY.statusis thenLIMIT_REACHEDandSUMMARY.notProcessedlists the files that were not started (count and up to 100 inputs). The status message andSUMMARY.limitReachedsay how many files were processed and how many are left;SUMMARY.remainingInputslists the remaining links and theREMAINING_INPUTrecord is your input with only those. To continue, raise the maximum cost per run and run that input: finished files are not in it, so they are not charged again (running the same full input again would transcribe and charge them again, unlessnewEpisodesOnlyorresumeStoreis on). The AI summary is only made when the limit also covers it. - If Apify restarts the run (server migration or Resurrect), items already finished are skipped and not charged again (
SUMMARY.resumedSkipped). - Time limit per file (
fileTimeoutSecs, default 1,800 seconds): a file whose download, conversion and transcription take longer gets a row witherrorType: "timeout"and is not charged, and the run goes on with the next file. Raise it for recordings of several hours. YouTube videos get at least their length x 15 seconds + 20 minutes. - Files in parallel (
fileConcurrency, default 3, up to 10): several files are downloaded and transcribed at the same time, so podcast batches finish sooner. Rows are saved in the order files finish;inputIndexis each file's position in the input. Your max charge per run and Max total minutes per run still hold: before a file is transcribed, its cost is reserved together with the files already in progress, so files running side by side can never go over the limit. With little run memory fewer files run at once (about 3 at the default 1,024 MB); the time a file waits for budget held by other files does not count againstfileTimeoutSecs.
Price comparison (checked 2 October 2026)
Per-minute prices of other transcription Actors on Apify Store, Free plan unless noted; "not stated" means we did not find it in that Actor's input or README:
| Actor | Price | Speaker labels |
|---|---|---|
| This Actor | $0.006 per minute, no start fee (Gold+ $0.0048) | $0.015 per minute instead of $0.006 |
| steadyfetch / media-transcriber | $0.003 per minute | not stated |
| amanatools / whisper-transcriber | $0.008 per minute, plus $0.005 per file | not stated |
| kaz_kakyo / audio-transcriber | $0.01 per minute; chapters $0.01 per file | yes (Deepgram nova-3) |
| viralanalyzer / video-transcriber | $0.015 per minute | not stated |
| sauliusautomatesit / media-transcriber | $0.035 to $0.12 per minute | not stated |
| hgservices / speech-to-text | $0.042 per minute (Gold $0.024), plus $0.09 per YouTube video | yes |
| memo23 / video-audio-transcriber | $0.048 per minute (billed per second, $0.0008 per second), plus $0.005 per run | yes |
| tictechid / vanzi-universal-transcriber | $0.0025 per second ($0.15 per minute), plus $0.01 per run | not stated |
| sian.agency / INCREDIBLY-FAST-audio-transcriber | $0.30 per minute ($0.005 per second) on the Free plan (Bronze $0.015, Gold $0.009), plus $0.005 per run | extra $0.001 per second on the Free plan, $0.0001 per second on paid plans |
| parseforge / audio-transcriber | $0.44 per file, plus $0.012 per run | not stated |
This is not the lowest per-minute price: steadyfetch charges half as much. What the $0.006 includes: no start fee and no per-file fee; failed, silent and skipped files free; SRT/VTT subtitles and TXT/Markdown files; podcast RSS feeds with date and title filters, publisher transcripts (0 audio minutes) and only-new-episode schedules; Google Drive, Dropbox and OneDrive share links; files up to 2 GB. Speaker diarization, translated subtitles and AI summaries are optional. YouTube video links, public TikTok video links, public Instagram Reels, Loom, Wistia, Streamable and Reddit (v.redd.it) video links are transcribed at the same per-minute price, with no per-video fee; keyword search (searchTerms) is included.
Switching from another transcription Actor
You can paste the input you already use: field names of the most used transcription Actors are accepted and mapped to this Actor's fields (the run log lists each one under "Input compatibility"). Options that have no equivalent here are listed in the log too, never silently dropped.
Comparison with the two most used audio/video file transcribers on Apify Store, checked on 2 October 2026 from each Actor's public pricing and README (Free plan; prices can change, check their pages):
| This Actor | sian.agency / INCREDIBLY-FAST-audio-transcriber | memo23 / video-audio-transcriber | |
|---|---|---|---|
| Price per 1,000 audio minutes | $6 (Gold+ $4.80) | $300 on the Free plan ($0.005 per second); $15 Bronze, $9 Gold+ | $48 ($0.0008 per second) |
| Start fee | none | $0.005 per GB of run memory | $0.005 per GB of run memory ($0.01 at the default 2 GB) |
| Billing unit | started minute per file | second | second |
| Failed or silent files free? | yes, every row has charged | files over the Free-plan cap are not charged; other failures not stated | yes, only delivered transcripts are charged |
| Files per run | up to 1,000 | 1 file of up to 5 MB or 60 seconds on the Free plan; unlimited on paid plans | up to 50 URLs |
| Speaker labels | $0.015 per minute instead of $0.006 | extra $0.001 per second on the Free plan | included |
| AI summary and chapters | $0.01 per file | not offered | included |
| Podcast RSS feeds | yes, with date/title filters and publisher transcripts | not stated | yes, with publisher transcripts |
| YouTube / TikTok / Instagram links | YouTube, public TikTok video links and public Instagram Reels yes, same per-minute price (no speaker labels on YouTube) | yes (paid plans) | yes |
| Model | Whisper large-v3-turbo | not stated | Whisper tiny, base or small |
| Max file size | 2 GB | 1 GB on paid plans | not stated |
If you transcribe files, recordings, podcast feeds, YouTube, TikTok or Instagram Reels, this one costs less per minute. For YouTube videos that already have captions, our YouTube Transcript Scraper is cheaper still.
Paste your existing input. Both of these work unchanged:
{"audioUrls": ["https://example.com/episode.mp3"],"language": "english","speakerDiarization": true}
{"mediaUrls": ["https://example.com/meeting.mp4", "https://feeds.npr.org/510289/podcast.xml"],"maxEpisodesPerFeed": 1,"maxMinutesPerItem": 120,"publishedAfter": "2026-09-01","newItemsOnly": true,"watchlistId": "my-show"}
| Field from another Actor | Read here as |
|---|---|
audioUrls, startUrls (strings or { "url": ... }), mediaUrls, media_urls, videoUrls, fileUrls | urls (podcast feed links in these lists go to podcastFeeds) |
podcastUrls, feedUrls, rssUrls | podcastFeeds |
maxEpisodesPerShow, rss_episodes | maxEpisodesPerFeed |
maxMinutesPerItem, max_minutes_per_file | maxMinutesPerFile (0 = no limit) |
publishedAfter / episodeTitleContains | episodesSince / titleIncludes |
newItemsOnly / watchlistId | newEpisodesOnly / monitorName |
speakerDiarization, diarize | speakerLabels (the speaker-label price applies) |
includeWords, word_timestamps | wordTimestamps |
keyterms | vocabulary |
outputFormats containing srt and/or vtt | Save only the selected original-language subtitle formats; omitting it keeps both, and a list without either saves neither |
language: "auto", "english", "en-US" | auto-detect, "en", "en" |
Not used here (noted in the log): translateToEnglish (the transcript stays in the spoken language; for English subtitles set "subtitleLanguages": ["English"]), useEuServers, model, cookies, deepgramApiKey. When both a field of this Actor and another Actor's name for it are set, this Actor's field wins.
Supported input
| Input | Details |
|---|---|
| Audio files | MP3, WAV, M4A, AAC, OGG, OPUS, FLAC, AIFF, WMA, AMR and other formats ffmpeg can read |
| Video files | MP4, MOV, MKV, WEBM, AVI and more. Only the audio track is used |
| Size | Up to 2 GB per file, any length |
| Share links | Google Drive files and public folders (every audio/video file in the folder; large files without the virus-scan page), Dropbox shared links (also a file opened inside a shared folder), OneDrive (1drv.ms), SharePoint / OneDrive for Business, Box shared links, WeTransfer and iCloud Drive links. The file must open without a sign-in ("Anyone with the link"); see How do I share a cloud link so it works? |
| Temporary and presigned links | Dropbox temporary links (files/get_temporary_link, 4 hours), presigned URLs of Amazon S3 and S3-compatible storage (Cloudflare R2, Backblaze B2, Wasabi, MinIO, DigitalOcean Spaces), Google Cloud Storage signed URLs, Azure SAS URLs, Oracle pre-authenticated requests, signed Apify key-value store links, Mux signed playback links (stream.mux.com/...?token=). An expired link fails at once with errorType: "link_expired" (free) |
| Uploads | audioFiles: files uploaded in Apify Console (stored in a key-value store of your account). In API calls, pass file URLs in urls |
| Web pages | A page with exactly one <audio> / <video> file, or one og:audio / og:video / JSON-LD contentUrl file link; the found file is in mediaUrl |
| YouTube videos and lists | Public video, playlist, channel and @handle links; each selected video's audio is transcribed and billed per minute (details). List lookup itself is free |
| Loom, Wistia | Loom share and embed links (loom.com/share/<id>, also with a title, ?sid=, a <workspace>.loom.com or useloom.com host, the library address loom.com/looms/videos/<title>-<id> and the video's thumbnail link cdn.loom.com/sessions/thumbnails/<id>-...), Wistia media links (<account>.wistia.com/medias/<id>, fast.wistia.com/embed/medias/<id>, fast.wistia.net/embed/iframe/<id>): public media only, billed per minute like files (details) |
| TikTok videos | tiktok.com/@<user>/video/<id> links of public videos and short links (vm.tiktok.com/..., tiktok.com/t/...): the video's original sound is transcribed, billed per minute like files (details). Profile links are not expanded |
| Instagram Reels | instagram.com/reel/<code>/, /reels/<code>/, /p/<code>/, /tv/<code>/ and /<user>/reel/<code>/ links of public reels and video posts: the reel's sound is transcribed, billed per minute like files (details). Photo and carousel posts are free rows; profile and story links are not expanded |
| Streamable videos | streamable.com/<code> links of public videos (also the /s/<code> and /e/<code> forms): the MP4 of the host's public player is transcribed, billed per minute like files (details). A video that is still uploading or processing is a free row saying to run it again later |
| Reddit videos | v.redd.it video links (v.redd.it/<id>, or the video's HLS playlist such as v.redd.it/<id>/HLS_PLAYLIST.m3u8, the hls_url of the post): the audio track of the video is transcribed, billed per minute like files (details). Reddit post and profile pages are not read (Reddit's robots.txt does not allow it): pass the video's v.redd.it link, or a Reddit scraper Actor's dataset via datasetId |
| Podcast feeds | Any podcast RSS feed, or an Apple Podcasts show or episode link. Publisher transcripts are used when the feed has them; date and title filters are optional |
| Dataset | The datasetId of another Actor run, or its rows pasted into datasetItems: datasetId detects a field from the first row and uses it for the dataset, while datasetItems detects a field per row (or set datasetField; dot paths work); pasted rows without a usable link are free invalid_input rows that name their index |
Links must be downloadable without logging in. Facebook, X, Vimeo and Spotify page links are web pages, not files: each comes back as a free row with errorType: "unsupported_platform" and a hint. Download the media first (for example with a downloader Actor from Apify Store) and pass the file URL, or that Actor's dataset via datasetId. Vimeo streams its videos encrypted, so for Vimeo use the file behind the video's Download button when the owner allows downloads; direct file links, such as a Vimeo "video file link" (player.vimeo.com/external/... or .../progressive_redirect/...), are transcribed normally. Reddit post pages are not read either (Reddit's robots.txt does not allow it): pass the video's v.redd.it link or its direct audio file URL instead.
Web pages: for any other web page, the page is read and its media file is looked up: the one <audio> or <video> element (its src or its first playable <source>; several sources of one element are one file), or else one direct file named by og:audio, og:video, twitter:player:stream or JSON-LD contentUrl. Only direct files count: streaming playlists (HLS .m3u8, DASH .mpd) and player pages are not used. When the page has no such file (errorType: "no_media") or several ("multiple_media", listed in mediaCandidates), the row is free; pass the file you want as a direct URL.
A line that is not a URL does not stop the run: it becomes one free row with errorType: "invalid_input" and the other files are processed. Use urls (a plain list of strings) in API calls; the request-list field audioUrls from earlier versions still works.
How to use it
- Paste audio or video file URLs (the most used input; Google Drive, Dropbox and OneDrive share links work, or pages with one player), upload files, and/or add YouTube links and podcast feeds (optionally with date and title filters).
- Optional: set the language (e.g.
en,es,ja), vocabulary hints, and turn on AI summary and chapters, speaker labels or word-level timestamps. - Optional (Advanced settings): cap the cost with Max minutes per file and Max total minutes per run; for schedules, turn on Only new episodes with a monitor name.
- Click Start. Each file becomes one result with the text, segments, paragraphs and links to its SRT/VTT files.
Input example
File links first (the most common input), with a language, vocabulary hints, an AI summary and cost caps:
{"urls": ["https://example.com/meeting-recording.mp4", "https://drive.google.com/file/d/FILE_ID/view?usp=sharing"],"language": "en","vocabulary": "Apify, Cloudflare, TidyTools","summarize": true,"maxMinutesPerFile": 90,"maxTotalMinutes": 300}
Add a podcast feed (the newest 3 episodes) and a YouTube video to the same run:
{"urls": ["https://www.youtube.com/watch?v=CKRxRE7qRzc"],"podcastFeeds": ["https://feeds.npr.org/510289/podcast.xml"],"maxEpisodesPerFeed": 3}
Output example (real result, shortened)
An 8-minute MP4 video with "summarize": true:
{"url": "https://archive.org/download/gerald-ford-inaugural-address-august-9-1974-720p/Gerald%20Ford%20inaugural%20address_%20August%209%2C%201974%20%28720p%29.mp4","success": true,"format": "mp4","fileSizeBytes": 48964530,"durationSeconds": 500.1,"billedMinutes": 9,"language": "en","wordCount": 876,"text": "Mr. Chief Justice, my dear friends, my fellow Americans, the oath that I have taken is the same oath that was taken by George Washington...","segments": [{ "start": 1.36, "end": 20.4, "text": "Mr. Chief Justice, my dear friends, my fellow Americans, the oath that I have taken is the same oath that was taken by George Washington and by every president under the Constitution." },{ "start": 20.4, "end": 31.32, "text": "But I assume the presidency under extraordinary circumstances never before experienced by Americans." }],"paragraphs": [{ "start": 1.36, "end": 39.68, "text": "Mr. Chief Justice, my dear friends, ... This is an hour of history that troubles our minds and hurts our hearts." },{ "start": 41.44, "end": 73.64, "text": "Therefore, I feel it is my first duty to make an unprecedented compact with my countrymen. ..." }],"summary": "The president assumes office under extraordinary circumstances ... and asks for prayers for Richard Nixon and his family.","chapters": [{ "startSeconds": 0, "start": "0:00", "title": "Introduction and Compact with the Nation" },{ "startSeconds": 304, "start": "5:04", "title": "Message of Hope and Unity" }],"srtUrl": "https://api.apify.com/v2/key-value-stores/.../records/0000-Gerald-20Ford-...mp4.srt","vttUrl": "https://api.apify.com/v2/key-value-stores/.../records/0000-Gerald-20Ford-...mp4.vtt","txtUrl": "https://api.apify.com/v2/key-value-stores/.../records/0000-Gerald-20Ford-...mp4.txt","mdUrl": "https://api.apify.com/v2/key-value-stores/.../records/0000-Gerald-20Ford-...mp4.md","processingSeconds": 33}
With "speakerLabels": true (a real 74-minute Changelog & Friends episode: 3 speakers found, 13,069 words, 195 seconds of processing, 74 minutes billed at $0.015):
{"success": true,"model": "nova-3","durationSeconds": 4390.7,"billedMinutes": 74,"speakers": 3,"segments": [{ "start": 16.14, "end": 23.34, "speaker": 1, "text": "Welcome to Changelog and Friends, a weekly talk show about thinking outside the Dropbox." }],"paragraphs": [{ "start": 16.14, "end": 34.87, "speaker": 1, "text": "Welcome to Changelog and Friends, a weekly talk show about thinking outside the Dropbox. Thanks as always to our partners at fly.io, ..." },{ "start": 43.38, "end": 69.23, "speaker": 2, "text": "This is the year we almost break the database. Let me explain. Where do agents actually store their stuff? ..." }],"speakerTranscript": "Speaker 1 [0:16]: Welcome to Changelog and Friends, ...\n\nSpeaker 2 [0:43]: This is the year we almost break the database. ..."}
With "wordTimestamps": true, every segment also gets its words (real result, a 5-minute news episode):
{ "start": 4.16, "end": 7.52, "speaker": 1, "text": "What's up, friends? Adam here. Big week here at ChangeLog.","words": [{ "word": "What's", "start": 4.16, "end": 4.4, "speaker": 1 }, { "word": "up,", "start": 4.4, "end": 4.56, "speaker": 1 }, { "word": "friends?", "start": 4.56, "end": 4.96, "speaker": 1 }] }
In the SRT file a line starts with Speaker 2: when the speaker changes; the VTT file uses <v Speaker 2> voice tags. Files longer than 50 minutes are sent in overlapping pieces, and the speakers of each piece are matched by the words in the overlap, so Speaker 1 stays Speaker 1 for the whole file.
Speaker names and term corrections
Two optional tables, applied to the finished transcript (no extra charge, no new event, no second transcription):
speakerNames(withspeakerLabelson): your names for the speaker numbers of a first run, e.g.{"1": "Host", "2": "Guest"}(up to 20; names 1-40 characters, no line breaks, control characters or<>). A row with speakers getsnamedSpeakerTranscriptandnamedSpeakers; a speaker you did not name staysSpeaker N(source: "default").speakerTranscript,segments,paragraphs, the SRT/VTT subtitles and the TXT/Markdown files keepSpeaker N. Who is who is never guessed: check the numbers inspeakerTranscriptfirst. Without speaker labels the table is ignored.termCorrections: heard form → correct spelling, e.g.{"Cloud flare": "Cloudflare", "Kuber Netties": "Kubernetes"}(up to 50; each side 1-80 characters). Every successful row getscorrectedText(a corrected copy oftext) andcorrectionCounts(replacements per entry,0when not found).text,segments, subtitles, transcript files, the AI summary and keyword search keep the words as transcribed. Matching is literal and case-sensitive (no patterns), whole words in Latin text (AIdoes not changesaid), while a Chinese or Japanese neighbour counts as a word boundary; the longest entry wins and the text is read once. An entry whose correct spelling is another entry (A → B,B → A) is refused.
An invalid table ends the run before anything is downloaded, with one free invalid_input row saying what to change. Failed and no-speech rows get neither field. With resumeStore, both tables are applied to the saved transcript, so you can rename speakers or fix terms in a later run for free (the row is reused: true, nothing is transcribed or charged again).
{"speakerTranscript": "Speaker 1 [0:00]: Welcome back to the show.\n\nSpeaker 2 [0:04]: Thanks, we moved everything to Cloud flare.","namedSpeakerTranscript": "Host [0:00]: Welcome back to the show.\n\nGuest [0:04]: Thanks, we moved everything to Cloud flare.","namedSpeakers": [{ "speaker": 1, "name": "Host", "source": "user" }, { "speaker": 2, "name": "Guest", "source": "user" }],"text": "Welcome back to the show. Thanks, we moved everything to Cloud flare.","correctedText": "Welcome back to the show. Thanks, we moved everything to Cloudflare.","correctionCounts": { "Cloud flare": 1 }}
Transcript files (TXT and Markdown)
Every transcribed file also gets two download links (no extra charge; turn off with "saveTranscriptFiles": false):
txtUrl: plain text, one paragraph per block. With speaker labels:Speaker 1 [0:16]: ....mdUrl: Markdown with the title (episode title or file name), source link, duration, language, the AI summary and chapters whensummarizeis on, then the transcript with a[m:ss]timestamp per paragraph.
When a run has two or more transcripts, all of them are also joined in input order into transcripts.txt and transcripts.md in the run's key-value store; the links are in SUMMARY.combinedTranscript and in the run log.
A podcast episode from an RSS feed also carries the episode details:
For each successful podcast episode with a title, saveTranscriptFiles (on by default) also saves two separate publication drafts in the run's key-value store, at no extra charge:
podcastPageHtmlUrl: downloadable HTML with the title, source episode link and transcript. Whensummarizeproduced a summary or chapters, those appear too.podcastPageCmsUrl: downloadable JSON withtitle,podcastTitle,sourceUrl,excerpt,chaptersandbodyHtmlfor copying into a CMS.
These are drafts to review and edit before publishing. The Actor does not publish to a CMS or send email. With summarize: false, the excerpt and chapters are empty; no summary is invented. Failed, silent and skipped files, other media, and runs with saveTranscriptFiles: false get no page links. If either page file cannot be saved, podcastPageError explains the problem and neither page link is returned. The original TXT/MD transcript and SRT/VTT subtitle links remain available under their usual conditions.
{"url": "https://tracking.swap.fm/track/.../default.mp3?...","podcastTitle": "Planet Money","episodeTitle": "Middlegarchs are the new Oligarchs","pubDate": "2026-09-25T21:46:44.000Z","episodeUrl": "https://www.npr.org/2026/09/25/nx-s1-5981194/stealthy-wealthy-everywhere-millionaires-pass-through","feedUrl": "https://feeds.npr.org/510289/podcast.xml","success": true,"format": "mp3","durationSeconds": 2065.2,"billedMinutes": 35,"wordCount": 5505}
Meeting notes
Turn on "meetingNotes": true to get structured notes for meetings, calls and interviews (no extra charge):
meetingNotes.summary,meetingNotes.decisionsandmeetingNotes.actionItems(task, plusownerandduewhen they are said in the recording). Only decisions and tasks that are actually said are listed; the lists are empty when there are none.- Optional
meetingNotes.evidence[]cites a decision or action item withkind(decisionoractionItem), zero-basedindexin that list,startSeconds, and an exact transcriptquote. A quote is included only when it matches a timed transcript paragraph. Notes without a verifiable timed quote remain in the lists without evidence; the Markdown file shows the time and quote beside cited items. Repeated quotes use the first matching paragraph. - With
speakerLabelson:meetingNotes.speakerStats(talk time, share of talk time, words and turns per speaker, computed from the timings, not by AI) andmeetingNotes.speakerHighlights(each speaker's main points). meetingNotesUrl: the same notes as a Markdown file with a checklist of action items. The notes are also added to themdUrltranscript.ACTION_ITEMS.csvin the run key-value store: one row per AI-suggested task across successful files, withinputIndex,title,sourceUrl,task,owner,due. Missing owner or deadline stays blank; if there are no tasks, the file contains only the header.SUMMARY.actionItemsCsvUrllinks to it andSUMMARY.actionItemsCountcounts task rows. Check tasks against the original transcript before acting on them.
For long recordings, the AI notes prompt samples the beginning, evenly spaced passages, and the final spoken paragraph in time order. If the final paragraph itself is too long, its beginning and end are kept so a closing commitment can reach the notes model. The text transcript and saved transcript files remain complete; sampling applies only to the AI notes prompt.
{"meetingNotes": {"summary": "The team reviewed the launch plan and agreed on the date.","decisions": ["Ship version 2 on Friday"],"actionItems": [{ "task": "Update the user guide", "owner": "Speaker 2", "due": "Thursday" }],"speakerStats": [{ "speaker": 1, "talkTime": "12:40", "talkShare": 61.2, "words": 1830, "turns": 24 }],"speakerHighlights": [{ "speaker": 1, "points": ["Wants to launch before the conference"] }]},"meetingNotesUrl": "https://api.apify.com/v2/key-value-stores/.../records/0000-meeting.notes.md"}
The notes are written by an AI model from the transcript and can miss or misread a point: check them against the transcript before you send them on. Episodes that use the publisher's transcript (no audio minutes) get the speaker statistics only, unless summarize is on.
Publisher transcripts (no audio minutes)
Many podcasts put their own transcript in the feed (<podcast:transcript>, Podcasting 2.0). With usePublisherTranscripts on (the default), such an episode is not transcribed: the publisher's file is downloaded and converted to the same output. Timed formats (VTT, SRT, JSON) give segments with timestamps, paragraphs and our SRT/VTT files; HTML and plain text give the text and paragraphs only (timestamps: false, no subtitle files). Speaker names from the transcript are kept as speakerName on each segment. No audio minutes are charged (billedMinutes: 0); the AI summary, if you turn it on, is charged as usual.
The transcript is checked first: it must have at least 20 words, cover at least half of the episode's duration (from the feed) without running far past it, and have a plausible number of words per minute. A transcript that fails the check, or cannot be downloaded, is ignored and the episode is transcribed normally; the row then says why in publisherTranscriptError. Set "usePublisherTranscripts": false to always transcribe the audio yourself.
Real result (Podnews Daily, whose feed has a VTT transcript per episode; with "summarize": true, so only the $0.01 summary was charged):
{"podcastTitle": "Podnews Daily - podcast industry and podcasting news","episodeTitle": "Was Inception Point AI just a PR stunt?","pubDate": "2026-09-29T10:30:00.000Z","success": true,"transcriptSource": "publisher","transcriptUrl": "https://podnews.net/audio/podnews260929.mp3.vtt","transcriptFormat": "vtt","timestamps": true,"model": "publisher","language": "en","durationSeconds": 273,"billedMinutes": 0,"wordCount": 642,"segments": [{ "start": 0.56, "end": 4.96, "text": "From Singapore Airport, the latest from podnews.net with The Podglomerate.", "speakerName": "James Cridland" }],"summary": "Inception Point AI's CEO Jeanine Wright has suggested that the company's thousands of AI-made podcasts may have been perceived as a publicity stunt, ...","summaryCharged": true}
In the same test, a 63-minute Buzzcast episode (12,067 words, 810 segments, 10 chapters) was also taken from the publisher's transcript: 0 audio minutes instead of 64.
Feed filters
{"podcastFeeds": ["https://podnews.net/rss"],"episodesSince": "2026-09-20","episodesUntil": "2026-09-28","titleIncludes": ["patreon", "spotify*video"],"titleExcludes": ["weekly"],"maxEpisodesPerFeed": 1}
episodesSinceandepisodesUntilapply only to RSS/Podcast feeds.episodesSinceaccepts a date (YYYY-MM-DD) or a relative time such as30 days,2 weeks,6 months;episodesUntilaccepts a date, including the whole day.titleIncludes: keep an episode when its title contains one of the texts;titleExcludes: drop it when its title contains one. Case does not matter;*matches anything (spotify*video).- Filters are applied first, then
newEpisodesOnly, thenmaxEpisodesPerFeed. With a date filter, episodes without a publish date are dropped. A link to one Apple Podcasts episode is not filtered. - Filtered episodes get no row and are not charged;
SUMMARY.episodesFilteredcounts them. In our test above, 150 of 151 episodes were filtered and the one match ("Patreon removes per-creation memberships", 4.6 minutes) was transcribed.
Web page with one player
{ "urls": ["https://archive.org/details/nasa_tv-ScienceCast_-_Record-Setting_Asteroid_Flyby"] }
Real result (shortened): the page's og:video file was found and transcribed, 262 seconds, 5 minutes billed:
{"url": "https://archive.org/details/nasa_tv-ScienceCast_-_Record-Setting_Asteroid_Flyby","mediaUrl": "https://archive.org/download/nasa_tv-ScienceCast_-_Record-Setting_Asteroid_Flyby/ScienceCast_-_Record-Setting_Asteroid_Flyby.mp4","success": true,"transcriptSource": "asr","format": "mp4","billedMinutes": 5,"segments": [{ "start": 3.7, "end": 8.48, "text": "Record-setting asteroid flyby, presented by Science at NASA." }]}
Loom and Wistia links
{ "urls": ["https://www.loom.com/share/3690d29d5e3c426eb0f7ea00f9d3ae7b", "https://fast.wistia.com/embed/medias/j38ihh83m5"] }
Paste the link you would share with a colleague. The Actor asks the host for the file its own public player uses (Loom's video file, or for newer Loom videos that are only streamed, the audio track of the stream; Wistia's smallest MP4 or audio file, never an audio-description track), downloads it and transcribes it like any file, billed per audio minute, no per-link fee. The row adds mediaTitle (the title on Loom or Wistia), mediaUrl (the file that was transcribed) and sourceKind (loom or wistia); sourceFileId is the host's id (loom:<id>), so resumeStore recognises the same video in another link form.
Real result (shortened; 53-second Loom video, 1 minute billed, 7 seconds end to end):
{"url": "https://www.loom.com/share/3690d29d5e3c426eb0f7ea00f9d3ae7b","mediaUrl": "https://cdn.loom.com/sessions/transcoded/3690d29d5e3c426eb0f7ea00f9d3ae7b.mp4?Policy=...","mediaTitle": "Sell Faster and Smarter with Loom","sourceKind": "loom","success": true,"durationSeconds": 53.4,"billedMinutes": 1,"text": "Ready to prospect more efficiently, close more pipeline, and create loyal champions? Loom helps you stand out in a sea of text. ..."}
Only public media is read: a private, password-protected or workspace-only Loom or Wistia video and a removed link each come back as a free row (private_media or not_found) that says why. Encrypted streams are not read (free row, unsupported_platform). A Loom folder, space or library link is a free folder_link row that asks for the share link of each video; another loom.com page is a free unsupported_platform row. Loom's free rows also carry reasonDetail (loom_folder, loom_unrecognized_path, loom_encrypted, loom_no_audio_playlist or loom_no_segments). Requests carry our own user agent and only use paths that the hosts' robots.txt allows.
TikTok videos
Paste public TikTok video links (https://www.tiktok.com/@<user>/video/<id>, the m.tiktok.com form, or a short link from the app's Share button such as https://vm.tiktok.com/...), one per line. The Actor reads the public video page, takes the video's sound track and transcribes it like any file, billed per audio minute, no per-video fee. The row adds mediaTitle (the video's caption), mediaUrl (the sound file that was transcribed) and sourceKind: "tiktok"; sourceFileId is tiktok:<video id>, so resumeStore recognises the same video in another link form.
- Only the creator's original sound is transcribed. A video whose sound is licensed music or another creator's sound has no voice track of its own to transcribe: it is a free row (
errorType: "unsupported") that says so. - Private and removed videos are free rows (
private_media,not_found). When TikTok does not give Apify's network the video (a refusal, a verification or empty page, or an unknown TikTok status; this can happen from data-centre networks, and with videos shown only in some countries), the page is read again through our other networks (our backend, our server, our home line;downloadVialimits which,"direct"= Apify's network only). If none of them gets it, the row is free (blocked), lists each network's answer, and is worth running again later. - No login and no cookies are used, and the video file itself is not downloaded. Profile, sound, tag and photo links are not expanded: pass the link of each video.
- Direct file links from TikTok's servers (for example from another tool's export:
https://v16m.tiktokcdn-us.com/...,https://v16m.tiktokcdn.com/...,https://v16-webapp-prime.tiktok.com/video/tos/...) are downloaded like any file and billed per minute. TikTok signs these links for about a day, and the*-webapp-prime.tiktok.comones (a video'splayAddr/downloadAddr) only work in the browser session that loaded the video. An expired or refused one is a free row (errorType: "link_expired", or"link_invalid"when the link was changed or cut short) that tells you to pass the video page link (tiktok.com/@user/video/...) instead, which is read fresh on every run.
Instagram Reels
Paste links of public Instagram Reels or video posts (https://www.instagram.com/reel/<code>/, /reels/<code>/, /p/<code>/, /tv/<code>/ or /<user>/reel/<code>/), one per line. The Actor reads the reel's public embed player, downloads its video right away and transcribes the sound like any file, billed per audio minute, no per-reel fee. The row adds mediaTitle (the reel's caption) and sourceKind: "instagram"; sourceFileId is instagram:<code>, so resumeStore recognises the same reel in another link form. The video file link that Instagram signs for the player expires, so it is not saved in the row.
- Only the reel itself is used (its sound, caption and length). The owner's account, likes, comments and view counts are not read into the output.
- Photo and carousel posts are free rows (
errorType: "no_media", "this post is not a video"). A video Instagram has blocked for copyright is a free row (unsupported). - A reel whose sound is music from Instagram's library is still transcribed; when no speech is heard, the row is free (
no_text) and says the sound is library music. - Private and removed reels are free rows (
private_media,not_found). When Instagram refuses the request (this can happen from data-centre networks), the row is free (blocked) and worth running again later. - No login and no cookies are used. Profile, story, highlight and share links (
instagram.com/share/...) are not expanded: pass the link of each reel.
Streamable and Reddit videos
{ "urls": ["https://streamable.com/dnd1", "https://v.redd.it/zv89llsvexdz/HLSPlaylist.m3u8"] }
Paste a public Streamable link (streamable.com/<code>, the share, /s/<code> or embed form) or the link of a Reddit-hosted video (https://v.redd.it/<id>, or the video's HLS playlist such as https://v.redd.it/<id>/HLS_PLAYLIST.m3u8 — the hls_url of its post). Streamable videos are transcribed from the MP4 of their public player, Reddit videos from the audio track of their public HLS stream, billed per audio minute like files, no per-video fee. The row adds sourceKind (streamable / reddit); sourceFileId is streamable:<code> / reddit:<video id>, so resumeStore recognises the same video in another link form. A Streamable row never contains the file's CDN link (mediaUrl / downloadUrl): Streamable signs that link for a short time and it expires, so the row keeps your original link and the stable fields instead, and the signed link is only used to download the file right away. A Reddit row keeps mediaUrl (the audio playlist that was transcribed) as before.
- A Streamable video that is still uploading or processing is a free
service_busyrow: run that link again later; it is only transcribed (and charged) when it is ready. A video that failed on Streamable's side is a freenot_foundrow, and one whose file link is not a usable HTTPS URL (a malformed CDN link on Streamable's side) is a freeunsupported_platformrow. - Reddit post and profile links are not read: Reddit's robots.txt does not allow reading them with our user agent, and no cookies are used. Pass the video's v.redd.it link or HLS playlist instead, or a Reddit scraper Actor's dataset via
datasetId(its media URL fields are detected automatically). Direct video and audio files on v.redd.it (for exampleDASH_AUDIO_128.mp4) are transcribed as plain files too. - Requests carry our own user agent, use no cookies and only touch paths that the host's robots.txt allows (Streamable's API host; Reddit's video host v.redd.it). A video that was removed, or that our network is refused to read, is a free row (
not_found,blocked) that says so.
Keyword search (brand and topic mentions)
{"podcastFeeds": ["https://feeds.npr.org/510289/podcast.xml"],"maxEpisodesPerFeed": 5,"searchTerms": ["inflation", "Federal Reserve", "tariff*"]}
Add searchTerms to any run (files, feeds, YouTube, Loom...). Each transcript gets every mention with its time, the sentence it was said in, the speaker (with speaker labels or a publisher transcript that names them) and a link that opens the recording at that moment, plus a count per term. No extra charge: it runs on the transcript you already pay for.
Real result (shortened, "searchTerms": ["Loom", "prospect*", "pipeline", "video"] on the Loom video above):
{"keywordsFound": ["Loom", "prospect*", "pipeline", "video"],"keywordCounts": { "Loom": 7, "prospect*": 2, "pipeline": 1, "video": 2 },"keywordMatches": [{ "term": "prospect*", "matched": "prospect", "start": 0.5, "end": 5.7, "time": "0:00", "link": "https://www.loom.com/share/3690d29d5e3c426eb0f7ea00f9d3ae7b?t=0", "text": "Ready to prospect more efficiently, close more pipeline, and create loyal champions?" },{ "term": "Loom", "matched": "loom", "start": 6.86, "end": 9.7, "time": "0:06", "link": "https://www.loom.com/share/3690d29d5e3c426eb0f7ea00f9d3ae7b?t=6", "text": "Loom helps you stand out in a sea of text." },{ "term": "prospect*", "matched": "prospects", "start": 18.3, "end": 20.26, "time": "0:18", "link": "https://www.loom.com/share/3690d29d5e3c426eb0f7ea00f9d3ae7b?t=18", "text": "Prospects can book a meeting instantly." }]}
- Matching: case and accents are ignored (
cafefinds "café"); whole words only, soAIdoes not match "said" (Chinese, Japanese, Korean and Thai terms match anywhere, as those languages have no spaces); a phrase may run across two segments; a trailing*matches word endings (tariff*finds "tariffs"). Up to 50 terms, up to 500 listed mentions per file (counts stay exact;keywordMatchesTruncated: truewhen more). - Links to the moment: YouTube
&t=, Loom?t=, Wistia?wtime=, and#t=on the media file for files and podcast episodes (browsers' own players start there). - One list for the whole run:
KEYWORD_MATCHES.csv(opens in Excel and Google Sheets) andKEYWORD_MATCHES.jsonin the run's key-value store, one line per mention with the episode or video title; totals per term inSUMMARY.keywords; and the Keyword mentions view of the dataset. - Brand monitoring on a schedule: combine
searchTerms,newEpisodesOnly, a podcast feed list and a stablemonitorName. Create a recurring Apify Schedule yourself. Optionally set the secretmentionWebhookUrlto a JSON endpoint or Slack/Discord webhook. The first run transcribes the latest episodes as a baseline and sends no alert; later runs send at most one alert for newly completed podcast episodes with matches. No new episode, no match or a failed file sends none. The alert includes the term, episode title, time link when available, short excerpt and run link.SUMMARY.mentionAlertreportssent,failedorattemptedwithout the webhook URL; delivery failure does not alter the transcript or charges. A send attempt is marked before delivery to avoid a duplicate after restart, so an uncertain delivery is not automatically retried. WithresumeStore, transcripts done before are searched again with new terms without paying again (reused: true).
The shareable RETRY_INPUT and REMAINING_INPUT records omit mentionWebhookUrl. If you reuse either record, add any needed monitoring settings and webhook separately in the new run's input.
YouTube videos
{ "urls": ["https://www.youtube.com/watch?v=CKRxRE7qRzc", "https://youtu.be/...", "https://www.youtube.com/shorts/..."] }
Paste YouTube video links like any other file: youtube.com/watch?v=, youtu.be/, /shorts/, /live/ and /embed/ links all work (a watch link with &list= is that one video). The audio of the video is transcribed with the same Whisper large-v3-turbo model and billed exactly like a file: $0.006 per started audio minute, no per-video fee. Only transcribed minutes are charged; a video that cannot be transcribed is a free row.
Real result (shortened; 89-second video, 2 minutes billed, 30 seconds end to end):
{"url": "https://www.youtube.com/watch?v=CKRxRE7qRzc","sourceKind": "youtube","youtubeVideoId": "CKRxRE7qRzc","videoTitle": "特朗普與習近平在美國會面:有哪些關鍵議題?- BBC News 中文","success": true,"transcriptSource": "asr","asrBackend": "whisper-local","durationSeconds": 89.4,"billedMinutes": 2,"language": "en","text": "Presidents Trump and Xi are about to meet here in the US. So what's at stake? ..."}
How YouTube links differ from files:
- Captions are not used: the spoken audio is always transcribed (Whisper quality, your language settings). If the video already has captions and you only need the text, our YouTube Transcript Scraper is cheaper (one small fee per video instead of per minute), and it also lists whole channels and playlists.
- Playlists, channels and
@handlelinks: paste the public YouTube URL intourls. The newestmaxVideosPerListvideos (default 20, maximum 500) enter the same transcription path as individual video links. Before trying a playlist or channel, setmaxVideosPerListto 1–3 or useestimateOnlyto bound first-run minute charges. A list lookup or a video skipped by the cap is free; a failed lookup gives one freesource_unavailablerow.SUMMARY.listsExpanded,videosFromListsandvideosSkippedByLimitreport the counts.titleIncludesandtitleExcludesfilter list titles before the cap; date filters (episodesSince/episodesUntil) apply only to RSS/Podcast feeds. A YouTube list with either date field returns one freeinvalid_inputrow; remove the date field and usemaxVideosPerList, title filters ornewEpisodesOnlyinstead.newEpisodesOnlyremembers completed videos by monitor name; on a monitor's first run, older videos beyond the cap become its baseline. A video also pasted individually is transcribed once. - Per-video limit:
maxMinutesPerFile(default and maximum for YouTube: 240 minutes, so a 3-hour podcast video works without changing anything). Longer videos are a freetoo_largerow that says what to do: raise your ownmaxMinutesPerFile, or, over 240 minutes, use the YouTube Transcript Scraper when the video has captions, or pass the recording as a file link (files have no length limit). Your max charge per run andmaxTotalMinuteslimit each video the same way as files. A long video gets at least its length x 15 seconds + 20 minutes offileTimeoutSecs(a 4-hour video: 80 minutes), because the YouTube queue on our side decides how long it takes. - Speaker labels are not available for YouTube links: with
speakerLabelson, a YouTube link is a freeunsupportedrow (turn speaker labels off, or pass a downloaded file).wordTimestamps(segment timings only) andvocabularyare not applied; the row lists them inoptionsNotApplied. Subtitles (SRT/VTT), paragraphs, transcript files, AI summary, meeting notes and translated subtitles work as for files. language: the spoken language is always detected and the video is transcribed in it. When you setlanguageand the video is spoken in another language, you still get the transcript (charged as usual) and the row says so inlanguageNote; for a transcript in your language, add it tosubtitleLanguages. A Chineselanguage(zh,zh-TW,zh-CN) also picks the script (Traditional or Simplified).- Paced access, retry later: YouTube is accessed at a steady pace from our own service, without login or cookies. When YouTube challenges it, YouTube access pauses for a few hours; videos in that time get a free row (
errorType: "service_busy", "please try again later"). Private, removed, members-only, age-restricted and region-blocked videos are free rows too, each with its own reason (private_media,not_found,needs_login,unsupported). Videos are transcribed one at a time on our side, so a busy queue can make a run wait; the wait counts toward the video's time limit. - Time limits in long batches: a video still not finished when its time limit runs out is a free
timeoutrow that says so (retryable: true), and it is tried once more at the end of the run by reading the same job on our side (it is not transcribed or charged twice). Before the run's own timeout (Run options > Timeout), the run stops waiting 2 minutes early and does not start a YouTube video with less than 5 minutes left (freerun_time_limitrows): every video still gets its row, the transcribed ones are charged as usual, andRETRY_INPUTholds the links to run again. For a large batch, give the run a long timeout or split it into smaller runs. - Automatic retries: when YouTube refuses an audio download for a moment (HTTP 403), the video is tried again in the same run (up to 2 more times, 20 to 40 seconds apart); only a video that still fails is a free
download_failedrow that says to run the link again later. A video skipped only because other videos running in parallel held your max charge is tried again with its real length once that budget is free. When our YouTube service is busy or cannot be reached for a moment, the video is sent again (up to 2 more times, 20 and 40 seconds apart), and a video already sent is waited for through a short outage of our service (3 to 15 minutes, longer for longer videos); only a video that still does not get through is a freeservice_busyrow, tried once more at the end of the run. A video with only music or background sound is a free "no speech" row (noSpeech: true). - If our transcription server stops while processing YouTube audio, the video gets one free
transcription_unavailablerow (charged: false,billedMinutes: 0) and is not submitted to the same server again in that run. Try again later, or download and split a long recording into shorter clips. The 240-minute setting is an input limit, not a guarantee that every long recording can finish. - Extra fields on YouTube rows:
sourceKind: "youtube",youtubeVideoId,videoTitle,channel,publishDate,videoDurationSeconds;sourceFileIdisyoutube:<video id>.
Results that are not charged
Every row has success: true or false. Rows with success: false are free and carry an errorType and retryable (whether running them again later may work, see Retry only what may work later). When there is a specific fix, the row also has howToFix: one sentence with the exact setting to change for that link's host (for example "In Dropbox: Share > "Copy link" with "Anyone with the link can view" ..."). Loom rows can also carry reasonDetail, a short code for which Loom case it was (for example loom_folder).
| errorType | Meaning |
|---|---|
no_text | No speech in the audio (silence or music only); the row also has noSpeech: true. This is a free, valid answer, not a transcript or a download failure: it does not make the run PARTIAL_RESULTS and is counted in SUMMARY.noSpeech. If every file has no_text, check the spoken audio track or use another file or link with speech; the run status and SUMMARY.nextStep say so. |
too_large | Longer than maxMinutesPerFile, or larger than 2 GB |
limit_reached | Would go over maxTotalMinutes or your Apify spending limit |
run_time_limit | A YouTube video not started because less than 5 minutes of the run's timeout were left (retryable: true, in RETRY_INPUT); give the run a longer timeout or run those links again |
private_link | A share link that is not public: shared only with invited people or your organisation, protected by a password, or with downloads turned off (Box, OneDrive "Block download"). howToFix names the sharing setting to change |
needs_login | The link opened a sign-in page instead of the file (Google, Microsoft, Dropbox or Box sign-in): share the file as "Anyone with the link", or upload it with audioFiles |
quota_exceeded | The host's download limit for this file was reached (Google Drive: too many downloads in 24 hours; Dropbox: too much traffic). retryable: true: run it again later |
not_audio | The link is a file, but not audio or video (a Google Docs file, a PDF or ZIP shared from OneDrive or Box, an image or document that a Google Drive link downloads, a file without an audio track) |
folder_link | A Dropbox, OneDrive, SharePoint, Box or Loom folder, or a ZIP download: pass the link of each recording (Google Drive folder links are expanded instead) |
unsupported | Not an audio or video file (e.g. a web page), a TikTok video whose sound is licensed music (no original voice track), an Instagram video blocked for copyright, a language that speaker labels do not support (e.g. Chinese), or speaker labels on a YouTube link |
unsupported_platform | A Facebook, X, Vimeo or Spotify page link (not a file), a TikTok or Instagram profile, story or other non-video page, a YouTube non-video page such as search, a SoundCloud link (pass a podcast RSS feed or direct audio file), a Reddit post page, a loom.com page that is not a video, or an encrypted-only Loom stream (see reasonDetail); the error says what to do instead |
private_media | A private Loom, Wistia, TikTok, Instagram or YouTube video; ask the owner to make it public, or upload a recording you can access |
link_expired, link_invalid | A signed or temporary link that no longer works (link_expired: a presigned or Dropbox temporary link past its time, a TikTok file link that expired or only works in the browser session that made it) or whose signature was refused (link_invalid: changed or cut short when copied). Running it again will not help: make a new link, or for TikTok pass the video page link (tiktok.com/@user/video/...) |
needs_login (YouTube) | An age-gated or members-only video needs sign-in, which this Actor does not use; choose a public video or upload a recording you can access |
service_busy | YouTube links: YouTube access is paused after a YouTube challenge, the daily YouTube limit is reached, or speech-to-text is busy. Streamable videos that are still uploading or processing (run the link again later). Please try again later |
no_media | A web page without an audio or video file we can use (no player, or only a streaming playlist or player page), or an Instagram photo or carousel post |
multiple_media | A web page with several audio or video files; they are listed in mediaCandidates |
invalid_input | A line that is not a URL, or a setting that cannot be used (an unknown language or subtitle language, a wrong episode filter, no input at all): then the run ends with this one free row and says what to change |
not_found, blocked, network, timeout, http_error, download_failed | The file could not be downloaded. A YouTube video that is unavailable, removed or from a terminated account is not_found (use another video; running it again will not help); a YouTube video not finished within its time limit or before the run's timeout is timeout (run it again). A Google Drive link that downloads an empty or tiny file of an unknown type instead of the recording is download_failed, not no_text (an image or a document is not_audio): check the sharing and that the link points to the recording, or pass a direct download link or upload the file. A blocked row was refused by the file host on every route we tried; downloadAttempts lists them (see Files the host refuses) |
rate_limited | The file host asked us to wait more than 2 minutes before the next download (HTTP 429 with Retry-After); run that file again later |
failed | Processing stopped with an error, for example speech-to-text failed on one part of a file with speaker labels. This does not mean the recording has no speech. No partial transcript is delivered and nothing is charged. retryable: true: run it again |
A batch never fails as a whole because some files could not be downloaded: the other files are transcribed and charged, the failed ones are free rows, and RETRY_INPUT holds the links worth running again.
Files the host refuses
Some file hosts refuse downloads from data-center networks, refuse non-browser clients, or stop answering after many files in a row (HTTP 401, 403, 429 or a bot-check page). With the defaults, a refused file is tried again:
- on Apify's network with a standard desktop browser User-Agent instead of ours (
downloadUserAgent: "auto"; set"tidytools"to never switch); - through our backend (Cloudflare, any file size), our Oracle server in the US (files up to 15 MB) and our home line in Taiwan (files up to 10 MB) (
downloadVia: "auto"; pick one route to use only that one).
Each row says how the file came through (downloadRoute: direct, backend, vm or residential; downloadUserAgent: tidytools or browser), and a file that was refused at first also lists every attempt in downloadAttempts. SUMMARY.downloads counts them. There is no extra charge for any route. We never send cookies or log in, never solve CAPTCHAs or use browser automation to get past a bot check, and when a host sends Retry-After we wait that long before asking it again from any route. Files on the same host in one run start with the route that worked for that host; a host that refused everything for two files is asked only once for each later file.
If at least 10 files in a run are blocked and every result row is blocked, the status suggests trying fewer simultaneous batches later when several batches came from the same host. Retry only the blocked links with RETRY_INPUT when that record was saved. In a mixed batch, check each other row separately; its outcome is not implied by the blocked status note.
The run's key-value store also has a SUMMARY record with status (SUCCESS, PARTIAL_RESULTS, FAILED, NO_RESULTS or LIMIT_REACHED), files transcribed, files without speech (noSpeech, counted as successes), minutes billed and more. When files failed, the same record also says what to do with them: failuresByReason counts the failed files by errorType (e.g. { "private_link": 2, "needs_login": 1, "blocked": 1 }), filesNeedNewInput is how many of them will not work until the input changes, and failureHint is the same one-line next step as in the run's status message. Files without speech are not in these counts (still a valid answer, counted in noSpeech), and neither are limit_reached rows (the run's stop, not a file failure: SUMMARY.remainingInputs and the REMAINING_INPUT record say how to continue).
Use in n8n, Make or Zapier (no 25 MB limit)
OpenAI's Whisper API, and the Whisper nodes in n8n and Make, reject files over 25 MB, so long recordings have to be compressed or split first. Here you only send the file link: the Actor downloads the file (up to 2 GB, any length), cuts it at pauses and returns the whole transcript. No OpenAI key and no ffmpeg step.
One HTTP request. run-sync-get-dataset-items starts the run, waits for it and returns the results (one item per file):
POST https://api.apify.com/v2/acts/tidytools~audio-transcriber/run-sync-get-dataset-itemsAuthorization: Bearer <your Apify API token>Content-Type: application/json{ "urls": ["https://drive.google.com/file/d/FILE_ID/view?usp=sharing"] }
Use text, paragraphs, srtUrl, txtUrl or mdUrl from the response. Google Drive, Dropbox and OneDrive files must be shared as "anyone with the link".
n8n: paste this node into a workflow (Ctrl+V), then add a Header Auth credential with name Authorization and value Bearer <your Apify API token>. It reads the file link from the previous node's webViewLink (Google Drive trigger); change that expression for other sources.
{"nodes": [{"name": "Transcribe (Apify)","type": "n8n-nodes-base.httpRequest","typeVersion": 4.2,"position": [0, 0],"parameters": {"method": "POST","url": "https://api.apify.com/v2/acts/tidytools~audio-transcriber/run-sync-get-dataset-items","authentication": "genericCredentialType","genericAuthType": "httpHeaderAuth","sendBody": true,"specifyBody": "json","jsonBody": "={{ JSON.stringify({ urls: [$json.webViewLink] }) }}","options": { "timeout": 300000 }}}],"connections": {}}
You can also use the official Apify node (Run actor and get dataset, Actor tidytools/audio-transcriber) with the same JSON input; it waits for long runs without the 300-second limit below.
Make: add an HTTP > Make a request module:
| Field | Value |
|---|---|
| URL | https://api.apify.com/v2/acts/tidytools~audio-transcriber/run-sync-get-dataset-items |
| Method | POST |
| Headers | Authorization: Bearer <your Apify API token> |
| Body type | Raw, content type JSON (application/json) |
| Request content | { "urls": ["{{1.webContentLink}}"] } (the file link from your trigger module) |
| Parse response | Yes |
Or use Make's Apify app: Run an Actor (with Run synchronously on), then Get Dataset Items. Zapier works the same way with Webhooks by Zapier > Custom Request or the Apify app.
Ready-made n8n workflows: Google Drive recording to transcript Google Doc, and recording link (webhook or Dropbox) to transcript Doc. Import the JSON file, add your own credentials, and pick the folder.
Large files (200 MB and more) and safe retries. The file never passes through n8n or Make: send its link and the Actor streams it to disk (no memory limit in your workflow), continues an interrupted download where it stopped, and explains Drive or storage refusals (private_link, needs_login, quota_exceeded, link_expired, link_invalid) instead of transcribing an error page. Measured on 2 October 2026 with NASA press-conference videos behind signed links, at the default 1 GB memory: 100 MB / 40 min in 2.4 minutes, 250 MB / 55 min in 4.3 minutes, 500 MB / 43 min in 2.6 minutes, from start of the run to the finished transcript. Every row has sourceFileId (yours, or derived from the link) and runId. Set resumeStore (any name) and a retried workflow gets the finished transcript back without paying again (reused: true); if the first run is still going, the retry waits for it.
Retry only what may work later
Every free failure row has retryable: true when the same input may work if run again later (YouTube or the host was busy, a network error, a time limit, a Drive download quota, your spending limit), false when it will not until the input changes (no speech, private, removed, not a media file, too long for your maxMinutesPerFile). When a run has retryable failures, it saves RETRY_INPUT in its key-value store: your input with urls set to just those links (feeds, dataset and uploads removed, so nothing done is transcribed twice), and SUMMARY.retry lists them. This also holds for a run with nothing to transcribe: if your only input is a public Google Drive folder that Drive does not list right now (quota_exceeded, HTTP 429), the run ends with that free row, RETRY_INPUT holds the folder link and SUMMARY.retry lists it, so you can run it again later without pasting it again. In n8n or Make, read https://api.apify.com/v2/key-value-stores/<defaultKeyValueStoreId>/records/RETRY_INPUT and start a new run with it as the input, e.g. an hour later.
In a mixed batch, SUMMARY also says which failed files need new input and which may work later: failuresByReason counts the failed files by errorType, filesNeedNewInput is how many of them will not work until the input changes, and failureHint is the same one-line next step as in the run's status message. For the files that need new input, read each row's error and change the input (a web page instead of a media