# YouTube Transcript Scraper – Captions to Text for RAG & LLMs (`moyadata/youtube-transcript-scraper`) Actor

Turn YouTube subtitles/captions into clean text or timestamped segments by video URL or ID. No proxies, no browser, no bans.

- **URL**: https://apify.com/moyadata/youtube-transcript-scraper.md
- **Developed by:** [SERP King](https://apify.com/moyadata) (community)
- **Categories:** Automation, Developer tools, Social media
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 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.

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 – Captions to Text for RAG & LLMs

Turn any YouTube video's subtitles or captions into clean, joined text, timestamped segments, or ready-to-save SRT/VTT subtitle files — built for feeding video content into RAG pipelines, LLM apps, and summarization workflows. Paste a video URL, a `youtu.be` link, a Shorts link, or just the 11-character video ID. This actor calls a licensed SERP data provider directly — **no proxies, no headless browser, no YouTube rate limits or IP bans.**

### What data you get

Each result item includes:

- `videoId`, `videoUrl` — the video's ID and canonical `youtube.com/watch?v=` URL
- `title` — the video's title
- `language`, `translatedTo` — the subtitle language returned, and the language it was translated into (if you set `translateTo`)
- `subtitlesCount` — how many subtitle lines the video has
- `durationSec` — video duration inferred from the last subtitle's end time
- `text` — the full transcript, subtitle lines joined with spaces
- `segments` — array of `{start, end, text}` per subtitle line (only when `includeTimestamps` is on)
- `srt`, `vtt` — ready-to-save subtitle file strings (only when `outputFormat` requests them — see Input)
- `status`, `scrapedAt`

### Why this scraper

- **No bans, ever** — this actor never touches youtube.com directly. It calls a licensed SERP data provider's subtitles API, so there's no IP to block and no CAPTCHA to solve.
- **Cheap** — a flat **$0.005 per transcript**, and videos without captions cost nothing (see Pricing).
- **Fast by default** — `mode: "auto"` picks `fast` (live, a few seconds per video) for runs of 50 videos or fewer, and `standard` (queued, cheaper) for bigger batches — see Modes.
- **Translation built in** — set `translateTo` to get subtitles translated into another language, upstream, without a separate translation step.
- **SRT/VTT out of the box** — set `outputFormat` to get a ready-to-save subtitle file alongside the plain text, no separate conversion step.
- **Batch-friendly** — up to 1,000 videos per run.
- **No silent gaps** — every video that doesn't produce a transcript (no captions, wrong language, a stuck task) is listed in the `FAILED_VIDEOS` record with why, so a batch of 200 URLs never comes back as an unexplained 183 rows.

### Use cases

- **RAG / summarization pipelines** — feed `text` straight into a vector store or an LLM prompt for question-answering or summarizing video content at scale.
- **Content repurposing** — turn a video transcript into a blog post, show notes, or social copy without manually re-watching and typing it out.
- **Subtitles for clips** — set `outputFormat: "srt"` or `"vtt"` to get a subtitle file ready to attach to a re-cut clip or upload alongside a repost.

### Pricing

This actor uses Apify's **pay-per-event** pricing, flat across all plans. You are charged for:

| Event | What it means | Price |
|---|---|---|
| `apify-actor-start` | Apify's platform-billed "run started" event, charged once per run automatically — no code call needed | $0.00005 |
| `apify-default-dataset-item` ("Transcript" in Console) | Charged once per video that has a transcript, written to the output dataset | $0.005 |

**Videos without subtitles are never charged** — they don't produce a dataset row at all, so you only pay for transcripts you actually get.

**Worked example:** transcribing 1,000 videos that all have captions costs `1,000 × $0.005 + $0.00005 ≈ $5.00`. If 100 of those 1,000 have no subtitles, you pay for the 900 that succeeded: `900 × $0.005 + $0.00005 ≈ $4.50`.

Apify's free plan includes monthly platform credit that, on its own, covers roughly the first **~1,000 transcripts** with this actor before you'd need to add a payment method.

### Modes

| Mode | Upstream call | Wall-clock | Cap | When to use |
|---|---|---|---|---|
| `auto` (default) | `fast` for ≤50 videos, `standard` above that | See below | 1,000 videos/run | Picks the right mode for the batch size automatically — no need to think about it for typical runs. |
| `standard` | Queued (`task_post` + polling) | ~5 minutes per batch of up to 100 videos | 1,000 videos/run | Cheapest upstream cost per video; the right choice once `auto` would pick it anyway (>50 videos). |
| `fast` | Live (synchronous) | A few seconds per video | 50 videos/run | Small batches where you need the transcript back immediately (interactive use, a quick one-off check). Priced higher per video upstream to fund the instant response, capped at 50/run so a bad input doesn't run up a live-mode bill. |

**Expected total wall-clock** (with `auto`, or an explicit mode): 10 videos ≈ a few seconds (`fast`); 100 videos ≈ ~5 minutes (`standard`, one batch); 1,000 videos ≈ ~50 minutes (`standard`, 10 batches processed one after another).

### Input

Minimal example:

```json
{
  "videoUrls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "https://youtu.be/jNQXAC9IVRw"
  ],
  "language": "en"
}
```

| Field | Type | Default | Description |
|---|---|---|---|
| `videoUrls` | array of strings | (see prefill) | YouTube video URLs — `watch`, `youtu.be`, Shorts, or embed links — or bare 11-character video IDs. Max 1,000 per run, 50 when `mode` is explicitly set to `"fast"`. |
| `language` | string | `"en"` | Language code of the original subtitles/captions to fetch. |
| `translateTo` | string | — | Optional language code to translate the subtitles into. Leave empty to keep the original language. |
| `includeTimestamps` | boolean | `true` | Adds the `segments` array (per-line start/end/text) to each output item, alongside the joined `text`. |
| `mode` | string | `"auto"` | `"auto"`, `"standard"`, or `"fast"` — see Modes. |
| `outputFormat` | string | `"text"` | `"text"`, `"srt"`, `"vtt"`, or `"all"` — adds an `srt` and/or `vtt` subtitle-file string to each output item. |

### Output

A real dataset item, captured from an actual run against "Me at the zoo" (`jNQXAC9IVRw`, YouTube's first-ever upload) in September 2026:

```json
{
  "videoId": "jNQXAC9IVRw",
  "videoUrl": "https://www.youtube.com/watch?v=jNQXAC9IVRw",
  "title": "Me at the zoo",
  "language": "en",
  "translatedTo": null,
  "subtitlesCount": 6,
  "durationSec": 18.88,
  "text": "All right, so here we are, in front of the\nelephants the cool thing about these guys is that they\nhave really... really really long trunks and that's cool (baaaaaaaaaaahhh!!) and that's pretty much all there is to\nsay",
  "segments": [
    { "start": 1.2, "end": 3.36, "text": "All right, so here we are, in front of the\nelephants" },
    { "start": 5.318, "end": 7.97, "text": "the cool thing about these guys is that they\nhave really..." },
    { "start": 7.974, "end": 12.62, "text": "really really long trunks" },
    { "start": 12.616, "end": 14.37, "text": "and that's cool" },
    { "start": 14.421, "end": 15.73, "text": "(baaaaaaaaaaahhh!!)" },
    { "start": 16.881, "end": 18.88, "text": "and that's pretty much all there is to\nsay" }
  ],
  "status": "ok",
  "scrapedAt": "2026-09-08T05:04:41Z"
}
```

| Field | Type | Description |
|---|---|---|
| `videoId` | string | 11-character YouTube video ID |
| `videoUrl` | string | Canonical `https://www.youtube.com/watch?v=<id>` URL |
| `title` | string | null | Video title |
| `language` | string | null | Subtitle language actually returned (falls back to your requested `language` if the upstream response doesn't echo one) |
| `translatedTo` | string | null | Language the subtitles were translated into, `null` unless `translateTo` was set |
| `subtitlesCount` | integer | Number of subtitle lines in the video |
| `durationSec` | float | Video duration, inferred from the last subtitle's end time |
| `text` | string | Full transcript: every subtitle line's text, joined with spaces, empty lines skipped |
| `segments` | array of objects | null | `null` unless `includeTimestamps` is `true`, in which case a `{start, end, text}` object per subtitle line |
| `srt` | string | null | `null` unless `outputFormat` is `"srt"` or `"all"` — the full transcript as an SRT subtitle file |
| `vtt` | string | null | `null` unless `outputFormat` is `"vtt"` or `"all"` — the full transcript as a WebVTT subtitle file |
| `status` | string | Always `"ok"` — videos without subtitles never produce a row (see Limits) |
| `scrapedAt` | string | ISO 8601 UTC timestamp |

### Limits

- Videos with no subtitles/captions in the requested language, or that don't exist, produce **no dataset row and are not charged**. They're never silently dropped, though: every one is listed in the `FAILED_VIDEOS` key-value store record (`videoId`, `videoUrl`, `status` — `no_subtitles`, `unsupported_language`, `timed_out`, or `error` — and a `message`), and counted in `STATS` under `videosWithoutSubtitles`.
- `standard` mode (and `auto` above 50 videos): up to 1,000 videos per run, processed in batches of up to 100, polled for up to 15 minutes per batch. A batch still stuck after 15 minutes is skipped (counted in STATS as `timedOut`, and listed in `FAILED_VIDEOS` with that status) — this is rare and upstream-side.
- `fast` mode, whether picked by `auto` or set explicitly: capped at 50 videos per run (see Modes for why).
- This actor does not distinguish auto-generated captions from manually uploaded ones — the upstream data source returns whichever subtitle track YouTube serves for the requested `language`, without labeling its origin.
- Private, age-restricted, or region-locked videos may return no subtitles even if they otherwise have captions.

### FAQ

**Is this legal?**
The actor retrieves publicly available subtitle/caption data through a licensed SERP data provider, the same category of data source used by SEO and content-research tools. You are responsible for how you use the extracted text under your local regulations and YouTube's own terms of service.

**How is this different from other YouTube transcript tools on Apify?**
Most transcript scrapers drive a browser or an unofficial YouTube endpoint directly, which means occasional blocks and rate limits. This actor calls a licensed SERP data API instead: no proxies, no browser, no bans — the risk of getting blocked is the data provider's problem, not yours.

**What languages are supported?**
Any language YouTube has subtitles/captions for. Set `language` to the subtitle language code you want (e.g. `"en"`, `"es"`, `"pt"`), and optionally `translateTo` to get a machine translation into a different language.

**Do I get charged for videos without subtitles?**
No. A video with no subtitles in the requested language produces no dataset row at all, so `apify-default-dataset-item` is never charged for it. Videos without captions are listed in the `FAILED_VIDEOS` record and never billed — check it (alongside `STATS.videosWithoutSubtitles`) to see exactly which videos didn't come back and why.

**Can auto-generated and manually uploaded captions be told apart?**
No — the underlying data source returns whichever subtitle track YouTube serves for the language you request, without labeling whether it's auto-generated or manually authored. If that distinction matters for your use case, treat all transcripts as potentially auto-generated (which is the overwhelming majority of YouTube's caption tracks in practice).

# Actor input Schema

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

YouTube video URLs (watch, youtu.be, Shorts, embed) or bare 11-character video IDs. Max 1,000 per run (50 when `mode` is explicitly set to `fast`).

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

Language code of the original subtitles/captions to fetch, e.g. "en", "es".

## `translateTo` (type: `string`):

Language code to translate the subtitles into, e.g. "es", "pt". Leave empty to keep the original language.

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

Include a `segments` array (start/end/text per subtitle line) on each output item, in addition to the joined `text`.

## `mode` (type: `string`):

`auto` (default): `fast` for 50 videos or fewer, `standard` above that. `standard`: queued, ~5 min wall-clock per batch of up to 100 videos, $0.0018/video upstream cost. `fast`: live, a few seconds per video, $0.006/video upstream cost -- capped at 50 videos per run.

## `outputFormat` (type: `string`):

`text` (default): only the joined `text` field. `srt`/`vtt`: also add a ready-to-save subtitle file string in that format, built from the same subtitle segments. `all`: add both `srt` and `vtt`.

## Actor input object example

```json
{
  "videoUrls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "https://youtu.be/jNQXAC9IVRw"
  ],
  "language": "en",
  "includeTimestamps": true,
  "mode": "auto",
  "outputFormat": "text"
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `stats` (type: `string`):

No description

## `failedVideos` (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 = {
    "videoUrls": [
        "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
        "https://youtu.be/jNQXAC9IVRw"
    ],
    "language": "en",
    "mode": "auto",
    "outputFormat": "text"
};

// Run the Actor and wait for it to finish
const run = await client.actor("moyadata/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 = {
    "videoUrls": [
        "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
        "https://youtu.be/jNQXAC9IVRw",
    ],
    "language": "en",
    "mode": "auto",
    "outputFormat": "text",
}

# Run the Actor and wait for it to finish
run = client.actor("moyadata/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 '{
  "videoUrls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "https://youtu.be/jNQXAC9IVRw"
  ],
  "language": "en",
  "mode": "auto",
  "outputFormat": "text"
}' |
apify call moyadata/youtube-transcript-scraper --silent --output-dataset

```

## MCP server setup

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