How to use YouTube Transcript & Subtitles Scraper
Step by step: in the Apify Store, over the API, from JavaScript or Python, in n8n, and from AI agents through MCP or LangChain.
$2.48 per 1,000 transcripts on the Free plan, down to $1.80 on higher plans (live Apify price on 2026-10-31).
1. In the Apify Store (no code)
- Open arcadium/youtube-transcripts and sign in to Apify (a free account works).
- Fill in the input form. The fields below are the ones the example uses.
- Set a maximum cost per run in the run options if you want a hard cap.
- Start the run. Rows appear in the Output tab as they arrive.
- Export the dataset as JSON, CSV or Excel, or connect an integration.
| Input field | Type | Description |
|---|---|---|
urls | array | Example: https://www.youtube.com/watch?v=dQw4w9WgXcQ, https://youtu.be/dQw4w9WgXcQ, a Shorts URL, a bare video ID (dQw4w9WgXcQ), a channel (https://www.youtube.com/@kurzgesagt, add /shorts or /streams for those tabs) or a playlist URL. Channels and playlists give up to maxVideosPerSource videos each, channels newest first. |
languages | array | ISO language codes in order of preference, e.g. ["en", "de"]; en also matches en-US and en-GB. The first one the video has is used. Empty or none available: the original spoken language (languageMatched is false when none matched). |
captionType | string | Values: any (uploaded captions first, else auto-generated), manual (uploaded by the creator only), auto (YouTube speech recognition only). Videos without that type give a free error row. Default: any. |
maxVideosPerSource | integer | Transcripts per channel or playlist URL, 1 to 5,000 (default 50). Videos without captions or outside the date range do not count. Direct video URLs are not limited by this. |
subtitleFormat | string | Values: none, srt, vtt. srt or vtt adds the captions as a ready subtitle file in the srt or vtt field. Default: none. |
Example input:
{
"urls": [
"https://www.youtube.com/watch?v=dQw4w9WgXcQ"
],
"languages": [],
"captionType": "any",
"maxVideosPerSource": 10,
"subtitleFormat": "none"
}
2. Over the Apify API
The synchronous endpoint runs the actor and returns its rows in one call (for runs up to about five minutes; use the asynchronous run endpoint for longer ones).
curl -X POST "https://api.apify.com/v2/acts/arcadium~youtube-transcripts/run-sync-get-dataset-items" \
-H "Authorization: Bearer $APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"urls":["https://www.youtube.com/watch?v=dQw4w9WgXcQ"],"languages":[],"captionType":"any","maxVideosPerSource":10,"subtitleFormat":"none"}'
3. From JavaScript
// npm install apify-client
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('arcadium/youtube-transcripts').call({
"urls": [
"https://www.youtube.com/watch?v=dQw4w9WgXcQ"
],
"languages": [],
"captionType": "any",
"maxVideosPerSource": 10,
"subtitleFormat": "none"
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
4. From Python
# pip install apify-client
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("arcadium/youtube-transcripts").call(run_input={
"urls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"],
"languages": [],
"captionType": "any",
"maxVideosPerSource": 10,
"subtitleFormat": "none",
})
items = client.dataset(run["defaultDatasetId"]).list_items().items
print(items)
5. In n8n
Paste this node into a workflow (it is a plain HTTP Request node). Create a Header Auth credential with name Authorization and value Bearer <APIFY_TOKEN>; each returned row becomes one item for the next node.
{
"nodes": [
{
"name": "Run arcadium/youtube-transcripts",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4,
"position": [
0,
0
],
"parameters": {
"method": "POST",
"url": "https://api.apify.com/v2/acts/arcadium~youtube-transcripts/run-sync-get-dataset-items",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "{\"urls\":[\"https://www.youtube.com/watch?v=dQw4w9WgXcQ\"],\"languages\":[],\"captionType\":\"any\",\"maxVideosPerSource\":10,\"subtitleFormat\":\"none\"}",
"options": {
"timeout": 300000
}
}
}
],
"connections": {}
}
6. From an AI assistant (MCP)
Add Apify’s MCP server to Claude Desktop, Cursor, VS Code or any MCP client, limited to this actor:
{
"mcpServers": {
"apify": {
"url": "https://mcp.apify.com/?tools=arcadium/youtube-transcripts",
"headers": {
"Authorization": "Bearer <APIFY_TOKEN>"
}
}
}
}
More on the MCP page.
7. From LangChain
# pip install langchain-apify (reads APIFY_API_TOKEN from the environment)
from langchain_apify import ApifyActorsTool
tool = ApifyActorsTool("arcadium/youtube-transcripts")
rows = tool.invoke({"run_input": {
"urls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"],
"languages": [],
"captionType": "any",
"maxVideosPerSource": 10,
"subtitleFormat": "none",
}})
# or give `tool` to an agent: create_react_agent(llm, [tool])
Sample output
One row, from the example in the actor’s README. Long text is shortened and lists are cut to two items.
{
"rowType": "transcript",
"platform": "youtube",
"videoId": "dQw4w9WgXcQ",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"title": "Rick Astley - Never Gonna Give You Up (Official Video) (4K Remaster)",
"channelName": "Rick Astley",
"channelId": "UCuAXFkgsw1L7xaCfnd5JJOw",
"channelHandle": "@RickAstleyYT",
"channelUrl": "https://www.youtube.com/channel/UCuAXFkgsw1L7xaCfnd5JJOw",
"durationSeconds": 213,
"publishDate": "2009-10-24T23:57:33-07:00",
"viewCount": 1829055386,
"likeCount": 19491653,
"category": "Music",
"isLiveContent": false,
"keywords": [
"rick astley",
"Never Gonna Give You Up"
],
"description": "The official video for “Never Gonna Give You Up” by Rick Astley. Never: The Autobiography OUT NOW!...",
"language": "en",
"languageName": "English",
"isAutoGenerated": false,
"languageMatched": true,
"translationStatus": "not_requested",
"translatedFrom": null,
"availableLanguages": [
{
"code": "en",
"name": "English",
"isAutoGenerated": false
},
{
"code": "en",
"name": "English",
"isAutoGenerated": true
}
],
"transcript": "[♪♪♪] ♪ We're no strangers to love ♪ ♪ You know the rules and so do I ♪ ...",
"segmentCount": 61,
"wordCount": 487,
"segments": [
{
"start": 1.36,
"duration": 1.68,
"text": "[♪♪♪]"
},
{
"start": 18.64,
"duration": 3.24,
"text": "♪ We're no strangers to love ♪"
}
],
"source": null,
"scrapedAt": "2026-10-22T08:00:00.000Z"
}
| Field | What it holds |
|---|---|
rowType | Kind of row. Filter on this first. Values: 'transcript': one video's transcript (charged). 'error': a video or URL gave no transcript; see code and message (free). |
platform | Always 'youtube'. |
videoId | YouTube video id, 11 characters, e.g. dQw4w9WgXcQ. |
url | Watch page link of the video (Shorts included). |
title | Video title. |
channelName | Name of the channel that published the video (the publisher, not a viewer). |
channelId | Channel id, starts with UC, e.g. UCuAXFkgsw1L7xaCfnd5JJOw. |
channelHandle | Channel handle with the @, e.g. @RickAstleyYT. Null if YouTube did not return it. |
channelUrl | Link to the channel page built from channelId. |
durationSeconds | Video length in seconds. |
publishDate | Publish date and time as YouTube gives it, ISO 8601 with offset, e.g. 2009-10-24T23:57:33-07:00. |
viewCount | Views at the time of the run. |
likeCount | Likes at the time of the run; null when hidden. |
category | YouTube category, e.g. Music, Education, News & Politics. |
isLiveContent | True for live streams and recorded live streams. |
keywords | Tags the creator set on the video (may be empty). |
description | Video description, short (about 300 characters) or full per input; emails and phone numbers are removed. |
language | Language code of the returned transcript, e.g. en, de-DE, pt-BR. |
languageName | English name of that language, e.g. German (Germany). |
isAutoGenerated | True when the transcript is YouTube's speech recognition (no punctuation, may misspell names); false when the creator uploaded it. |
languageMatched | False when none of the requested languages existed and the original-language track was returned instead. |
translationStatus | Values: 'not_requested', 'original' (the video has a native track in translateTo), 'translated' (YouTube's own machine translation), 'unavailable' (YouTube refused it; the original is returned). |
translatedFrom | Source language code when translationStatus is 'translated', else null. |
availableLanguages | Every caption track of the video: code, name, isAutoGenerated. Use it to request another language. |
transcript | The whole transcript as one text: caption lines joined with spaces. Emails and phone numbers are removed. |
segmentCount | Number of caption lines. |
wordCount | Words in transcript, for chunking and token estimates. |
segments | Caption lines in order: start (seconds from the start), duration (seconds), text. Present when includeSegments is on. |
srt | The captions as an SRT subtitle file, when subtitleFormat is srt. |
vtt | The captions as a WebVTT subtitle file, when subtitleFormat is vtt. |
source | The channel or playlist URL this video came from; null for videos given directly. |
scrapedAt | When the row was read, ISO 8601. |
input | Error rows: the URL or id as given. |
code | Error rows: not_found (private, removed, unknown channel), no_match (no captions, or none of the requested type), unavailable (age-restricted, members-only, upcoming, temporary error), blocked (rate limited, retry later), invalid_input. |
message | Error rows: what happened, in plain English. |
Schedule it
In Apify Console, create a Task from your input and add a Schedule (for example daily). Each run writes a new dataset; integrations can push the rows to Google Sheets, Slack, a webhook or your database.