YouTube Transcript Scraper
Pricing
from $2.50 / 1,000 transcript returneds
YouTube Transcript Scraper
Extract YouTube video transcripts (manual + auto-generated captions) with timestamps and full plain text from video URLs or IDs. No login, no API key, no browser. Pay only per transcript returned. CSV/JSON.
YouTube Transcript Scraper (Pay Per Transcript)
Turn a list of YouTube video URLs into clean, timestamped transcripts — manual and auto-generated captions, in every language the video publishes. You pay only when a transcript is actually returned. No transcript, no charge.
Feed the actor video URLs (any form: watch?v=, youtu.be, Shorts, embeds, live links) or bare video ids. It resolves each video through the same public player endpoint every signed-out YouTube client calls, reads the caption track list, fetches the transcript for each selected language, and returns timestamped segments plus the full plain text — signed out, no login, no API keys, no browser.
- 📝 Timestamped segments —
{ start, dur, text }per caption cue (seconds), plus the whole transcript as one plain-text string - 🌍 Every published language — or only the codes you ask for (
en,de,pt-BR, …) - 🤖 Manual + auto-generated — auto-generated (speech-recognition) tracks are flagged
isAutoGenerated: trueand can be excluded - 📊 Video metadata on every record: title, channel, video length, and the full list of available caption languages
- 💰 Pay-per-result — priced per transcript returned, not per subscription. A video with no captions costs you nothing.
🎯 Best for
Self-identify in two seconds — this actor is built for:
- LLM / RAG pipelines — turn channels' videos into clean text for embedding, summarization, or fine-tuning corpora.
- Content repurposing — pull the transcript of your own or a source video to draft articles, newsletters, or show notes.
- Research & media monitoring — search what was actually said across a list of videos, with timestamps to jump to.
- Subtitle & localization workflows — grab the existing caption tracks (and see which languages exist) before translating.
- SEO & competitive analysis — analyze the full spoken content of ranking videos, not just titles and descriptions.
🔒 What makes this scraper different (and safer)
It runs signed out and reads only public surfaces — the public YouTube player endpoint (the JSON API the YouTube site itself calls before you ever log in) and the signed public timedtext URLs it returns. There is no login, no cookie, no CAPTCHA, no user API key, and no browser.
Where the transcripts come from
Public surfaces, signed out:
- the video's caption track list from the public player endpoint — every manual and auto-generated track the video publishes, and
- each track's timedtext URL — the same URL the YouTube player fetches to render captions on screen.
What it deliberately does NOT do
- ❌ Does not log into anything and does not touch private, unlisted-restricted, or members-only data. A video whose captions aren't publicly served simply comes back as an honest error row.
- ❌ Does not fabricate transcripts. There is no speech-to-text model in this actor — it returns exactly the caption tracks YouTube publishes, flagged manual vs auto-generated. If a video has no captions at all, you get
no_captions_available, not a guess.
🎬 Scope & limits (read this)
- Captions only. A video with captions disabled (music videos and animations are common cases) has no public transcript surface — it returns a free
status: "error"row witherrorDetail: "no_captions_available". That's an honest scope limit, not a bug. - Auto-generated quality is YouTube's.
isAutoGenerated: truetracks are YouTube's own speech recognition — mis-hearings, missing punctuation, and[Music]markers included. Manual tracks are whatever the uploader published. - No auto-translation. The actor returns the tracks the video actually publishes. Requesting a language the video doesn't have yields
no_matching_caption_track(with the available languages listed on the record) rather than a machine translation. - Age-restricted / region-locked / private videos may be unplayable signed out; they come back as
video_unavailable (…)error rows — free, never charged.
⚙️ How it works
Plain HTTP — no browser, no key, no login:
- Extract the video id from each input URL or bare id (
watch?v=,youtu.be/,/shorts/,/embed/,/live/all supported), deduped run-wide. - Resolve the video via the public player endpoint (
youtube.com/youtubei/v1/player) → title, channel, length, playability, and the caption track list. - Select tracks: your
languagesfilter (empty = all available), theincludeAutoGeneratedswitch, andpreferManual(when a language has both a manual and an auto track, return only the manual one so you aren't billed twice for near-identical content). - Fetch each track's signed timedtext URL and parse the response — all three of YouTube's transcript wire formats (srv3 XML, classic XML, json3) are handled by a dependency-free parser.
- Emit one record per transcript with segments, plain text, word/segment counts, and the full list of available caption languages.
Built-in resilience (shared with the maintainer's other scrapers):
- up to 3 retries per request with backoff, and a body-download-safe timeout,
- a response-size cap so a runaway response can't exhaust memory,
- run-global de-duplication by video id, so the same video submitted twice (or as URL + id) is fetched, charged, and emitted once,
- honest error output — an unavailable video, a caption-less video, or a failed track fetch is written to the dataset as
{ "status": "error", "errorDetail": "…" }rather than silently dropped or crashing the run.
▶️ How to use (no code required)
- Create a free Apify account.
- Open this actor and paste video links into Video URLs (or bare ids into Video IDs).
- (Optional) restrict Languages (e.g.
en), toggle Include auto-generated captions, and choose whether you want segments, plain text, or both. - Click Start.
- Watch rows appear in the Output tab, then export the dataset as JSON, CSV, Excel, XML, or HTML — or pull it via the API.
📥 Input
| Field | Type | Default | Notes |
|---|---|---|---|
videoUrls | array of strings | [] | Video URLs in any common form — watch?v=…, youtu.be/…, /shorts/…, /embed/…, /live/… — or bare 11-char ids. |
videoIds | array of strings | [] | Bare 11-character video ids. Merged with videoUrls and deduped. |
languages | array of strings | [] | Caption language codes to return. A base code matches regional variants (en matches en-US). Empty = every language the video publishes. |
includeAutoGenerated | boolean | true | Include YouTube's auto-generated (speech-recognition) tracks. |
preferManual | boolean | true | When a language has both manual and auto-generated tracks, return only the manual one. |
includeSegments | boolean | true | Emit the timestamped segments array. |
includeFullText | boolean | true | Emit the joined plain-text transcript string. |
Example input
{"videoUrls": ["https://www.youtube.com/watch?v=jNQXAC9IVRw","https://youtu.be/dQw4w9WgXcQ"],"languages": ["en"],"includeAutoGenerated": true,"preferManual": true,"includeSegments": true,"includeFullText": true}
📤 Output
One record per transcript (one video × one caption track), written to the dataset as results come in. A video with three published languages and no language filter yields three records. Export as JSON, CSV, Excel, XML, or HTML, or read it through the API.
{"videoId": "jNQXAC9IVRw","videoUrl": "https://www.youtube.com/watch?v=jNQXAC9IVRw","title": "Me at the zoo","channelName": "jawed","lengthSeconds": 19,"language": "en","languageName": "English","isAutoGenerated": false,"segments": [{ "start": 1.2, "dur": 2.16, "text": "All right, so here we are, in front of the elephants" }],"transcript": "All right, so here we are, in front of the elephants the cool thing about these guys is that they have really... really really long trunks and that's cool (baaaaaaaaaaahhh!!) and that's pretty much all there is to say","segmentCount": 6,"wordCount": 39,"charCount": 217,"availableLanguages": [{ "language": "en", "languageName": "English", "isAutoGenerated": false },{ "language": "de", "languageName": "German", "isAutoGenerated": false }],"requestedLanguages": ["en"],"sourceInput": "https://www.youtube.com/watch?v=jNQXAC9IVRw","scrapedAt": "2026-07-15T00:00:00.000Z","status": "ok","errorDetail": null}
Field notes:
segments— one{ start, dur, text }object per caption cue; times in seconds. Omitted whenincludeSegmentsis off.transcript— the whole transcript joined into one whitespace-normalized string. Omitted whenincludeFullTextis off.isAutoGenerated—truefor YouTube's speech-recognition tracks,falsefor uploader-published captions.availableLanguages— every caption track the video publishes, whether or not it was requested — so ano_matching_caption_trackerror row still tells you what is available.status—"ok"for a returned transcript,"error"otherwise, with a human-readableerrorDetail:invalid_video_url_or_id,video_unavailable (…),no_captions_available,no_matching_caption_track,transcript_fetch_failed (…), ortranscript_empty (…). Error rows are kept, not dropped — and never charged.
💰 Pricing (pay-per-event)
You are charged a fixed price per result event — not for platform usage or compute time.
| Event | Price | Per 1,000 | When you're charged |
|---|---|---|---|
transcript-returned | $0.0025 | $2.50 / 1,000 | Once per transcript actually returned (one video × one caption track, status: "ok"). A video with no captions is free. |
No-result-free is the core trust mechanic: you never pay for a video that yields nothing — no captions, unavailable, bad URL, or a failed fetch all come back as free status: "error" rows, never as a phantom charge for data that does not exist.
What a run costs — a worked example: you submit 300 video URLs with languages: ["en"]; 250 have an English track and return one transcript each → 250 × $0.0025 = $0.625. The 50 videos with no captions (or unavailable) cost nothing. Total: $0.625.
The price above is exactly what you're billed — nothing for platform usage or compute on top. Comparable Store actors price differently; compare the live listings and pick what fits your volume.
❓ FAQ
Do I pay for videos that have no captions?
No. You are charged only when a transcript is actually returned (status: "ok"). A caption-less video, an unavailable video, an invalid URL, or a failed fetch costs you nothing.
One video came back as several records — why?
The record grain is one transcript, i.e. one video × one caption track. With languages empty (the default) you get every language the video publishes. Set languages: ["en"] if you only want one.
Does it translate captions into languages the video doesn't have?
No. It returns only tracks the video actually publishes — auto-translation is deliberately out of scope (see Roadmap). The availableLanguages field on every record shows what exists.
Are auto-generated transcripts accurate?
They are YouTube's own speech recognition, returned verbatim and flagged isAutoGenerated: true — expect mis-hearings and missing punctuation, especially with music or accents. Prefer manual tracks when they exist (preferManual does that automatically).
Can I test with a small list first? Yes. Start with two or three URLs, confirm the output looks right, then scale up. Each transcript is billed individually, so a small test costs cents.
Can I run it on a schedule or from my own code?
Yes. Apify lets you run this actor from the Console UI, schedule it, call it via the Apify API, drive it with the apify-client SDKs for JavaScript and Python, run it from the Apify CLI, or connect it through an MCP server — and wire the output into Make, Zapier, Slack, Google Sheets, or a webhook.
Is it legal to scrape this? The actor reads only publicly available data, signed out — the same caption files YouTube serves to any visitor. You are responsible for how you use the content: transcripts may be copyrighted by the video's creator, so make sure your use (quotation, research, fair use, licensed reuse) is legitimate, and comply with applicable law and YouTube's terms. If unsure, consult your own legal counsel.
What happens when a fetch fails mid-run?
The request is retried up to 3 times with backoff, then written to the dataset as a { "status": "error", "errorDetail": "…" } row. One bad video never crashes the run — you get an honest record of what failed and are not charged for it.
🔌 API-ready
This actor is a drop-in piece of a pipeline. Apify auto-generates integration snippets for it — JavaScript (apify-client), Python (apify-client), the Apify CLI, the REST API, an OpenAPI spec, and an MCP server — so you can call it from your own stack or an agent workflow. See the API tab on the actor page.
Maintainer
Published by Jaxx. Categories: Videos · AI & LLM tools.
Found a video that should have parsed but didn't, or a field you'd like added? Open an issue on the actor's Issues tab — bug reports and extraction edge cases are actively reviewed.