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

Paste YouTube video URLs or IDs, channels, playlists or searches, in bulk. Get each transcript as plain text with timestamps, title, channel and date. At no extra cost: views, likes, tags, chapters, SRT and VTT subtitles, LLM text, RAG chunks by chapter. $3 per 1,000, videos without captions free.

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

## Pricing

from $2.10 / 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?

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 & Subtitles Scraper

Get the transcript of any YouTube video as clean text, ready for an LLM, a RAG pipeline or a spreadsheet.
Paste video URLs or IDs, a channel, a playlist or a search, pick a language, and get one row per video with the full transcript, timestamps and video details.
Turn on SRT, VTT, plain-text, LLM-ready Markdown or RAG chunks split by YouTube chapters, at no extra cost.

You pay **$3 per 1,000 transcripts** ($0.003 each), and less on Apify Store discount tiers (Bronze, Silver, Gold), down to $2.10 per 1,000 on Gold.
Videos that fail or have no captions are **never charged**.

### At a glance

| Topic | Details |
|---|---|
| Input | YouTube video URLs or IDs (watch, youtu.be, Shorts, embed and live links), channels, playlists and search queries |
| Output | One JSON row per video: `transcript` as one plain-text string, `segments` with start times, plus title, channel, duration and language. Optional: views, likes, comments, tags, chapters, and SRT, VTT, plain-text, LLM-ready and RAG chunk formats |
| Price | $0.003 per transcript ($3 per 1,000), or $2.70 per 1,000 on Bronze, $2.40 on Silver and $2.10 on Gold. No start fee. Videos without captions and failed videos are free |
| Tested | Final test run on 16 Oct 2026 with every option on: 56 video links, 4 channels and playlists and 3 searches gave 88 videos, and all 87 that exist returned a transcript with every format, in 73 seconds |
| Limits | Reads YouTube's own captions and does not run speech-to-text, so a video with no captions returns a free `no_transcript` row. No cap on videos per run, 10 fetched in parallel by default |

