# YouTube Transcript Scraper - Subtitles & Captions (`adyg/youtube-transcript-scraper`) Actor

Get YouTube transcripts (captions/subtitles) in bulk for videos, Shorts, channels, playlists or search results. Plain text, timestamped segments, SRT, VTT or RAG-ready chunks, with language fallback and video metadata. No browser, no API key. Pay only for transcripts found.

- **URL**: https://apify.com/adyg/youtube-transcript-scraper.md
- **Developed by:** [Adyanth Ganesh](https://apify.com/adyg) (community)
- **Categories:** Videos, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.95 / 1,000 transcript results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## YouTube Transcript Scraper & API for AI Agents

**Get YouTube transcripts (captions / subtitles) fast and in bulk: single videos, Shorts, whole channels, playlists or search results.** Each row has the full transcript as **plain text**, **timestamped segments**, **SRT**, **WebVTT** or **RAG-ready chunks**, plus video metadata: title, channel, publish date, duration, views, language and whether the captions are auto-generated.

It's built for AI agents, LLM pipelines, RAG / embeddings, content research and SEO. It doesn't use a browser, needs no YouTube API key, and you pay only for videos that return a transcript.

- ⚡ **Fast**: about **1-2 seconds per video**, many videos in parallel. In our cloud stress tests, about 130-140 videos from 15-16 channels, playlists and searches finished in 40-120 seconds per run.
- ✅ **Reliable**: **0 failed videos out of 536** in 4 cloud stress runs (20+ languages, Shorts, 3+ hour podcasts, live replays). Every video got either a transcript or a clear "no transcript" reason. If YouTube shows a "confirm you're not a bot" check, the request is retried on a fresh IP and switches to residential proxy when needed.
- 🌍 **Any language**: you set a list of preferred languages with fallbacks. If none is available, you get the original spoken language. Videos with AI-dubbed audio tracks are handled correctly.
- 🤖 **Agent-friendly output**: clean JSON, a word count, chapters, and RAG chunks with start/end times and deep links (`&t=123s`) for citations.
- 💸 **Pay only for transcripts**: videos without captions, private, age-restricted or unavailable videos return a free row that says why.

### What data do you get?

| Field group | Fields |
|---|---|
| Video | `videoId`, `url`, `title`, `channelName`, `channelId`, `channelUrl`, `publishDate`, `durationSeconds`, `duration`, `viewCount`, `category`, `isLive`, `isUnlisted`, `description`, `chapters` |
| Transcript | `text` (plain), `segments` (`start`, `end`, `text` in seconds), `timestampedText` (`[m:ss]` lines), `srt`, `vtt`, `chunks` (`index`, `text`, `start`, `end`, `url`, `chars`), `wordCount`, `segmentCount` |
| Language | `language`, `languageName`, `isAutoGenerated`, `originalLanguage`, `languageMatch` (`preferred` / `original`), `availableLanguages` (all caption tracks), `isTranslated`, `translationError` |
| Status | `status` (`ok`, `no_transcript`, `error`), `noTranscriptReason`, `error`, `source`, `sourceType`, `scrapedAt` |

### How to use

1. Paste **YouTube URLs, video IDs or @handles** into one list. The Actor detects the type for each one:
   - **Videos**: `watch?v=`, `youtu.be/`, `/shorts/`, `/live/`, `/embed/`, or a bare 11-character ID.
   - **Channels**: `@handle`, `/@handle`, `/channel/UC…`, `/c/…`, `/user/…`. Add `/shorts` or `/streams` to the URL for that tab.
   - **Playlists**: `playlist?list=…`
2. Optionally add **search queries**. You get the top results for each one.
3. Set **Max videos per channel / playlist / search**.
4. Choose your **preferred languages** (e.g. `en`, `es`) and the **output formats** you need.
5. Click **Start**. You can download the results as JSON, CSV or Excel, or call the Actor through the API / MCP.

### Input example

```json
{
    "urls": [
        "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
        "https://youtu.be/jNQXAC9IVRw",
        "https://www.youtube.com/@veritasium",
        "https://www.youtube.com/@mkbhd/shorts",
        "https://www.youtube.com/playlist?list=PLUl4u3cNGP63WbdFxL8giv4yhgdMGaZNA"
    ],
    "searchQueries": ["how transformers work"],
    "maxVideos": 20,
    "languages": ["en", "es"],
    "fallbackToAnyLanguage": true,
    "outputFormats": ["text", "segments", "chunks"],
    "chunkSize": 2000,
    "chunkOverlap": 200
}
```

### Output example

```json
{
    "videoId": "jNQXAC9IVRw",
    "url": "https://www.youtube.com/watch?v=jNQXAC9IVRw",
    "status": "ok",
    "title": "Me at the zoo",
    "channelName": "jawed",
    "channelId": "UC4QobU6STFB0P71PMvOGN5A",
    "publishDate": "2005-04-23T20:31:52-07:00",
    "durationSeconds": 19,
    "duration": "0:19",
    "viewCount": 441560186,
    "category": "Film & Animation",
    "language": "en",
    "languageName": "English",
    "isAutoGenerated": false,
    "originalLanguage": "en",
    "languageMatch": "preferred",
    "text": "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",
    "wordCount": 39,
    "segments": [
        {"start": 1.2, "end": 3.36, "text": "All right, so here we are, in front of the elephants"},
        {"start": 5.318, "end": 7.974, "text": "the cool thing about these guys is that they have really..."}
    ],
    "chunks": [
        {"index": 0, "text": "All right, so here we are, ...", "start": 1.2, "end": 18.881, "url": "https://www.youtube.com/watch?v=jNQXAC9IVRw&t=1s", "chars": 217}
    ],
    "availableLanguages": [
        {"code": "en", "name": "English", "isAutoGenerated": false},
        {"code": "de", "name": "German", "isAutoGenerated": false}
    ]
}
```

A video without a transcript returns a free row:

```json
{"videoId": "UiPcEW_d9Io", "status": "no_transcript", "noTranscriptReason": "no_captions", "error": "This video has no captions or auto-generated transcript.", "title": "...", "transcriptAvailable": false}
```

### Language selection

- **Preferred languages** are tried in order. For each language, creator-made captions come before auto-generated ones (unless you turn this off). `en` also matches `en-US`, `en-GB`, etc.
- **Fallback**: if none of your languages exists, you get the original spoken language (`languageMatch: "original"`). If you turn the fallback off, you get a free `no_transcript` row with reason `language_not_available` and the list of available languages.
- **Only human-made captions**: ignore auto-generated (ASR) captions completely.
- **Auto-translate** (best effort): asks YouTube to machine-translate the original transcript into your first preferred language. YouTube currently rate-limits these translation requests heavily, so most of them fail. When that happens you get the original-language transcript with `translationError` set. If you need a translation, we recommend translating the `text` field with your own LLM.

### Output formats

| Format | Field | Use it for |
|---|---|---|
| Plain text | `text` | LLM prompts, summaries, search |
| Timestamped segments | `segments` | Highlights, quotes, video editing |
| Timestamped text | `timestampedText` | LLM prompts that cite times (`[12:34] ...`) |
| SRT / WebVTT | `srt`, `vtt` | Subtitle files for players and editors |
| RAG chunks | `chunks` | Embeddings / vector DBs. Chunks follow caption boundaries and have `start`, `end` and a deep link to the exact moment |

All formats cost the same. Turn off formats you don't need to keep the dataset small.

### Pricing

This Actor uses **pay-per-event** pricing. You pay for **`transcript-result`**: one video that returned a transcript. Proxy and compute costs are included. Rows for videos without a transcript, unavailable videos and errors are **free**. Check the **Pricing** tab for the current price.

You can cap your spending with the run's **maximum cost** (or maximum paid results). The Actor stops cleanly when it reaches the limit.

### Reliability - how it works

- The Actor calls the same public endpoints the YouTube mobile apps use. It doesn't load watch pages or run a browser, so each video costs only a few kilobytes of traffic and finishes in about a second.
- Requests go through Apify datacenter proxy first. If YouTube answers "Sign in to confirm you're not a bot" or HTTP 429, the request is retried on a fresh IP with backoff. When the datacenter IPs keep getting blocked, the Actor switches to residential IPs for the rest of the run.
- Each video is processed on its own. One failed video never stops the run, and the run summary (key-value store `SUMMARY`) shows the success rate and proxy statistics.

### Related tools

Content research: what people search for, what creators say, and what channels post.

- [Google Trends Scraper](https://apify.com/adyg/google-trends-scraper): interest over time, by region, related queries and Trending Now
- [Telegram Channel Scraper](https://apify.com/adyg/telegram-channel-scraper): posts, views and reactions from public channels
- [Keyword Search Volume (Google Keyword Planner)](https://apify.com/adyg/keyword-search-volume): search volume, CPC and keyword ideas in bulk

All of these are maintained by the same developer and export flat JSON, CSV or Excel, so you can feed one tool's dataset into the next.

### FAQ

**Do I need a YouTube API key or account?**
No. The Actor uses no login, no cookies and no API key.

**Which videos have no transcript?**
Videos whose creator turned off captions and that YouTube didn't auto-caption (often music, live news streams and very short clips). Also private, members-only, age-restricted and removed videos, and live streams that haven't started. These return a free row with `noTranscriptReason`.

**Does it work for Shorts and live streams?**
Yes. Use Shorts URLs, a channel's `/shorts` tab, or set **Channel tab** to `Shorts`. Finished live streams (replays) work when YouTube has captions for them.

**How many videos can I get from a channel?**
As many as the channel has. Set **Max videos per channel** (up to 5,000 per source). Channels are listed newest first.

**Can I use it from an AI agent / MCP / n8n / Make / Zapier?**
Yes. Call the Actor through the Apify API, MCP server or any Apify integration with a single video URL, and read `text` from the first dataset item. Rows are small, flat JSON.

**Is it legal to scrape YouTube transcripts?**
This Actor collects publicly available captions and metadata only, and no personal data beyond public channel names. Transcripts are the creators' copyrighted content. Make sure your use (e.g. research, analysis, summarization) complies with YouTube's Terms of Service and copyright law in your country. If you are unsure, consult a lawyer.

**Something didn't work?**
Open an issue on the **Issues** tab with the run ID and the video URL. We usually fix things within a day.

# Actor input Schema

## `urls` (type: `array`):

One per line, mixed freely: video URLs (<code>watch?v=</code>, <code>youtu.be/</code>, <code>/shorts/</code>, <code>/live/</code>, <code>/embed/</code>), 11-character video IDs, channel URLs or <code>@handles</code> (<code>/@name</code>, <code>/channel/UC…</code>, <code>/c/</code>, <code>/user/</code>; add <code>/shorts</code> or <code>/streams</code> to pick that tab) and playlist URLs (<code>playlist?list=</code>). The type is detected automatically.

## `searchQueries` (type: `array`):

Search YouTube and get transcripts of the top <i>Max videos</i> results (videos only) for each query, e.g. <code>how transformers work</code>.

## `maxVideos` (type: `integer`):

How many videos to take from each channel, playlist or search query (newest first for channels). Single video URLs are always included.

## `channelContentType` (type: `string`):

Which uploads to take from a channel. A tab in the channel URL (e.g. <code>/@name/shorts</code>) overrides this.

## `maxTotalVideos` (type: `integer`):

Upper limit for the whole run, after removing duplicates.

## `languages` (type: `array`):

Language codes in order of preference, e.g. <code>en</code>, <code>es</code>, <code>pt-BR</code>. <code>en</code> also matches <code>en-US</code>/<code>en-GB</code>. Leave empty to always get the video's original (spoken) language.

## `fallbackToAnyLanguage` (type: `boolean`):

If none of the preferred languages exists, return the transcript in the video's original language (manual captions first, then auto-generated). Turn off to get a free <code>no_transcript</code> row instead.

## `autoTranslate` (type: `boolean`):

If no transcript exists in your preferred languages, ask YouTube to machine-translate the original transcript into the first preferred language. If YouTube refuses the translation, the original language is returned and <code>translationError</code> is set.

## `preferManualCaptions` (type: `boolean`):

Pick creator-uploaded captions over auto-generated (ASR) ones in the same language.

## `skipAutoGenerated` (type: `boolean`):

Ignore auto-generated captions. Videos with only auto-generated captions get a free <code>no_transcript</code> row.

## `outputFormats` (type: `array`):

Which transcript formats to include in each row. Every format is included in the same price.

## `chunkSize` (type: `integer`):

Only for the <i>RAG chunks</i> format. Chunks follow caption boundaries (no cut words). 2,000 characters ≈ 400-500 tokens.

## `chunkOverlap` (type: `integer`):

Text repeated from the end of one chunk at the start of the next.

## `includeDescription` (type: `boolean`):

Add the video description and chapters (parsed from description timestamps).

## `proxyConfiguration` (type: `object`):

Defaults to Apify datacenter proxy with automatic residential fallback when YouTube asks to 'confirm you're not a bot'. Choose the RESIDENTIAL group to always use residential IPs.

## `residentialFallback` (type: `boolean`):

Retry blocked requests on residential IPs. Recommended - this is what keeps the success rate high.

## `maxConcurrency` (type: `integer`):

Videos processed in parallel.

## `maxRetries` (type: `integer`):

Each blocked request is retried on a new IP, with backoff.

## Actor input object example

```json
{
  "urls": [
    "https://www.youtube.com/watch?v=jNQXAC9IVRw"
  ],
  "maxVideos": 1,
  "channelContentType": "videos",
  "maxTotalVideos": 1000,
  "languages": [
    "en"
  ],
  "fallbackToAnyLanguage": true,
  "autoTranslate": false,
  "preferManualCaptions": true,
  "skipAutoGenerated": false,
  "outputFormats": [
    "text",
    "segments"
  ],
  "chunkSize": 2000,
  "chunkOverlap": 200,
  "includeDescription": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "residentialFallback": true,
  "maxConcurrency": 20,
  "maxRetries": 6
}
```

# Actor output Schema

## `transcripts` (type: `string`):

No description

## `allRows` (type: `string`):

No description

## `summary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        "https://www.youtube.com/watch?v=jNQXAC9IVRw"
    ],
    "maxVideos": 1,
    "languages": [
        "en"
    ],
    "outputFormats": [
        "text",
        "segments"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("adyg/youtube-transcript-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "urls": ["https://www.youtube.com/watch?v=jNQXAC9IVRw"],
    "maxVideos": 1,
    "languages": ["en"],
    "outputFormats": [
        "text",
        "segments",
    ],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("adyg/youtube-transcript-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "urls": [
    "https://www.youtube.com/watch?v=jNQXAC9IVRw"
  ],
  "maxVideos": 1,
  "languages": [
    "en"
  ],
  "outputFormats": [
    "text",
    "segments"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call adyg/youtube-transcript-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,adyg/youtube-transcript-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/WUIPI2ZsnykzxuMeq/builds/i9p3yQYcP1alQNhQ0/openapi.json
