# YouTube Transcript Scraper - JSON, SRT, VTT, RAG (`jamhimself/youtube-transcript-extractor`) Actor

Extract transcripts from YouTube videos. Input: video URLs or IDs + language preferences. Output: plain text, timestamped segments, SRT/VTT subtitles, and RAG chunks with deep links. 100+ languages, no API key. $0.0075 per delivered transcript.

- **URL**: https://apify.com/jamhimself/youtube-transcript-extractor.md
- **Developed by:** [Jaime Martinez](https://apify.com/jamhimself) (community)
- **Categories:** Videos, Agents, Social media
- **Stats:** 4 total users, 3 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.50 / 1,000 transcripts

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## YouTube Transcript Scraper & API for developers and AI pipelines

YouTube transcript scraper and transcript API for developers and AI pipelines — extract transcripts and subtitles as JSON, text, SRT, VTT, or RAG chunks with timestamps, in bulk, across 100+ languages, with no API key.

YouTube gates its caption endpoint behind PoToken bot-checks and datacenter-IP bans, so free libraries like `youtube-transcript-api` and `yt-dlp` often return empty transcripts in production. This actor mints a per-video proof-of-origin token, escalates to residential proxies when needed, and is actively maintained — and you only pay for transcripts actually delivered.

Built for developers and AI builders who need YouTube captions at scale: feed an LLM, build a RAG pipeline, summarize videos, translate, search, or repurpose content.

***

### ⚡ Quick start

Paste this into the Actor's **Input** (JSON view) and hit **Start**:

```json
{
  "videoUrls": ["https://www.youtube.com/watch?v=jNQXAC9IVRw"],
  "languages": ["en"],
  "includePlainText": true
}
```

Each result row looks like:

```json
{
  "ok": true,
  "videoId": "jNQXAC9IVRw",
  "url": "https://www.youtube.com/watch?v=jNQXAC9IVRw",
  "title": "Me at the zoo",
  "channelName": "jawed",
  "language": "en",
  "captionKind": "manual",
  "segmentCount": 6,
  "text": "All right, so here we are, in front of the elephants..."
}
```

Or run it from the API:

```
POST https://api.apify.com/v2/acts/jamhimself~youtube-transcript-extractor/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN
```

Turn on `includeSrt`, `includeVtt`, or `ragChunking` to also get subtitle files and embedding-ready chunks in the same run.

### Why this actor

- ✅ **Built for YouTube's PoToken era.** YouTube now blocks naive transcript scrapers (empty responses behind its bot-check). This actor solves the current proof-of-origin requirement and is actively maintained with a daily canary watchdog — 100% success across our monitored canary runs since launch (2026-06-12).
- ⚡ **Bulk & fast.** Pass one URL or thousands; concurrent extraction with automatic retries.
- 🎯 **Every format you need.** Plain text, timestamped segments, **SRT**, **VTT**, and **RAG chunks** with deep links — in a single run.
- 🌍 **100+ languages**, manual and auto-generated captions, with language preference and fallback.
- 💸 **Pay per video.** No monthly fee. You're only charged for transcripts we actually deliver.

### Use cases

- **RAG / LLM ingestion** — turn long videos into clean, chunked, timestamped context for vector databases (LangChain, LlamaIndex).
- **Summaries & notes** — feed transcripts to Claude/GPT for summaries, chapters, show notes.
- **Subtitles** — export ready-to-use `.srt` / `.vtt` files.
- **Search & analytics** — index spoken content across a channel.
- **Translation & repurposing** — transcribe once, translate anywhere.

### Input

| Field | Type | Description |
|-------|------|-------------|
| `videoUrls` | array | **Required.** YouTube video URLs (`watch`, `youtu.be`, `shorts`, `embed`, `live`) or raw 11-char video IDs. |
| `languages` | array | Preferred language codes in order, e.g. `["en","es"]`. Falls back to English, then first available. Default `["en"]`. |
| `preferManual` | boolean | Prefer human-uploaded captions over auto-generated. Default `true`. |
| `includeSegments` | boolean | Include timestamped `{start, duration, text}` segments. Default `true`. |
| `includePlainText` | boolean | Include the whole transcript as one clean string. Default `true`. |
| `includeSrt` | boolean | Include a SubRip `.srt` string. Default `false`. |
| `includeVtt` | boolean | Include a WebVTT `.vtt` string. Default `false`. |
| `ragChunking` | boolean | Emit overlapping chunks with timestamps + deep links for embeddings. Default `false`. |
| `chunkMaxChars` / `chunkOverlapChars` | integer | Chunk sizing for RAG. Defaults 1500 / 200. |
| `concurrency` | integer | Videos processed in parallel (1–10). Default 5. |
| `proxyCountryCode` | string | Optional two-letter country code for proxies (affects region-locked captions). |

#### Example input

```json
{
  "videoUrls": [
    "https://www.youtube.com/watch?v=jNQXAC9IVRw",
    "https://youtu.be/8S0FDjFBj8o"
  ],
  "languages": ["en"],
  "includeSrt": true,
  "ragChunking": true
}
```

### Output

One dataset item per video:

```json
{
  "ok": true,
  "videoId": "jNQXAC9IVRw",
  "url": "https://www.youtube.com/watch?v=jNQXAC9IVRw",
  "title": "Me at the zoo",
  "channelName": "jawed",
  "durationSeconds": 19,
  "viewCount": 358000000,
  "language": "en",
  "captionKind": "manual",
  "availableLanguages": [{ "languageCode": "en", "kind": "manual", "name": "English" }],
  "segmentCount": 6,
  "text": "All right, so here we are, in front of the elephants...",
  "segments": [{ "start": 1.2, "duration": 2.16, "text": "All right, so here we are, in front of the elephants" }],
  "srt": "1\n00:00:01,200 --> 00:00:03,360\nAll right, so here we are...",
  "chunks": [{ "index": 0, "text": "...", "startSeconds": 1.2, "endSeconds": 18.5, "deepLink": "https://www.youtube.com/watch?v=jNQXAC9IVRw&t=1s" }]
}
```

The dataset ships with three views: **Transcripts** (one row per video), **RAG chunks** (one row per chunk, with timestamps and deep links), and **Subtitles (SRT/VTT)**.

Successful transcripts are written to the **default dataset** — that's the only thing you're billed for. Videos that couldn't be delivered (no captions, private/unavailable, bad URL) are listed with the reason in the run's key-value store under **`SKIPPED`**, and a per-run **`SUMMARY`** record counts successes, no-caption videos, and failures — **never charged**.

### What you'll pay

**The first 50 transcripts in every run are free.** After that, **$0.0075 per delivered transcript** — no actor-start fee, no subscription, no minimums. Skipped videos are never billed.

| Transcripts delivered in a run | You pay |
|---|---|
| 50 or fewer | **$0.00** |
| 100 | **$0.38** |
| 1,000 | **$7.13** |

Migrating from a monthly-subscription transcript scraper? There's nothing to subscribe to here — small runs are free, and a run that delivers nothing costs nothing.

*Prices above are the standard rate. Apify Store subscription discounts apply automatically — Bronze −10%, Silver −20%, Gold and above −40% (that is the "from" price shown on this page).*

The **Pricing** tab always shows the current rate.

### Use as an MCP tool / with AI agents

This actor is exposed as an MCP tool via Apify's MCP server (streamable HTTP):

```
https://mcp.apify.com/?tools=jamhimself/youtube-transcript-extractor
```

Example MCP client configuration:

```json
{
  "mcpServers": {
    "youtube-transcripts": {
      "url": "https://mcp.apify.com/?tools=jamhimself/youtube-transcript-extractor"
    }
  }
}
```

Any MCP client — Claude, Cursor, or agent frameworks — can call this actor directly with the same input and output described above (authenticate with your Apify API token).

### Why scrapers return empty transcripts (and why this one doesn't)

YouTube's caption (`timedtext`) endpoint now requires a proof-of-origin token (PoToken) minted by its BotGuard bot-check and bound to the specific video — without it, the endpoint returns an empty 200 response. That, plus aggressive datacenter-IP blocking, is why many free libraries and stale scrapers suddenly return no transcript at all.

This actor:

- solves the BotGuard challenge and mints a **fresh PoToken bound to each video ID**,
- tries a cheap datacenter IP first, then **automatically escalates to rotating residential proxies**,
- is **actively maintained** with a daily canary watchdog — 100% success across our monitored canary runs since launch (2026-06-12).

**Delivery receipt, not just a success stat:** our daily canary counts **transcript rows actually delivered**, not merely "run succeeded" — because when YouTube tightens its bot-check, broken scrapers keep reporting ~99.9% success while silently returning empty datasets. A green run-stat can't tell you that; a row-count canary can. (Canary status current as of 2026-08.)

If a video still returns no transcript, it's almost always genuine: captions disabled, private/members-only, region-locked, or a live stream without a caption track. Those are skipped and never billed.

### FAQ

**Does it work for auto-generated captions?** Yes — manual and ASR (auto) captions, in 100+ languages.

**Playlists and channels?** Pass individual video URLs for now. Playlist/channel expansion is coming.

**Is this legal?** It accesses publicly available caption data. You are responsible for complying with YouTube's Terms of Service and applicable copyright law in your use of the output.

**Why do some videos return no transcript?** The video genuinely has captions disabled, is private/age-restricted/region-locked, or is a live stream without a caption track.

***

**Was this useful?** An honest review helps other people find this Actor — and tells me what to fix next. [Leave a review](https://apify.com/jamhimself/youtube-transcript-extractor/reviews).

Questions or a format you need? Open an issue on the actor — it's actively maintained.

*YouTube is a trademark of Google LLC. This actor is not affiliated with or endorsed by them. Built and maintained by Jamhimself LLC.*

# Actor input Schema

## `videoUrls` (type: `array`):

YouTube video URLs or IDs — extract the transcript / subtitles / captions of each as JSON, SRT, VTT, or RAG chunks. Accepts watch, youtu.be, shorts, embed, and live URLs or raw 11-character video IDs. One transcript per video.

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

Transcript language codes in order of preference (e.g. \["en", "es"]). Falls back to English, then the first available caption track.

## `preferManual` (type: `boolean`):

When both exist, pick human-uploaded captions over YouTube's auto-generated (ASR) ones.

## `includeSegments` (type: `boolean`):

Include the full array of {start, duration, text} segments.

## `includePlainText` (type: `boolean`):

Include the whole transcript as one clean text string.

## `includeSrt` (type: `boolean`):

SRT subtitles — include the transcript as a SubRip (.srt) subtitle string.

## `includeVtt` (type: `boolean`):

VTT subtitles — include the transcript as a WebVTT (.vtt) subtitle string.

## `ragChunking` (type: `boolean`):

RAG chunks — split the transcript into overlapping chunks with start/end timestamps and a clickable deep link per chunk, ready for embedding pipelines (LangChain, LlamaIndex, vector DBs).

## `chunkMaxChars` (type: `integer`):

Maximum characters per RAG chunk.

## `chunkOverlapChars` (type: `integer`):

Approximate character overlap between consecutive chunks.

## `concurrency` (type: `integer`):

How many videos to process in parallel (1-10).

## `proxyCountryCode` (type: `string`):

Optional two-letter country code for proxies (affects which captions YouTube serves for region-locked content).

## Actor input object example

```json
{
  "videoUrls": [
    "https://www.youtube.com/watch?v=jNQXAC9IVRw"
  ],
  "languages": [
    "en"
  ],
  "preferManual": true,
  "includeSegments": true,
  "includePlainText": true,
  "includeSrt": false,
  "includeVtt": false,
  "ragChunking": false,
  "chunkMaxChars": 1500,
  "chunkOverlapChars": 200,
  "concurrency": 5
}
```

# Actor output Schema

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

One row per video: transcript text, timestamped segments, and metadata.

## `ragChunks` (type: `string`):

One row per RAG chunk with start/end timestamps and a deep link (enable the ragChunking input).

## `subtitles` (type: `string`):

Ready-to-save SRT/VTT subtitle strings per video (enable the includeSrt / includeVtt inputs).

# 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 = {
    "videoUrls": [
        "https://www.youtube.com/watch?v=jNQXAC9IVRw"
    ],
    "languages": [
        "en"
    ],
    "chunkMaxChars": 1500,
    "chunkOverlapChars": 200,
    "concurrency": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("jamhimself/youtube-transcript-extractor").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 = {
    "videoUrls": ["https://www.youtube.com/watch?v=jNQXAC9IVRw"],
    "languages": ["en"],
    "chunkMaxChars": 1500,
    "chunkOverlapChars": 200,
    "concurrency": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("jamhimself/youtube-transcript-extractor").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 '{
  "videoUrls": [
    "https://www.youtube.com/watch?v=jNQXAC9IVRw"
  ],
  "languages": [
    "en"
  ],
  "chunkMaxChars": 1500,
  "chunkOverlapChars": 200,
  "concurrency": 5
}' |
apify call jamhimself/youtube-transcript-extractor --silent --output-dataset

```

## MCP server setup

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

```

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/zWN2VGuUeLRDkScdf/builds/piqgBrE519vcKiB2t/openapi.json
