# YouTube Transcript Scraper — Captions & Subtitles for AI (Bulk) (`yasaslive/youtube-transcript-scraper`) Actor

Extract YouTube transcripts, captions and subtitles in bulk from video URLs or IDs. Manual or auto-generated tracks, any language, optional translation, plain text, timestamped segments, SRT or VTT. Ready for AI, RAG and LLM pipelines.

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

## Pricing

$4.00 / 1,000 transcript extracteds

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?

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 — Captions & Subtitles for AI (Bulk)

Turn any list of YouTube videos into clean transcripts. Paste watch links, youtu.be links, Shorts, embeds, live URLs or plain video IDs, and get back one dataset item per video with the full transcript as plain text, plus optional timestamped segments, SRT and WebVTT. Manual subtitles are used when they exist, with auto-generated captions as a fallback. YouTube's built-in translation can return the transcript in another language.

You pay only for videos where a transcript was actually extracted. Videos with no captions, private or removed videos, and bad links are reported in the dataset for free.

### What you can use it for

- **RAG and AI knowledge bases**: index a channel's tutorials, lectures or podcasts into a vector store. `segments` keeps timestamps so answers can link back to the exact moment in the video.
- **LLM summarization and Q\&A**: feed `transcriptText` straight into a prompt to summarize videos, pull out key points or generate show notes.
- **Content repurposing**: turn videos into blog posts, newsletters, social threads or chapter markers.
- **Research and monitoring**: analyze what creators, competitors or politicians said, across hundreds of videos at once.
- **Subtitles**: download ready-to-use `.srt` or `.vtt` bodies, optionally translated.
- **Dataset building**: collect speech text with language and "auto-generated" labels for NLP work.

### How it works

For each video the Actor asks YouTube's mobile app API for the caption track list, picks the best track and downloads it:

1. A manual track in your **preferred language** (an exact code like `en` first, then a regional variant like `en-GB`)
2. An **auto-generated** track in that language, if allowed
3. Any other manual track
4. Any other auto-generated track, if allowed
5. If **Translate to** is set: a native track in that language wins. Otherwise the picked track is machine-translated by YouTube, when YouTube allows translation for it.

If the first method fails, the Actor tries a second one automatically. Failed requests are retried with backoff on a fresh proxy session. A problem with one video never stops the run.

### Input

| Field | Default | Description |
|---|---|---|
| `videoUrls` | `["https://www.youtube.com/watch?v=jNQXAC9IVRw"]` | Video URLs or 11-character IDs. Duplicates are fetched once. |
| `preferredLanguage` | `en` | Caption language to prefer (`en`, `es`, `de`, `pt-BR`, ...). |
| `allowAutoGenerated` | `true` | Use automatic captions when no human-made subtitles exist. |
| `translateTo` | (empty) | Machine-translate the transcript into this language. |
| `outputFormat` | `["text", "segments"]` | Plain text is always included. Add `segments`, `srt` and/or `vtt`. |
| `includeVideoDetails` | `true` | Add channel name, channel ID, duration and view count. |
| `maxConcurrency` | `5` | Videos processed in parallel. |
| `proxyConfiguration` | Apify Proxy | Use the RESIDENTIAL group if YouTube shows bot checks. |

```json
{
    "videoUrls": [
        "https://www.youtube.com/watch?v=jNQXAC9IVRw",
        "https://youtu.be/dQw4w9WgXcQ",
        "https://www.youtube.com/shorts/abcdefghijk"
    ],
    "preferredLanguage": "en",
    "allowAutoGenerated": true,
    "outputFormat": ["text", "segments", "srt"]
}
```

### Output

One item per video. A successful item:

```json
{
    "status": "ok",
    "videoId": "jNQXAC9IVRw",
    "url": "https://www.youtube.com/watch?v=jNQXAC9IVRw",
    "inputUrl": "https://youtu.be/jNQXAC9IVRw",
    "title": "Me at the zoo",
    "channelName": "jawed",
    "channelId": "UC4QobU6STFB0P71PMvOGN5A",
    "durationSeconds": 19,
    "viewCount": 380412245,
    "availableLanguages": [
        { "languageCode": "en", "languageName": "English", "isAutoGenerated": false, "isTranslatable": true },
        { "languageCode": "de", "languageName": "German", "isAutoGenerated": false, "isTranslatable": true }
    ],
    "language": "en",
    "languageName": "English",
    "originalLanguage": "en",
    "isAutoGenerated": false,
    "isTranslated": false,
    "transcriptText": "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, that's cool and that's pretty much all there is to say",
    "segments": [
        { "start": 1.2, "duration": 2.16, "text": "All right, so here we are, in front of the elephants" },
        { "start": 5.318, "duration": 7.031, "text": "the cool thing about these guys is that they have really, really, really long trunks" }
    ],
    "srt": "1\n00:00:01,200 --> 00:00:03,360\nAll right, so here we are, in front of the elephants\n\n...",
    "segmentCount": 4,
    "wordCount": 39,
    "sourceClient": "IOS",
    "scrapedAt": "2026-10-16T05:00:12.150Z"
}
```