### Call it from Python

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("heyitsgautham/youtube-transcript-scraper").call(
    run_input={"videos": ["https://youtu.be/jNQXAC9IVRw"], "includeTimestamps": False}
)
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(row["status"], row.get("title"), row.get("transcript"))
```

### Channels, playlists and search

Besides `videos`, two optional inputs add videos in bulk.

| Input | What it takes |
|---|---|
| `channelsAndPlaylists` | Channel links in any form (`@handle`, `/channel/UC...`, `/c/name`, `/user/name`), playlist links with `list=...`, and bare `@handles`, `UC...` channel IDs or `PL...` playlist IDs. Channels go newest first |
| `searchQueries` | YouTube searches, each giving its top videos in YouTube's order |
| `maxVideosPerSource` | How many videos to take from each channel, playlist or search. Default 100, `0` means all, up to 20,000 |
| `includeShorts` | Channels only. Off: regular videos and past live streams. On: every upload |

Rows from these sources also carry `sourceType` (`channel`, `playlist` or `search`), `sourceId`, `sourceTitle` and `position`, and `input` holds the channel, playlist or query you gave.
A video met twice in one run, in `videos`, in two sources or twice in one playlist, is fetched and charged once, for the first input that reaches it.
Inputs count in order, `videos` first, then `channelsAndPlaylists`, then `searchQueries`.
Each later copy is a free `duplicate` row whose `duplicateOf` names that first input.
Listing a channel, a playlist or a search is free, and you pay only for transcripts.
A channel or playlist that does not exist comes back as one free `not_found` row.
A channel or playlist link put in `videos` comes back as a free `invalid_input` row whose message points to `channelsAndPlaylists`.

### What you get per video

| Field | What it holds |
|---|---|
| `status` | `ok`, `no_transcript`, `unavailable`, `invalid_input`, `duplicate` or `error` |
| `title`, `channel`, `channelId`, `durationSeconds` | Video details from the same request, at no extra cost |
| `publishedAt` | Exact publish time in UTC, such as `2009-10-25T06:57:33Z`. Empty in the rare case YouTube leaves it out |
| `language` | Language of the returned transcript |
| `isAutoGenerated` | `true` when the captions came from YouTube's speech recognition |
| `requestedLanguageFound` | `false` when the video has no captions in your language, so you got the original ones |
| `transcript` | The full transcript as one string |
| `wordCount` | Words in the transcript |
| `segments` | `[{ "start": 12.4, "duration": 3.1, "text": "..." }]`, when timestamps are on |

### Video details and output formats

Both are off by default and cost nothing extra.
They ride in the same row, so a transcript is still one charge.

`includeVideoDetails: true` adds:

| Field | What it holds |
|---|---|
| `viewCount`, `likeCount` | Exact counts at run time. `likeCount` can be empty when YouTube leaves it out |
| `commentCount` | Exact comment count. Empty when comments are turned off, as on NASA's videos |
| `category` | YouTube category, such as `Education` |
| `description` | The video description. Emails and phone numbers in it are replaced with `[email removed]` and `[phone removed]` |
| `tags` | The uploader's tags |
| `thumbnailUrl` | The largest thumbnail YouTube returns |
| `chapters` | `[{ "title", "start", "end", "url" }]` from the description's timestamps, by YouTube's rule: the list starts at 0:00 and has at least three rising entries. Each `url` opens the video at that chapter |

`outputFormats` takes any of these:

| Value | Adds | What it holds |
|---|---|---|
| `text` | `plainText` | The captions as paragraphs, one per line, split at pauses of 2 seconds or more and after about 120 words |
| `llm` | `llmText` | Markdown for an LLM: title, channel, date, URL, duration and caption type on top, a heading per chapter, a `[m:ss]` stamp per paragraph, and sound cues such as `[Music]` removed |
| `srt` | `srt` | The SubRip subtitle file as text |
| `vtt` | `vtt` | The WebVTT subtitle file as text |
| `chunks` | `chunks` | RAG chunks: one per YouTube chapter, with long chapters split evenly, or even pieces when the video has no chapters. Each has `index`, `chapter`, `start`, `end`, `wordCount`, `text` and a `url` that opens the video at that second |

`chunkSizeWords` sets the chunk size, 300 words by default.
Chunks are cut between caption lines, so one can run a little over.
In the final test the largest of 1,938 chunks held 319 words.
34 of the 87 videos had chapters.

### Input

```json
{
  "videos": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "https://youtu.be/jNQXAC9IVRw",
    "https://www.youtube.com/shorts/aqz-KE-bpKQ",
    "M7lc1UVf-VE"
  ],
  "language": "en",
  "includeTimestamps": true
}
```

The scraper accepts watch, youtu.be, Shorts, embed and live links, plus bare 11-character IDs.

### How language works

1. Human-made captions in your language come first.
2. Then YouTube's auto-generated captions in your language.
3. If neither exists, you get the video's original captions, and the row shows `requestedLanguageFound: false`.

### Output example

This is real output for the input above, from a run on 15 Oct 2026.
The `segments` array is cut to two entries here.

```json
{
  "input": "https://youtu.be/jNQXAC9IVRw",
  "videoId": "jNQXAC9IVRw",
  "url": "https://www.youtube.com/watch?v=jNQXAC9IVRw",
  "status": "ok",
  "publishedAt": "2005-04-24T03:31:52Z",
  "title": "Me at the zoo",
  "channel": "jawed",
  "channelId": "UC4QobU6STFB0P71PMvOGN5A",
  "durationSeconds": 19,
  "language": "en",
  "requestedLanguageFound": true,
  "isAutoGenerated": false,
  "transcript": "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, "duration": 2.16, "text": "All right, so here we are, in front of the elephants" },
    { "start": 5.318, "duration": 2.656, "text": "the cool thing about these guys is that they have really..." }
  ]
}
```

Big Buck Bunny in the same input has no captions, so its row comes back as `"status": "no_transcript"` and costs nothing.

### Output example with every option on

This row comes from the final test run on 16 Oct 2026, with `searchQueries: ["how transformers work", ...]`, `maxVideosPerSource: 5`, `includeVideoDetails: true` and `outputFormats: ["text", "llm", "srt", "vtt", "chunks"]`.
Long strings are cut and marked with `...`, `segments`, `srt`, `vtt` and `llmText` are left out, and the arrays are cut.
The real row has 17 tags, 4 chapters and 4 chunks.

```json
{
  "input": "how transformers work",
  "sourceType": "search",
  "sourceId": "how transformers work",
  "sourceTitle": "how transformers work",
  "position": 3,
  "videoId": "7RtBUEZbKmI",
  "url": "https://www.youtube.com/watch?v=7RtBUEZbKmI",
  "status": "ok",
  "publishedAt": "2020-04-01T10:00:07Z",
  "title": "GCSE Physics - How Transformers Work (2026/27 exams)",
  "channel": "Cognito",
  "channelId": "UCaGEe4KXZrjou9kQx6ezG2w",
  "durationSeconds": 259,
  "language": "en",
  "requestedLanguageFound": true,
  "isAutoGenerated": true,
  "transcript": "before we look at exactly how transformers work let's very quickly recap how they're used in the nat ...",
  "wordCount": 578,
  "viewCount": 607042,
  "likeCount": 9232,
  "commentCount": 393,
  "category": "Education",
  "description": "⭐️ Check out our website https://www.cognito.org/ ⭐️\n\n*** WHAT'S COVER ...",
  "tags": ["transformers", "step-up", "step-down"],
  "thumbnailUrl": "https://i.ytimg.com/vi/7RtBUEZbKmI/sddefault.jpg",
  "chapters": [
    { "title": "Intro & Role in National Grid", "start": 0, "end": 67, "url": "https://www.youtube.com/watch?v=7RtBUEZbKmI&t=0s" },
    { "title": "Transformer Structure", "start": 67, "end": 84, "url": "https://www.youtube.com/watch?v=7RtBUEZbKmI&t=67s" }
  ],
  "plainText": "before we look at exactly how transformers work let's very quickly recap how they're used in the nat ...",
  "chunks": [
    {
      "index": 0,
      "chapter": "Intro & Role in National Grid",
      "start": 0,
      "end": 65.52,
      "wordCount": 145,
      "text": "before we look at exactly how transformers work let's very quickly recap how they're used  ...",
      "url": "https://www.youtube.com/watch?v=7RtBUEZbKmI&t=0s"
    }
  ]
}
```

The start of the same row's `llmText`:

```
## GCSE Physics - How Transformers Work (2026/27 exams)
Channel: Cognito
Published: 2020-04-01T10:00:07Z
URL: https://www.youtube.com/watch?v=7RtBUEZbKmI
Duration: 4:19
Captions: en, auto-generated

