YouTube Transcript Scraper & API - Transcripts + Comments avatar

YouTube Transcript Scraper & API - Transcripts + Comments

Pricing

from $2.00 / 1,000 transcripts

Go to Apify Store
YouTube Transcript Scraper & API - Transcripts + Comments

YouTube Transcript Scraper & API - Transcripts + Comments

YouTube transcript API and scraper: get transcripts, captions and subtitles in the language you choose from videos, channels, playlists or search, plus top comments. Timestamped segments, text or markdown chunks for AI, RAG, n8n and MCP. No-caption videos are free. $0.80 per 1,000 transcripts.

Pricing

from $2.00 / 1,000 transcripts

Rating

0.0

(0)

Developer

Deependra Singh

Deependra Singh

Maintained by Community

Actor stats

0

Bookmarked

3

Total users

2

Monthly active users

14 days ago

Last modified

Categories

Security

Limited permissions

Share

YouTube Transcript Scraper & API

Get the transcript (captions and subtitles) of any public YouTube video, channel, playlist or search, in the language you choose, with views, likes and top comments, ready for Google Sheets, ChatGPT or your own code.

$2 per 1,000 transcripts, $0.50 per 1,000 comments, no start fee. Videos without captions are free.

Who is it for?

  • YouTube creators and marketers: get the transcripts of a competitor's last 50 videos with views, likes and top comments, and find the topics and hooks that worked.
  • Students and researchers: turn a lecture playlist into plain-text notes in English, Spanish, Hindi or any other language the video has captions in.
  • Knowledge bases for ChatGPT, Claude or NotebookLM: get transcripts as Markdown sections with a timestamp and a link to the exact moment in the video.
  • SEO and content teams: search YouTube for a keyword and read what the top videos say.
  • Developers and AI agents: a YouTube transcript API for code, n8n, Make, Zapier and MCP (Claude, Cursor), without managing proxies or blocked IPs.

