YouTube Transcript Scraper ⚡ Bulk, No API Key
Pricing
from $5.00 / 1,000 transcript returneds
YouTube Transcript Scraper ⚡ Bulk, No API Key
Download YouTube transcripts, subtitles and captions in bulk. Pass video URLs or IDs; get plain text, timestamped segments, video title, channel, duration and view count as JSON or CSV. No YouTube API key, no quota, no cap. Failed videos say why and are never charged.
Pricing
from $5.00 / 1,000 transcript returneds
Rating
5.0
(2)
Developer
Yaniv van der Stigchel
Maintained by CommunityActor stats
3
Bookmarked
2
Total users
1
Monthly active users
13 hours ago
Last modified
Share
YouTube Transcript Scraper — bulk, no cap, LLM-ready
Extract transcripts from thousands of YouTube videos in one run. Plain text for embeddings, timestamped segments for search and clipping. No YouTube API key, no quota, no per-video rate-limit dance.
Extract YouTube transcripts in bulk
Pass a whole channel's worth of video IDs and get clean JSON back. Built for people filling a vector store or a dataset, not for copying one transcript by hand.
- Download YouTube transcripts to JSON or CSV
- Get YouTube subtitles with timestamps
- Extract transcripts from a list of YouTube URLs
- Scrape YouTube captions without a YouTube API key
- Bulk export YouTube video transcripts for AI training
Why this one works
Many transcript tools broke when YouTube started requiring proof-of-origin tokens on caption URLs — they return empty results or fail silently. This scraper uses a fallback ladder of client identities and reports which one served each result, so you get a transcript where other tools return nothing.
Honest failure reporting
Every item tells you exactly what happened, and you are only charged for transcripts actually delivered:
reason | Meaning | Charged |
|---|---|---|
— (ok: true) | Transcript returned | Yes |
no-captions | Video is fine, has no captions | No |
unavailable | Deleted, private, or region-locked | No |
blocked | Extraction route failed | No |
Feeding in a large list of older videos? Dead entries cost you nothing, and you can tell a caption-less video apart from a deleted one.
Language fallback
Ask for nl and it prefers a human-written Dutch track, falls back to
auto-generated Dutch, then to any track that exists. Most scrapers return
nothing when the exact language is missing.
Input
| Field | Required | Description |
|---|---|---|
videos | yes | Watch URLs, youtu.be links, shorts, live, embed URLs, or bare 11-character IDs |
language | no | Two-letter code. Default en. |
includeSegments | no | Include timestamps. Default true. |
maxConcurrency | no | Parallel requests, 1–25. Default 8. |
Example input
{"videos": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ","https://youtu.be/jNQXAC9IVRw","kJQP7kiw5Fk"],"language": "en","includeSegments": true}
Output
Every row has the same fields whether it succeeded or failed, so you can select columns without branching. Failed rows are never charged.
| Field | Type | Description |
|---|---|---|
success | boolean | True when a transcript was returned. Failed rows are never charged. |
videoId | string | YouTube's 11-character video identifier. |
videoUrl | string | Canonical watch URL for the video. |
title | string | The video's title as published. |
channel | string | Display name of the channel that published the video. |
channelId | string | YouTube channel identifier, stable across renames. |
durationSeconds | number | Length of the video in seconds. |
viewCount | integer | Views at the time of the run. |
language | string | BCP-47 code of the caption track returned. |
isAutoGenerated | boolean | True when the captions were machine-generated rather than human-authored. |
segmentCount | integer | Number of timed segments in the transcript. |
fullText | string | The complete transcript as one plain-text string. Null on failure. |
segments | array | Timestamped transcript segments. Only present when includeSegments is enabled. |
errorCode | string | Machine-readable failure reason. Null on success. |
errorMessage | string | Human-readable explanation of the failure. Null on success. |
Example — success
{"success": true,"videoId": "jNQXAC9IVRw","videoUrl": "https://www.youtube.com/watch?v=jNQXAC9IVRw","title": "Me at the zoo","channel": "jawed","channelId": "UC4QobU6STFB0P71PMvOGN5A","viewCount": 358194821,"durationSeconds": 19,"language": "en","isAutoGenerated": false,"segmentCount": 3,"fullText": "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.","errorCode": null,"errorMessage": null,"segments": [{"start": 0.84,"duration": 3.2,"text": "All right, so here we are, in front of the elephants."},{"start": 4.04,"duration": 3.6,"text": "The cool thing about these guys is that they have"},{"start": 7.64,"duration": 2.9,"text": "really, really, really long trunks."}]}
Example — failure
A failure still identifies the video, so you can act on it without a second lookup.
{"success": false,"videoId": "s1CFmzZzO4c","videoUrl": "https://www.youtube.com/watch?v=s1CFmzZzO4c","title": "Board meeting recording","channel": "Example Corp","channelId": "UCzzz999","viewCount": 412,"durationSeconds": 3120,"language": null,"isAutoGenerated": null,"segmentCount": null,"fullText": null,"errorCode": "no-captions","errorMessage": "This video has no captions published, so there is no transcript to return."}
Error codes
no-captionsunavailableblockedempty-transcriptunparseable-inputerror
Use it for
- YouTube transcript JSON — structured output for pipelines, not a caption file
- Podcast transcripts — YouTube-hosted episodes in bulk
- Video transcript bulk export — thousands of videos in a single run
- Interview transcripts — podcast and long-form interview episodes
- Webinar and meeting transcripts — anything recorded and posted to YouTube
- Earnings call transcripts — investor calls published on a company channel
- RAG and embedding pipelines, semantic video search
- Accessibility, subtitles and content repurposing
- Competitor content analysis and training datasets
Related actors
| If you need | Use |
|---|---|
| Every video on a channel | YouTube Channel Transcript Scraper |
| Every video in a playlist | YouTube Playlist Transcript Scraper |
| A channel's Shorts | YouTube Shorts Transcript Scraper |
| Videos you have not picked yet, from a keyword | YouTube Search to Transcripts |
| Comments instead of the spoken text | YouTube Comments Scraper |
Pricing
Pay per transcript returned. Videos without captions, deleted videos, and failed fetches cost nothing.
Use it from an AI agent (MCP)
This Actor is callable as a tool through the Apify MCP server, so Claude, ChatGPT, Cursor and VS Code can run it directly.
Add the server to your MCP client:
{"mcpServers": {"apify": {"url": "https://mcp.apify.com","headers": {"Authorization": "Bearer <YOUR_APIFY_TOKEN>"}}}}
Then ask for what you want in plain language — for example “get the transcript of this YouTube video” — and the agent calls cleanfeed/youtube-transcript-downloader with the right input. Every output field is described in the dataset schema, so the agent knows what it is getting back before it runs anything.
Call it from code
Python
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("cleanfeed/youtube-transcript-downloader").call(run_input={"videos": ["https://www.youtube.com/watch?v=jNQXAC9IVRw"],})for item in client.dataset(run["defaultDatasetId"]).iterate_items():if item["success"]:print(item)
JavaScript
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('cleanfeed/youtube-transcript-downloader').call({videos: ["https://www.youtube.com/watch?v=jNQXAC9IVRw"],});const { items } = await client.dataset(run.defaultDatasetId).listItems();console.log(items.filter((i) => i.success));
cURL
curl -X POST "https://api.apify.com/v2/acts/cleanfeed~youtube-transcript-downloader/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \-H 'Content-Type: application/json' \-d '{"videos": ["https://www.youtube.com/watch?v=jNQXAC9IVRw"]}'
Limitations
- A residential proxy is required. YouTube serves
playabilityStatus: ERRORto datacenter IP ranges while the identical request succeeds from a home connection — measured 12/12 success on residential against 0/12 on a cloud host with no proxy, and 7/12 through a datacenter proxy. The input defaults to residential; changing it will break most runs. Full method and per-environment figures are published in the reliability benchmark. - Not every video has captions. Videos with captions disabled return
errorCode: no-captionsand are never charged. This is a property of the video, not a failure of the run. - Captions are read, not generated. There is no speech-to-text fallback — if YouTube publishes no caption track, no transcript exists to return.
FAQ
Do I need a YouTube API key?
No. This does not use the YouTube Data API, so there is no key, no OAuth and no daily quota. The official API also cannot return auto-generated captions at all, which is most of what exists.
Why does my own script work locally but fail on a server?
YouTube blocks datacenter IP ranges. The same request that succeeds from your laptop returns playabilityStatus: ERROR from AWS, GCP or Azure. This Actor routes through residential egress by default, which is the fix. There is a symptom-by-symptom walkthrough in this diagnostic guide.
What happens to videos without captions?
They return errorCode: no-captions with a readable errorMessage, and are never charged. Failed rows carry the same fields as successful ones, so nothing downstream has to branch.
Can I get timestamps?
Yes — set includeSegments: true and each row gains a segments array of { text, start, duration }. It is off by default because it multiplies payload size.
Is there speech-to-text for videos with no captions?
No. This reads the caption tracks YouTube publishes. If a video has none, there is nothing to return and you are not charged for it.
Notes
Only publicly available caption data is collected. No login, no personal data, no video downloads.