# YouTube Transcript Scraper 📝 (bulk, any language) (`tagadanar/youtube-transcript-extractor`) Actor

Transcripts for a whole list of YouTube videos in one run: plain text for your LLM, timed segments for subtitles and search, plus title, channel, duration and views. Picks the language you ask for. Videos with no captions are never charged. No API key, platform usage included.

- **URL**: https://apify.com/tagadanar/youtube-transcript-extractor.md
- **Developed by:** [Tagada Data](https://apify.com/tagadanar) (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.80 / 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.
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: captions for a whole list of videos

Paste a list of YouTube links and get the transcript of every one back as clean JSON. Plain text ready to drop into an LLM, timed segments ready for subtitles or search, and the video's title, channel, duration and view count on the same row.

It picks the language you asked for. If the video has no track in that language but YouTube can translate one, it translates it, and the row says so. If a video has no captions at all, or is private, or was taken down, you get a row saying which, and you are not charged for it.

One flat price per transcript delivered. Platform usage (residential proxies, compute) is already in that price, so the number you see is the number you pay. No API key, no OAuth, no browser, no cookies to paste.

### What it costs against the rest of the category

| | This actor | The most-used YouTube transcript actor on Apify | YouTube Data API |
| --- | --- | --- | --- |
| Price | $4 per 1,000 transcripts, all-in | $10 per 1,000 | Captions endpoint needs the channel owner's OAuth |
| Platform usage | Included | Usually billed on top | n/a |
| Videos with no captions | Free, with the reason | Usually billed as a result | n/a |
| Language choice | Ordered list, falls back to machine translation | Usually whatever the video defaults to | n/a |
| Timed segments | Yes, with plain text on the same row | Often one or the other | n/a |
| Setup | Paste links | Paste links | Google Cloud project, OAuth consent, channel ownership |

The public YouTube Data API can list caption tracks but will only hand you the caption *body* if you are authenticated as the channel owner, which is why nobody uses it for research. This actor reads the same public caption tracks the YouTube player itself downloads.

### What you get

One record per video:

| Field | Description |
| --- | --- |
| `videoId` | The 11-character YouTube ID |
| `url` | Canonical watch link |
| `status` | `ok`, `no-captions`, `blocked`, `unavailable`, `private`, `members-only`, `age-restricted`, `geo-blocked`, `not-started`, `unplayable` or `error` |
| `statusMessage` | Why there is no transcript, in YouTube's own words where it has any |
| `title` | Video title |
| `channel`, `channelId`, `channelUrl` | Who published it |
| `durationSeconds` | Length in seconds |
| `viewCount` | Views at the moment of the run |
| `isLive` | True for a live stream or a video that was one |
| `thumbnailUrl` | Largest thumbnail YouTube lists |
| `language` | Language of the transcript you actually got |
| `languageName` | Same, in YouTube's wording, e.g. `English (auto-generated)` |
| `isAutoGenerated` | True when the captions came from speech recognition rather than a human |
| `isTranslated` | True when YouTube machine-translated the track into your language |
| `matchedPreferredLanguage` | False when none of your languages was available and you got the video's own track instead |
| `availableLanguages` | Every caption track the video has, so you can re-run for another one |
| `cueCount` | Number of timed segments |
| `characterCount` | Length of the transcript in characters |
| `transcriptTruncated` | True only on extremely long videos (see the limit below) |
| `transcript` | The whole transcript as one string |
| `segments` | `[{ start, duration, text }]`, seconds as decimals |
| `capturedAt` | When the run collected the row (ISO) |

`transcript` is null when you asked for segments only, and `segments` is null when you asked for text only.

### Who uses this

- **People building on LLMs.** Turn a channel, a playlist or a research list into a text corpus for summarising, RAG, fine-tuning or classification. The plain-text field is one string per video, so it goes straight into a prompt.
- **SEO and content teams.** Read what competitors actually say in their videos, mine it for keywords and questions, and repurpose long videos into articles and newsletters.
- **Market and product researchers.** Pull review and unboxing videos for a product line and search the transcripts for the complaint that keeps coming up.
- **Course and podcast producers.** Get timed segments to build chapter markers, search inside your own back catalogue, or ship subtitle files.
- **Accessibility and localisation.** Check which of your videos have human captions and which are relying on speech recognition, and get a translated track for the languages you publish in.

### Input

Everything is optional except the list of videos.

| Field | What it does |
| --- | --- |
| `videoUrls` | The videos, up to 650 per run. Watch links, `youtu.be` links, Shorts links, embed links and bare 11-character IDs, mixed freely. Extra parameters like `?t=` or `&list=` are ignored, and duplicates are removed so you never pay twice for the same video |
| `preferredLanguages` | Language codes in order of preference, e.g. `["fr", "en"]`. `en` also matches `en-GB` and `en-US` |
| `includeAutoGenerated` | Off means human-written subtitles only. Videos with nothing but speech-recognition captions are then reported as `no-captions` and not charged |
| `outputFormat` | `both` (default), `text`, or `segments` |
| `maxVideos` | Stop after this many videos, whatever the list contains (1–650, default 200). Useful when you pipe another actor's dataset in |

The 650 ceiling is not arbitrary: a video costs about 5.5 seconds end to end, so
650 is what fits in the platform's default 3600-second run timeout. The run also
watches its own clock — when it runs out, everything already collected is
charged and pushed, and the run finishes with a status saying how many videos
were done and that raising the run timeout (or lowering `maxVideos`) is the fix.
It never gets killed mid-list.

#### A few links, English

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

#### French if it exists, English otherwise, human captions only

```json
{
  "videoUrls": [
    "aircAruvnKk",
    "PkZNo7MFNFg"
  ],
  "preferredLanguages": ["fr", "en"],
  "includeAutoGenerated": false,
  "outputFormat": "text"
}
```

#### Text for an LLM, bounded spend

```json
{
  "videoUrls": ["https://www.youtube.com/watch?v=aircAruvnKk"],
  "preferredLanguages": ["en"],
  "outputFormat": "text",
  "maxVideos": 50
}
```

### Pricing

| Event | Price |
| --- | --- |
| Transcript extracted | $0.004 per video, i.e. **$4 per 1,000 transcripts** |
| Run start | $0.001 per run |

Store discounts apply on top: Bronze $0.0036, Silver $0.0032, Gold $0.0028 per transcript.

Two things that make the bill smaller than it looks:

- **Misses are free.** A video with no captions, a private video, a deleted video, a members-only video, a video YouTube's bot check refused: all of these come back as a row explaining what happened, and none of them is charged. You pay for transcripts, not for attempts.
- **Platform usage is included.** Residential proxies and compute are on us. There is no proxy setting in the input because there is nothing for you to pay for or configure.

### Limits and honest caveats

- **Only videos that already have captions.** This actor reads YouTube's own caption tracks, human-written or speech-recognised. It does not transcribe audio itself, so a video with captions turned off has none to give and is reported as `no-captions`.
- **Very long videos are capped.** A four-hour lecture is roughly 3,000 segments and 150,000 characters and comes back whole. Past 400,000 characters or 40,000 segments the transcript is cut and `transcriptTruncated` is true, so one enormous video cannot blow up your dataset.
- **Private, members-only and age-restricted videos cannot be read** without an account, and this actor never signs in to anything. They come back with the matching status.
- **A live stream has no transcript until it ends** and YouTube has processed captions for the recording.
- **Machine translation is YouTube's, not ours.** When `isTranslated` is true the text quality is whatever YouTube's translation gives you, which is fine for search and gisting and not for publishing.
- **View counts are a snapshot** taken at the moment of the run.

### Frequently asked questions

**How do I get the transcript of a YouTube video without an API key?**
Run this actor with the video's link. There is no key, no OAuth and no Google Cloud project. The public YouTube Data API will not give you caption text unless you own the channel.

**Can I get transcripts for a whole YouTube channel or playlist?**
Give it the list of video links. Any actor or export that produces YouTube URLs can be piped straight in: the input accepts objects with a `url`, `videoUrl` or `videoId` key, so another scraper's dataset works as-is.

**Which languages are supported?**
Whatever the video has. `availableLanguages` on every row lists them, so you can re-run for a different one. If your language is missing but YouTube offers a machine translation, you get that instead and the row says `isTranslated: true`.

**Can I get subtitles with timestamps, like an SRT file?**
Yes, in `segments`: each one has `start` and `duration` in seconds and its own text. Building an SRT or VTT file from that is a few lines of code.

**How accurate are auto-generated captions?**
They are YouTube's speech recognition. Good on clear studio audio, weaker on accents, music and crosstalk, and they carry no punctuation on some older videos. `isAutoGenerated` tells you which rows to treat carefully.

**Do you charge for videos that have no transcript?**
No. Only a delivered transcript is charged, plus the $0.001 run start fee.

**Is scraping YouTube transcripts legal?**
This reads publicly available caption tracks that YouTube serves to any viewer, the same data you see by opening "Show transcript" under a video. Copyright in the underlying content stays with its owner: use transcripts for research, analysis and indexing, and check your own rights before republishing them.

**Why did a video come back as `blocked`?**
YouTube occasionally shows an anti-bot check instead of the video data. The actor rotates to a new exit and retries several times before giving up, and a blocked video is never charged. Re-running it usually works.

### Related actors

- **[Apple App Store Reviews](https://apify.com/tagadanar/apple-app-store-reviews)** and **[Google Play Reviews](https://apify.com/tagadanar/google-play-reviews)** for what users write about an app.
- **[Trustpilot Reviews](https://apify.com/tagadanar/trustpilot-reviews)** and **[Capterra Reviews](https://apify.com/tagadanar/capterra-reviews)** for the same job on companies and software.
- **[Brand News Monitor](https://apify.com/tagadanar/brand-news-monitor)** to watch what is being published about a brand.
- **[Telegram Channel Monitor](https://apify.com/tagadanar/telegram-channel-monitor)** and **[Threads Scraper](https://apify.com/tagadanar/threads-scraper)** for social listening on other surfaces.

***

YouTube transcript scraper, YouTube captions API, bulk YouTube transcripts, YouTube subtitles to JSON, YouTube transcript extractor, download YouTube subtitles, YouTube video transcript for LLM, YouTube transcript to text, YouTube auto-generated captions, YouTube transcript API without key, YouTube Shorts transcript, YouTube transcript translation.

# Actor input Schema

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

One per line. Watch links (<code>https://www.youtube.com/watch?v=…</code>), short links (<code>https://youtu.be/…</code>), Shorts links, embed links and bare 11-character video IDs all work, and you can mix them. Extra parameters like <code>?t=</code> or <code>\&list=</code> are ignored. Duplicates are removed, so you are never charged twice for the same video.

## `preferredLanguages` (type: `array`):

Language codes in the order you want them, for example <code>en</code> then <code>es</code>. A plain code like <code>en</code> also matches regional tracks such as <code>en-GB</code>. If none of your languages has its own caption track, the transcript is machine-translated into the first one YouTube can translate to, and the row says so. If even that is impossible you still get whatever the video has, flagged with <code>matchedPreferredLanguage: false</code>. Leave empty to always take the video's own default track.

## `includeAutoGenerated` (type: `boolean`):

Most videos only have YouTube's speech-recognition captions. Turn this off to take human-written subtitles only, and skip videos that have none.

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

Plain text is what you paste into an LLM; timed segments carry a start time and a duration per line, for subtitles, search and video editing.

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

Stop after this many videos, whatever the list contains. Useful when you pipe another scraper's dataset in and want to bound the spend. You are only charged for transcripts actually delivered. The ceiling is 650, which is what a default 3600-second run can actually finish (about 5.5 seconds per video); for more than that, raise the run timeout and split the list across runs.

## Actor input object example

```json
{
  "videoUrls": [
    "https://www.youtube.com/watch?v=aircAruvnKk",
    "https://youtu.be/jNQXAC9IVRw"
  ],
  "preferredLanguages": [
    "en"
  ],
  "includeAutoGenerated": true,
  "outputFormat": "both",
  "maxVideos": 200
}
```

# Actor output Schema

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

One item per video in the default dataset.

# 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=aircAruvnKk",
        "https://youtu.be/jNQXAC9IVRw"
    ],
    "preferredLanguages": [
        "en"
    ],
    "outputFormat": "both",
    "maxVideos": 200
};

// Run the Actor and wait for it to finish
const run = await client.actor("tagadanar/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=aircAruvnKk",
        "https://youtu.be/jNQXAC9IVRw",
    ],
    "preferredLanguages": ["en"],
    "outputFormat": "both",
    "maxVideos": 200,
}

# Run the Actor and wait for it to finish
run = client.actor("tagadanar/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=aircAruvnKk",
    "https://youtu.be/jNQXAC9IVRw"
  ],
  "preferredLanguages": [
    "en"
  ],
  "outputFormat": "both",
  "maxVideos": 200
}' |
apify call tagadanar/youtube-transcript-extractor --silent --output-dataset

```

## MCP server setup

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