(The transcript in this example is shortened.) `start` and `duration` are in seconds. `srt` and `vtt` appear only when you select them.

A video without a usable transcript still gets an item, so you can see what happened to every input:

```json
{
    "status": "no_transcript",
    "videoId": "NoCaps00001",
    "url": "https://www.youtube.com/watch?v=NoCaps00001",
    "title": "Silent timelapse of clouds",
    "availableLanguages": [],
    "reason": "This video has no captions or subtitles.",
    "scrapedAt": "2026-10-16T05:00:12.160Z"
}
```

| `status` | Meaning | Charged |
|---|---|---|
| `ok` | Transcript extracted | Yes |
| `no_transcript` | The video has no captions, or only auto-generated ones while those are turned off | No |
| `unavailable` | Private, removed, age-restricted or region-blocked video (the `reason` says which) | No |
| `error` | YouTube could not be reached after retries. Try again later or use a residential proxy | No |
| `invalid_input` | The input line is not a YouTube video URL or ID | No |

Run statistics (counts per status, how many videos were charged, and whether the run stopped early) are saved to the `RUN_STATS` record in the default key-value store.

### Pricing

Pay per event: **$0.004 per transcript extracted** ($4 per 1,000 videos), plus a small amount of Apify platform usage. You are charged only for items with `status: "ok"`. If you set a maximum cost per run, the Actor stops starting new videos once the limit is reached and keeps everything collected so far.

### FAQ

**Does it work for videos without captions?**
No. It reads the captions YouTube already has (uploaded or auto-generated). It does not transcribe audio itself. Such videos come back as `no_transcript`, free of charge.

**Which languages are supported?**
Any language YouTube has captions for. Set `preferredLanguage` to a language code. If that language is missing, you get the best other track, and the item's `language` field says which one. Use `translateTo` to get a machine translation from YouTube.

**How do I tell auto-generated captions from human ones?**
Check `isAutoGenerated`. Turn off **Allow auto-generated captions** to accept only human-made subtitles.

**Can I scrape a whole channel or playlist?**
This Actor takes video URLs. Collect the video URLs with a channel or playlist scraper first, then pass them in.

**Is the text ready for an LLM?**
Yes. Line breaks inside captions are joined, HTML entities are decoded and layout-only cues are removed. `transcriptText` is one clean string, and `segments` keeps the timing for chunking with timestamps.

**What about Shorts and live streams?**
Shorts, `/live/` and embed URLs are supported. Live streams usually get captions only after the stream ends.

**Why do I see `error` items?**
YouTube sometimes rate-limits or bot-checks datacenter IPs. The Actor retries on new proxy sessions, but if it keeps happening, switch the proxy to the RESIDENTIAL group and rerun those videos.

**Is this legal?**
The Actor reads publicly available caption data, the same data YouTube's own apps display. You are responsible for how you use the transcripts, including copyright and YouTube's Terms of Service.

### Limitations

- Private, members-only and some age-restricted videos cannot be read.
- Transcript accuracy depends on YouTube's captions. Auto-generated captions have no punctuation in many languages.
- YouTube changes its internal APIs from time to time. If transcripts stop coming through, please open an issue so it can be fixed quickly.

# Actor input Schema

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

One video per line: watch URLs, youtu.be links, Shorts, embed and live URLs, or bare 11-character video IDs. Duplicates are fetched once.

## `preferredLanguage` (type: `string`):

Language code of the caption track to prefer, e.g. en, es, de, pt-BR. If the video has no track in this language, the best other track is used and its language is reported in the output.

## `allowAutoGenerated` (type: `boolean`):

Use YouTube's automatic speech-recognition captions when no manually uploaded subtitles are available. Turn off to accept only human-made subtitles.

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

Language code to machine-translate the transcript into using YouTube's built-in caption translation, e.g. en, fr, ja. A native track in this language is used when one exists. Leave empty to keep the original language.

## `outputFormat` (type: `array`):

Plain text (transcriptText) is always included. Add timestamped segments, an SRT file body and/or a WebVTT file body.

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

Add channel name, channel ID, duration and view count to every item.

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

How many videos to process in parallel.

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

YouTube blocks datacenter IPs with a bot check, so Apify RESIDENTIAL proxy is the default and strongly recommended.

## Actor input object example

```json
{
  "videoUrls": [
    "https://www.youtube.com/watch?v=jNQXAC9IVRw"
  ],
  "preferredLanguage": "en",
  "allowAutoGenerated": true,
  "outputFormat": [
    "text",
    "segments"
  ],
  "includeVideoDetails": true,
  "maxConcurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

No description

## `allFields` (type: `string`):

No description

## `stats` (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=jNQXAC9IVRw"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("yasaslive/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=jNQXAC9IVRw"],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("yasaslive/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=jNQXAC9IVRw"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call yasaslive/youtube-transcript-scraper --silent --output-dataset

```

## MCP server setup

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