How to use it in 3 steps

  1. Fill in the form. Open the Actor in Apify Console (a free Apify account comes with monthly free credit, enough for thousands of transcripts). The Input tab shows a form. Paste a channel address into Channels (for example https://www.youtube.com/@mkbhd), video links into Video links, playlist links into Playlists, or words into Search words. The form starts with one example video; remove it if you only want your own channels, playlists or searches, or its transcript is added (and charged) too.
  2. Choose the details. Set Videos per channel (for example 50), your Transcript languages (en for English, es for Spanish, hi for Hindi) and the Transcript format: Plain text for spreadsheets, Markdown sections for ChatGPT or Claude. Turn on Include comments if you want them.
  3. Click Start, then open the results. The green Start button is at the bottom of the form. When the run finishes (1 to 2 minutes for 50 videos), the Output tab shows the results in tabs: Overview (views, likes, length, language), Transcripts, Markdown and Comments. Click Export to download them as CSV, Excel or JSON.

Recipe: a competitor's last 50 videos in Google Sheets

  1. Channels: https://www.youtube.com/@aliabdaal (copy the address from your browser while you are on the channel page). Remove the example video.
  2. Videos per channel: 50. Transcript format: Plain text. Include comments: on. Comments per video: 5.
  3. Click Start. It costs about $0.20 (see "How much does it cost?").
  4. In the Output tab, open the Overview or Transcripts tab, click Export, choose CSV and download the file.
  5. In Google Sheets: File > Import > Upload, pick the file, Insert new sheet.
  6. Sort by Position (1 = newest video) or by Views. Rows can arrive in any order, because videos are fetched in parallel. In the downloaded file the columns have their API names: source.position (Position), viewCount (Views), likeCount (Likes), durationSeconds (Length), publishedAt (Published), transcriptText (Transcript).

Good to know for spreadsheets:

  • Google Sheets holds at most 50,000 characters in one cell, and Excel at most 32,767. Speech is about 1,300 characters per minute, so transcripts of videos longer than about 38 minutes (25 minutes for Excel) are cut off. For long videos, choose the Markdown sections format and export the Markdown tab (one short section per row), or download JSON.
  • Export a tab, not "All fields". The full export turns every list into many columns (for example availableLanguages/0/code, comments/0/text), which gives 100+ columns. The tabs above have only the useful columns. In the Export dialog you can also pick the columns you want.
  • Dates: Published is either a date (2026-03-19) or a date with time and time zone (2026-03-26T08:00:24-07:00), which Google Sheets does not read as a date. Use =LEFT(A2,10) in a new column to get a sortable date.
  • Length is in seconds (1299 = 21 min 39 s). In Sheets, =A2/86400 formatted as Format > Number > Duration shows it as 0:21:39.
  • Comments and titles are other people's text. A cell that starts with =, +, - or @ can run as a formula. In Google Sheets, set Convert text to numbers, dates and formulas to No in the import dialog; in Excel use Data > From Text/CSV and keep the text columns as Text.

Input

The form fields, with the name used in the API (JSON) in brackets.

Form field (API name)What to enterDefault
Video links (videos)Video links, one per line. Share links, Shorts and live links work.one example video
Channels (channels)Channel addresses or handles, e.g. https://www.youtube.com/@mkbhd or @mkbhdempty
Videos per channel (maxVideosPerChannel)How many of the newest videos to take from each channel10
Playlists (playlists)Playlist links (https://www.youtube.com/playlist?list=PL...)empty
Videos per playlist (maxVideosPerPlaylist)How many videos from the top of each playlist50
Search words (searchQueries)One search per line, e.g. langchain tutorialempty
Videos per search (maxResultsPerSearch)How many top videos per search10
Transcript languages (languages)Language codes, best first: en English, es Spanish, hi Hindi, pt-BR Portuguese (Brazil)en
Transcript format (outputFormat)Plain text (text), Markdown sections (markdown), text + timestamped lines (both), timestamped lines only (segments)Plain text
Timestamps in plain text (includeTimestamps)One [mm:ss] text line per caption instead of one paragraphoff
Extra video details (includeMetadata)Exact publish time, likes, category and @handle (title, channel, views and length always come)on
Include comments (includeComments)Also get the top comments of each videooff
Comments per video (maxCommentsPerVideo)How many comments per video (up to 1,000)20
Channel tab (channelTab)Videos (videos), Shorts (shorts) or past live streams (streams)Videos
Search order (searchSort)Relevance (relevance) or most viewed (popularity)Relevance
Uploaded within (searchUploadDate)Only recent search results: hour, today, week, month, yearAny time
If a video is not in your languages (languageFallback)Use the spoken language (original), any language (any), or skip the video (none)spoken language
Allow automatic captions (includeAutoGenerated)Use YouTube's automatic captions when there are no human-made oneson
Leave out videos without captions (skipVideosWithoutCaptions)Do not add rows for videos without a transcriptoff
Comment order (commentsSort)Top comments (top) or newest first (newest)Top
Markdown section size (chunkCharacters)Rough length of each Markdown section, in characters1500
Max videos in total (maxItems)Safety limit across all inputs (videos without captions count too). The old name maxVideos still works.1000
Parallel videos (maxConcurrency)No need to change10
Time limit per video (videoTimeoutSecs)No need to change. One attempt at one video may take this long before it is retried120
Retries per video (maxRetries)No need to change. Retries after a rate limit (HTTP 429), timeout or network error, after a growing pause6
Proxy (proxyConfiguration)Leave as is. Included in the price. Optionally pick a country to scrape as that country (see Known limits).Apify residential

The same recipe as JSON (for the API, n8n or Make):

{
"channels": ["https://www.youtube.com/@aliabdaal"],
"maxVideosPerChannel": 50,
"languages": ["en"],
"outputFormat": "text",
"includeComments": true,
"maxCommentsPerVideo": 5
}

Only Hindi, nothing else (first 5 videos of a playlist): in the form, set Transcript languages to hi and If a video is not in your languages to Skip the video. Otherwise a video spoken in another language (for example Bengali) comes back in that language and is charged like any transcript.

{
"playlists": ["https://www.youtube.com/playlist?list=PL-CAJsjanTBu7DQydZH8to-Xb3rUO6AoZ"],
"maxVideosPerPlaylist": 5,
"languages": ["hi"],
"languageFallback": "none",
"outputFormat": "text"
}

Markdown for a ChatGPT or Claude knowledge base (latest 3 videos):

{
"channels": ["https://www.youtube.com/@AhrefsCom"],
"maxVideosPerChannel": 3,
"outputFormat": "markdown"
}

Search, this month's videos, 30 newest comments each:

{
"searchQueries": ["langchain tutorial"],
"maxResultsPerSearch": 10,
"searchUploadDate": "month",
"includeComments": true,
"maxCommentsPerVideo": 30,
"commentsSort": "newest"
}

Output

One row per video. The Overview tab looks like this (real values from runs on 2026-10-19):

PositionTitlePublishedViewsLikesLength (seconds)WordsLanguageStatus
26How I'd Create Content in 2026 (If I Had To Start Over)2026-03-26T08:00:24-07:00358,72410,9689554,417English (auto-generated)ok
27The psychology of making money2026-03-19136,6474,0181,2995,743English (auto-generated)ok
Rick Astley - Never Gonna Give You Up (Official Video) (4K Remaster)2009-10-24T23:57:33-07:001,828,063,13719,488,185213366Englishok

The tabs also show the video link, the channel, the transcript (Transcripts tab), each Markdown section with a link to that moment (Markdown tab) and each comment with author, likes and replies (Comments tab, one comment per row).

Status tells you what happened to each video:

StatusIn plain wordserrorCodeCharged
okTranscript deliverednullYes
no_captionsThe video has no subtitles on YouTube, not even automatic ones (or only automatic ones and you turned them off)NO_CAPTIONS, AUTO_CAPTIONS_EXCLUDED, EMPTY_CAPTION_TRACKNo
language_not_availableThe video has subtitles, but not in your languages, and you chose "Skip the video"LANGUAGE_NOT_AVAILABLENo
unavailablePrivate, deleted, members-only, age-restricted, not available in the proxy's country, or live right nowVIDEO_UNAVAILABLE, LOGIN_REQUIRED, LIVE_STREAM, GEO_RESTRICTEDNo
errorYouTube refused access (bot check), kept rate-limiting, or the connection failed after several retriesBLOCKED, RATE_LIMITED, CAPTIONS_NEED_PO_TOKEN, TIMEOUT, NETWORK_ERROR, HTTP_ERROR, UNKNOWN_ERRORNo

errorMessage has the reason in words (YouTube's own text for unavailable videos). A video never comes back as an empty "success": if there is no transcript, transcriptAvailable is false and errorCode says why. If comments fail on a video that has a transcript, the transcript row is still ok and commentsError explains what happened to the comments.

Language chosen (languageMatch) shows how the transcript language was picked: preferred-manual or preferred-auto = one of your languages (human-made or automatic captions), original = not in your languages, so the language spoken in the video was used, any = the first language the video has (also shown when you chose the spoken language but YouTube cannot tell it, for example a video without automatic captions: then the first human-made captions are used).

One real row in JSON (local run of 2026-10-24, input {"videos": ["dQw4w9WgXcQ"], "includeTimestamps": true, "includeComments": true, "maxCommentsPerVideo": 5}; long text and lists shortened, commenter replaced by a placeholder):

{
"videoId": "dQw4w9WgXcQ",
"videoUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"status": "ok",
"errorCode": null,
"errorMessage": null,
"title": "Rick Astley - Never Gonna Give You Up (Official Video) (4K Remaster)",
"channelName": "Rick Astley",
"channelId": "UCuAXFkgsw1L7xaCfnd5JJOw",
"channelHandle": "@RickAstleyYT",
"channelUrl": "https://www.youtube.com/@RickAstleyYT",
"publishedAt": "2009-10-24T23:57:33-07:00",
"durationSeconds": 213,
"viewCount": 1829759604,
"likeCount": 19493269,
"description": "The official video for “Never Gonna Give You Up” by Rick Astley. ...",
"keywords": ["rick astley", "Never Gonna Give You Up", "nggyu"],
"category": "Music",
"isLive": false,
"thumbnailUrl": "https://i.ytimg.com/vi/dQw4w9WgXcQ/sddefault.jpg",
"language": "en",
"languageName": "English",
"transcriptType": "manual",
"transcriptAvailable": true,
"languageMatch": "preferred-manual",
"availableLanguages": [
{
"code": "en",
"name": "English",
"isAutoGenerated": false
},
{
"code": "en",
"name": "English (auto-generated)",
"isAutoGenerated": true
},
{
"code": "de-DE",
"name": "German (Germany)",
"isAutoGenerated": false
},
{
"code": "ja",
"name": "Japanese",
"isAutoGenerated": false
}
],
"transcriptText": "[00:01] [♪♪♪]\n[00:18] ♪ We're no strangers to love ♪\n[00:22] ♪ You know the rules and so do I ♪\n...",
"segments": null,
"segmentCount": 61,
"chunks": null,
"wordCount": 366,
"comments": [
{
"commentId": "UgyEnXfdC-umwvTt8JF4AaABAg",
"text": "Gonna flag this for nudity so I can rick roll the YouTube staff",
"author": "@example-user",
"authorChannelId": "UC...",
"isCreator": false,
"isPinned": false,
"likeCount": 569000,
"replyCount": 723,
"publishedTimeText": "6 years ago"
}
],
"commentCount": 2460372,
"commentsDisabled": false,
"commentsError": null,
"source": {
"type": "video",
"input": "dQw4w9WgXcQ",
"position": null
},
"collectedAt": "2026-10-23T20:37:27.579Z"
}

Rows without a transcript from the same run (other fields shortened):

{"videoId":"AAAAAAAAAAA","status":"unavailable","errorCode":"VIDEO_UNAVAILABLE","errorMessage":"This video is unavailable","transcriptAvailable":false,"transcriptText":null,"segmentCount":null}
{"videoId":"8c7KOGxeY3w","status":"no_captions","errorCode":"NO_CAPTIONS","errorMessage":"video has no captions","transcriptAvailable":false,"transcriptText":null,"segmentCount":null}

Every row has the same fields; fields that do not apply to your format are null. More examples (timestamped lines, Markdown sections, comments) are in "For developers" below.

How much does it cost?

You pay only for what you get. Apify platform and proxy costs are included. There is no start fee.

WhatPrice
Transcript (one video with status ok, any length)$2 per 1,000 ($0.002 each)
Comment (one delivered comment)$0.50 per 1,000 ($0.0005 each)
Videos without captions, unavailable, failed or found twiceFree

Examples:

  • 1 video: $0.002.
  • The first 5 lectures of a playlist: $0.01.
  • A channel's last 50 videos with 5 comments each: 50 × $0.002 + 250 × $0.0005 = $0.10 + $0.125 = $0.225.
  • 100 videos with 20 comments each: $0.20 + $1.00 = $1.20.
  • 1,000 transcripts: $2. 10,000 transcripts: $20.
  • A 3-hour podcast costs the same as a 3-minute video: $0.002.

Comments are often most of the bill: 5 comments cost more than the transcript of their video. Keep Comments per video low if you only want a taste. To cap the bill, open Run options (next to the Start button) and set Maximum cost per run; the Actor stops cleanly when it reaches the limit.

Integrations

  • Google Sheets: the simplest way is the recipe above (Export CSV, then File > Import). To send results to a sheet automatically after each run, use the Integrations tab of the Actor with a Google Sheets step in Make or Zapier, or the "Google Sheets Import & Export" Actor from Apify Store.
  • Make and Zapier: add the Apify app, choose Run an Actor, pick this Actor, paste a JSON input from above, then add Get dataset items and send the rows to Google Sheets, Notion, Airtable or email.
  • n8n: add an HTTP Request node: method POST, URL https://api.apify.com/v2/acts/superxcoder~youtube-transcripts-scraper/run-sync-get-dataset-items, header Authorization: Bearer <your Apify API token>, JSON body like {"videos": ["{{ $json.videoUrl }}"], "outputFormat": "text"}. It waits up to 5 minutes and returns one item per video. For bigger jobs use the Apify node's "Run Actor" and "Get dataset items".
  • Claude, ChatGPT, Cursor and other AI agents (MCP): add https://mcp.apify.com/?actors=superxcoder/youtube-transcripts-scraper as an MCP server in Claude Desktop, Claude Code, Cursor or VS Code and sign in with your Apify account. Then ask "get the transcript of this video in Spanish" or "the last 5 videos of @channel as Markdown".
  • API: see "For developers" below. Your API token is in Apify Console under Settings > API & Integrations.

FAQ

Is it legal to scrape YouTube transcripts? The Actor reads only public data that YouTube shows to any visitor without logging in. It uses no account, cookies or login. Comment rows hold only the author's public name or handle and channel ID. You are responsible for how you use the data: respect YouTube's terms, the copyright in the transcripts, and privacy laws such as GDPR when you store or publish comments. If unsure, ask a lawyer.

Why did I get empty results or "no_captions"? Many videos, especially Shorts, music and non-English lectures, have no subtitles at all, not even automatic ones. Those rows say no_captions and are free. language_not_available means the video has subtitles, but not in your languages, and you chose to skip such videos. Add more languages, or choose "Use the language spoken in the video".

I asked for Hindi and got Bengali. Why? With the default If a video is not in your languages: Use the language spoken in the video, a video without Hindi captions comes back in its own language and is charged. The Language and Language chosen columns show this. Choose Skip the video to get only your languages.

"Channel not found". What now? The handle must match YouTube exactly (for example @AhrefsCom, not @ahrefs). Open the channel on YouTube and copy the address from your browser. If no channel, playlist or search could be read and there are no video links, the run fails and nothing is charged. If other inputs worked (for example the prefilled example video), the run succeeds and those videos are charged; the log and RUN-SUMMARY (problems) name the channel that was not found.

The log shows warnings like "HTTP 429" or "Sign in to confirm you're not a bot". Is it broken? No. A rate limit (HTTP 429) is retried after a pause. A bot check is YouTube refusing access to that video: the Actor accepts the refusal, does not retry it, and returns the video as error with errorCode: "BLOCKED", free of charge. On Apify this is rare: our cloud tests saw a few bot checks per 100 videos. What counts is the run status (Succeeded) and the Status column of each video. Run the blocked videos again later if you need them.

Why are the rows not in order? Videos are fetched in parallel, so rows arrive in any order. Sort by Position (1 = newest in a channel, first in a playlist or search) or by Published.

The first comment is the creator's own promo link. Can I skip it? Not yet. The creator's pinned comment is usually first in "Top comments" and counts towards Comments per video. The Comments tab marks it with Pinned and By creator, so you can filter it out in your sheet.

Can it translate transcripts? No. YouTube blocks its machine-translated captions for automated tools, so translation is not offered. You get the languages the video really has; translate the text with ChatGPT, DeepL or Google Translate if you need to.

How do I run it every day or every week? In Apify Console, click Schedule (or go to Schedules > Create new), choose how often, and pick this Actor with your input. Combine it with the Google Sheets or Make integration to get new transcripts automatically.

Does it work with Shorts and live streams? Yes. Paste Shorts links as videos, or set Channel tab to Shorts or Past live streams. A stream that is live right now has no transcript yet (unavailable).

Are comment replies included? No. You get top-level comments (top or newest first, up to 1,000 per video) with each comment's reply count.

Known limits

What this Actor can and cannot get, without a YouTube login (it never uses one):

  • Public videos only. Private, members-only and age-restricted videos need a signed-in account, so they come back as unavailable (LOGIN_REQUIRED or VIDEO_UNAVAILABLE) and are free.
  • Only captions that exist on YouTube. The Actor never makes up a transcript, never summarises with AI, and never runs speech-to-text. A video without captions is no_captions (free). Many Shorts and music videos have none.
  • No translated captions. YouTube's machine translation (tlang) needs a "PO token" that YouTube only gives to its own web player, so it is not offered. You get the languages the video really has.
  • Live streams have no transcript while they are live (LIVE_STREAM); past streams work once YouTube has made their captions.
  • Regions. YouTube decides availability by the country of the proxy IP. By default no proxy country is set, so Apify picks residential IPs in any country, and a video its uploader limited to some countries can come back unavailable with errorCode: "GEO_RESTRICTED" (free). The Actor never retries such a video from another country. In our 100-video test, 7 of 100 search results were GEO_RESTRICTED. To get the videos available in one country, choose that country under Proxy (for example United States); the Actor then uses only that country.
  • Bot checks are not bypassed. When YouTube asks for a bot check, shows a captcha page or answers HTTP 403, the Actor stops on that video: no retry, no new IP, no other app client, no captcha solving, no login. The video gets a free error row with errorCode: "BLOCKED". Likewise, caption tracks that need YouTube's PO token come back as CAPTIONS_NEED_PO_TOKEN (free).
  • Up to 5,000 videos per channel or playlist, 500 per search, 1,000 comments per video (no replies) and 100,000 videos per run.
  • Speed: about 40 to 50 videos per minute on Apify (measured: 100 videos in 129 to 171 seconds). A run that reaches its Timeout stops a few seconds early, keeps every finished row and says how many found videos were left; only finished rows are charged.
  • Search returns regular videos only and cannot sort by newest; use Uploaded within instead.
  • Max videos in total counts every video the run tries, including free ones without captions. To cap what you pay, use Maximum cost per run.
  • Comment like counts come from YouTube's short form ("291K"), so large numbers are approximate.
  • YouTube can change. The Actor uses the same app interfaces as YouTube's iPhone and Android apps. If YouTube starts asking those apps for a PO token too, captions stop working until the Actor is updated; the run then fails with a clear message instead of returning empty rows, and nothing is charged.

For developers

Output formats

  • text: transcriptText is the whole transcript as one string (with includeTimestamps: true, one [mm:ss] text line per caption).
  • segments: segments is a list of caption lines {startSeconds, durationSeconds, text}, in time order.
  • both: segments and transcriptText.
  • markdown: chunks is a list of sections of about chunkCharacters characters, each ending on a caption line, with a ### [mm:ss] Title (part n) heading and a link to that moment.

Timestamped lines (format both or segments):

[
{ "startSeconds": 1.36, "durationSeconds": 1.68, "text": "[♪♪♪]" },
{ "startSeconds": 18.64, "durationSeconds": 3.24, "text": "♪ We're no strangers to love ♪" },
{ "startSeconds": 22.64, "durationSeconds": 4.32, "text": "♪ You know the rules and so do I ♪" }
]

Markdown sections (format markdown, chunkCharacters: 1500, the first of 2, text shortened):

[
{
"index": 0,
"startSeconds": 1.36,
"endSeconds": 161.32,
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ&t=1s",
"markdown": "### [00:01] Rick Astley - Never Gonna Give You Up (Official Video) (4K Remaster) (part 1)\n\n[♪♪♪] ♪ We're no strangers to love ♪ ..."
}
]

One comment (with includeComments: true; author shown as a placeholder):

{
"commentId": "Ugxzbv_ZeZXNRcfwsSF4AaABAg",
"text": "Is everyone going to ignore the fact that Rick Astley looks like a twelve year old ...",
"author": "@example-user",
"authorChannelId": "UC...",
"isCreator": false,
"isPinned": false,
"likeCount": 291000,
"replyCount": 683,
"publishedTimeText": "6 years ago"
}

Every row has segmentCount (caption lines), transcriptType (manual = uploaded by the creator, auto_generated = YouTube's speech recognition) and transcriptAvailable, in every format.

The run's key-value store (Storage tab) also holds RUN-SUMMARY: how many videos had each status, what was charged, which inputs were invalid, connection statistics, and reliability (retries by kind, timeouts, time per video as average/p50/p95/max, videos per minute). Its status is finished, failed, aborted, timed_out (the run stopped itself just before its Timeout and kept what it had) or running (still going, or killed without warning). Dataset views for the API: ?view=overview, ?view=transcripts, ?view=markdown, ?view=comments.

API examples

curl (waits for the run and returns the rows):

curl -X POST "https://api.apify.com/v2/acts/superxcoder~youtube-transcripts-scraper/run-sync-get-dataset-items" \
-H "Authorization: Bearer $APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"videos": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"], "languages": ["en"], "outputFormat": "text"}'

JavaScript (npm install apify-client):

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('superxcoder/youtube-transcripts-scraper').call({
channels: ['@mkbhd'],
maxVideosPerChannel: 10,
outputFormat: 'markdown',
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const video of items) {
if (video.status === 'ok') console.log(video.title, video.chunks.length, 'chunks');
}

Python (pip install apify-client):

import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("superxcoder/youtube-transcripts-scraper").call(
run_input={
"videos": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ", "https://youtu.be/jNQXAC9IVRw"],
"languages": ["en"],
"outputFormat": "both",
}
)
for video in client.dataset(run["defaultDatasetId"]).iterate_items():
if not video["transcriptAvailable"]:
print(video["videoId"], video["status"], video["errorCode"], video["errorMessage"])
continue
print(video["title"], video["transcriptType"], video["segmentCount"], "lines")
for seg in video["segments"][:3]:
print(f' {seg["startSeconds"]:>7.2f}s {seg["text"]}')

AI and RAG pipelines

Choose outputFormat: "markdown": each video comes as sections of about chunkCharacters characters that end on a caption line, each with a [mm:ss] heading and a link to that moment. Embed the sections as they are and keep the link as the citation. The Actor makes no LLM calls and adds no AI text: what you embed is YouTube's caption text.

import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("superxcoder/youtube-transcripts-scraper").call(
run_input={"channels": ["@lexfridman"], "maxVideosPerChannel": 5, "outputFormat": "markdown", "chunkCharacters": 1500}
)
documents = [
{
"id": f'{video["videoId"]}-{chunk["index"]}',
"text": chunk["markdown"],
"metadata": {
"source": chunk["url"], # deep link, e.g. ...watch?v=ID&t=754s
"title": video["title"],
"channel": video["channelName"],
"language": video["language"],
"startSeconds": chunk["startSeconds"],
},
}
for video in client.dataset(run["defaultDatasetId"]).iterate_items()
if video["transcriptAvailable"]
for chunk in video["chunks"]
]
# documents -> your embedding model and vector store (pgvector, Pinecone, Chroma, ...)

Transcripts are third-party text: keep them in the "context" part of a prompt, not the instructions.

n8n

  1. Add an HTTP Request node: method POST, URL https://api.apify.com/v2/acts/superxcoder~youtube-transcripts-scraper/run-sync-get-dataset-items, Authentication: Generic > Header Auth with name Authorization and value Bearer <your Apify API token>.
  2. Body (JSON): {"videos": ["{{ $json.videoUrl }}"], "languages": ["en"], "outputFormat": "text"}. One item comes back per video, with transcriptText, title, status and errorCode.
  3. Add an IF node on {{ $json.transcriptAvailable }}, then send the true branch to OpenAI, Google Sheets or Notion.

The sync endpoint waits up to 5 minutes, enough for about 100 videos. For bigger batches use the Apify community node: Run an Actor (wait for finish), then Get dataset items.

Technical notes

  • Blocked IPs. YouTube blocks caption requests from most cloud and datacenter IPs, which is why youtube-transcript-api scripts that work on a laptop fail on a server, in Colab or in n8n cloud ("IP blocked", "RequestBlocked"). The Actor asks YouTube's mobile app API (Innertube, iOS first, then Android) for the caption tracks, sends the player and caption requests through residential proxy, and moves to the next app client only when one answers with an error that is not an access refusal (for example an app version YouTube no longer accepts). A bot check, captcha page or HTTP 403 ends that video as BLOCKED without a retry. Channel, playlist, search and comment pages go through cheaper datacenter proxy.
  • Translation. YouTube's machine-translated caption tracks (tlang) answer HTTP 429 to every request without a PO token, from home, datacenter and residential IPs alike, so they are not offered.
  • Spoken language. With languageFallback: "original", the language of YouTube's automatic speech-recognition track decides the spoken language, then a human-made track in that language is preferred. When YouTube cannot tell (no automatic captions), the first human-made captions are used and the row says languageMatch: "any". Many tools take the first track, which is often a different language.
  • Language codes follow BCP-47: legacy codes are converted (iw becomes he, in becomes id). en matches en-US and en-GB; pt-BR falls back to pt. A video can list the same code twice when it has two human-made tracks in one language.
  • Rate limits and speed. Requests are spread over many IPs and sessions. Measured on Apify: 100 videos took 129 to 150 seconds at maxConcurrency 10 and 171 seconds at 30, so 10 (the default) is the sweet spot.
  • Retries and time limits. A video that is rate-limited (HTTP 429), times out, gets a 5xx or hits a network error is retried up to maxRetries times after an exponential pause with random jitter (1-30 s after a 429, 0.5-8 s otherwise). Access refusals are never retried: a bot check, captcha page or 403 gives BLOCKED, a PO-token caption track gives CAPTIONS_NEED_PO_TOKEN, a region block gives GEO_RESTRICTED. Every HTTP request has a hard deadline (at most 30 s) and every attempt at a video a time limit (videoTimeoutSecs), so nothing can hang. One failed video never stops the others: it gets its own error row.
  • publishedAt is exact (ISO 8601 with time zone) when YouTube's web player response is returned, otherwise the date from the watch page (YYYY-MM-DD).
  • Region. Requests ask YouTube for US content (gl=US, which affects search ranking), but availability follows the proxy IP's country. No country is pinned by default; a region-blocked video is reported once as GEO_RESTRICTED and never retried from another country. Pin a country under Proxy to scrape as that country.
  • Fails loud. If YouTube blocks every request, the proxy is unreachable, every channel or playlist is unknown, or no video lists any caption track, the run fails with a clear message and nothing is charged, instead of succeeding with an empty dataset.
  • Untrusted content. Titles, captions and comments are written by other people. Treat them as untrusted input in LLM prompts (they can contain instructions or links). Markdown headings strip markdown control characters from titles. In Google Sheets or Excel, a cell that starts with =, +, - or @ can run as a formula, so keep Convert text to numbers, dates and formulas off when importing, and do not allow external links from imported comments.