### Intro & Role in National Grid [0:00]

[0:04] before we look at exactly how transformers work let's very quickly recap how they're used in the national grid ...
```

And of its `srt`:

```
1
00:00:04,799 --> 00:00:06,319
before we look at exactly how

2
00:00:06,319 --> 00:00:07,759
transformers work
```

### Use it from AI agents

The scraper works as a tool through the [Apify MCP server](https://mcp.apify.com) in Claude, Cursor, VS Code and other MCP clients.
Agents can pass a list of video URLs, a channel or a search and get back plain text they can summarise, quote or index, or chapter chunks with links they can cite.
Each call charges only for the transcripts it returns.

You can also call it through the [Apify API](https://docs.apify.com/api/v2) or the Python and JavaScript clients, and schedule it or connect it to Make, Zapier and n8n.

### Pricing

Prices are per 1,000 transcripts.

| Event | No discount | Bronze | Silver | Gold |
|---|---|---|---|---|
| Transcript returned | $3 | $2.70 | $2.40 | $2.10 |
| Video unavailable, no captions, invalid input or failed fetch | Free | Free | Free | Free |
| Listing a channel, playlist or search, video details and output formats | Free | Free | Free | Free |

Bronze, Silver and Gold are Apify Store discount tiers, and Apify's Pricing tab shows them as Starter, Scale and Business.
Apify decides which tier an account is on.

Set a maximum cost per run in the run options.
The scraper stops once that limit is reached.

### FAQ

**Why does a video show `no_transcript`?**
The uploader turned captions off and YouTube made no auto-generated ones.
That row isn't charged.

**Does it work for Shorts and live streams?**
Yes, if the video has captions.
Live streams get captions after they end.

**Does it use the official YouTube API?**
No.
It reads the same caption data the YouTube app shows.
Only use transcripts in ways that respect the rights of the video owners.

**What about videos with no captions at all?**
Speech-to-text for those is on the roadmap.
Open an issue on the Issues tab if you need it.

# Actor input Schema

## `videos` (type: `array`):

Video URLs or 11-character video IDs. Accepts watch, youtu.be, Shorts, embed and live links. For whole channels, playlists or searches use the next two fields.

## `channelsAndPlaylists` (type: `array`):

Optional. YouTube channel or playlist URLs, expanded into their videos. Accepts @handle, /channel/UC..., /c/... and /user/... links, playlist links with list=..., and bare @handles, UC channel IDs or PL playlist IDs. Channels go newest first. Each video is charged like any other transcript.

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

Optional. YouTube searches, each expanded into its top videos in YouTube's order. Each video is charged like any other transcript, and the search itself is free.

## `maxVideosPerSource` (type: `integer`):

How many videos to take from each channel, playlist or search. 0 means all videos, up to 20,000. Does not limit the videos field.

## `includeShorts` (type: `boolean`):

Channels only. Off: regular videos and past live streams. On: every upload, Shorts included.

## `language` (type: `string`):

ISO 639-1 code such as en, es, hi or de. Uses captions in this language when the video has them. Otherwise returns the video's original captions and sets requestedLanguageFound to false.

## `includeTimestamps` (type: `boolean`):

Adds a segments array with start time, duration and text for each caption line. The plain transcript is always included.

## `includeVideoDetails` (type: `boolean`):

Adds viewCount, likeCount, commentCount, category, description, tags, thumbnailUrl and chapters (from the description's timestamps, each with a url that opens the video there) to each transcript row, at no extra cost. Emails and phone numbers in descriptions are replaced with \[email removed] and \[phone removed].

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

Extra fields on each transcript row, at no extra cost. text: plainText, the captions as paragraphs, one per line. llm: llmText, Markdown with the title, channel, date and URL on top, chapter headings, a \[m:ss] stamp per paragraph and sound cues such as \[Music] removed. srt and vtt: the subtitle file as text. chunks: a chunks array for RAG, one chunk per YouTube chapter, long chapters split evenly, or even pieces when the video has no chapters, each with start, end, text, wordCount and a url that opens the video at that second.

## `chunkSizeWords` (type: `integer`):

Used by the chunks format. A chunk holds at most about this many words, cut between caption lines. Chapters shorter than this stay one chunk.

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

How many videos to fetch in parallel.

## `useProxy` (type: `boolean`):

Leave on. YouTube blocks cloud IPs.

## Actor input object example

```json
{
  "videos": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
  ],
  "maxVideosPerSource": 100,
  "includeShorts": false,
  "language": "en",
  "includeTimestamps": true,
  "includeVideoDetails": false,
  "outputFormats": [],
  "chunkSizeWords": 300,
  "maxConcurrency": 10,
  "useProxy": true
}
```

# Actor output Schema

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

One item per input video with status, title, channel, language, the full transcript text and optional timestamped segments. Only items with status ok are charged.

# 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 = {
    "videos": [
        "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("heyitsgautham/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 = { "videos": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"] }

# Run the Actor and wait for it to finish
run = client.actor("heyitsgautham/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 '{
  "videos": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
  ]
}' |
apify call heyitsgautham/youtube-transcript-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,heyitsgautham/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/DW0rIxmB1NdIXsepf/builds/24SQ9Y6udmVnZhnT2/openapi.json
