# Bulk YouTube Transcript Scraper & Transcript API (`apimint/youtube-transcript-scraper`) Actor

Get transcripts of all videos in a YouTube channel, playlist or Shorts feed, or any list of video URLs. Bulk YouTube transcript API: timestamped segments, plain text, captions and subtitles, language, views, AI-ready chunks. Clear reason for every video without captions, never charged. No login.

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

## Pricing

from $3.80 / 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 Scraper

Get the transcript of any public YouTube video, Short, channel or playlist: timestamped segments and plain text, the caption language and whether it was typed by the creator or auto-generated, plus the video's title, channel, publish date, duration, views, likes and chapters. Paste many URLs at once. Every video without a transcript gets a row that says why, and is never charged.

No YouTube account, no API key, no cookies.

| What you pay | Free plan | Starter | Scale | Business |
|---|---|---|---|---|
| Per 1,000 transcripts (videos up to 1 hour) | $4.50 | $4.20 | $4.00 | $3.80 |
| Per 1,000 extra hours of video (long videos and podcasts) | $0.50 | $0.50 | $0.50 | $0.50 |

Proxy traffic is included. Videos without captions, private or unavailable videos, and anything blocked are free.

### What you get

| Field | Example |
|---|---|
| `title`, `channelName`, `channelId`, `channelUrl` | "Rick Astley - Never Gonna Give You Up…", "Rick Astley" |
| `publishDate`, `durationSeconds`, `viewCount`, `likeCount`, `category` | 2009-10-24T23:57:33-07:00, 213, 1825089739, 19481552, Music |
| `language`, `languageName`, `captionType` | en, English, manual |
| `availableLanguages` | every caption track: code, name, manual or auto |
| `text` | the whole transcript as one string |
| `segments` | `[{start: 18.64, end: 21.88, text: "♪ We're no strangers to love ♪"}, …]` |
| `chunks` (optional) | about N words each, with start, end and a link to that moment, ready for embeddings |
| `srt`, `vtt` (optional) | subtitle files as text |
| `chapters` | from the description: `[{title: "Intro", start: 0}, …]` |
| `status`, `errorMessage` | `ok`, or why there is no transcript |

### How to use it

#### One or many videos

Paste video URLs or IDs into **YouTube URLs**, one per line. Any shape works: `watch?v=`, `youtu.be/`, `/shorts/`, `/live/`, `/embed/`, or the bare 11-character ID.

#### A whole channel

Paste the channel URL (`https://www.youtube.com/@TED`, `/channel/UC…`, `/c/…`). You get its newest videos first, up to **Max videos per channel or playlist** (0 = all). Choose regular videos, Shorts, past live streams or everything with **Channel videos to include**, or paste `@TED/shorts` or `@TED/streams` directly. Add **Published after** ("30 days", "2026-09-01") to keep only recent uploads.

#### A playlist

Paste the playlist URL (`https://www.youtube.com/playlist?list=PL…`).

#### Pick the language

Leave **Languages** empty to get the language actually spoken in the video. Many videos list another language first (a TED talk in English listed Arabic first), so we don't just take the first track. Or give an order, such as `en, es`. Creator captions come before auto-generated ones in the same language. If none of your languages exists you get the spoken one with `languageMatched: false`, or switch off **Fall back to another language** to get a free `language-not-available` row instead.

#### Transcripts for RAG and AI agents

Set **AI chunks: words per chunk** to 200–500. Each chunk keeps its start and end time and a link that opens the video at that moment, so answers can cite the source.

#### Monitor a channel

Turn on **Only new videos** and run the Actor on a schedule. Each run returns only uploads it hasn't returned before for that channel and settings.

### Input example

```json
{
  "urls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "https://www.youtube.com/@TED"
  ],
  "maxVideosPerChannel": 20,
  "publishedAfter": "30 days",
  "languages": ["en"],
  "chunkWords": 300
}
```

Switching from another transcript Actor? Calls that use `startUrls`, `videoUrls`, `videoIds`, `youtube_url`, `channel_url`, `language` or `max_videos` work as they are.

### Output example

```json
{
  "videoId": "dQw4w9WgXcQ",
  "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "status": "ok",
  "errorMessage": null,
  "title": "Rick Astley - Never Gonna Give You Up (Official Video) (4K Remaster)",
  "channelId": "UCuAXFkgsw1L7xaCfnd5JJOw",
  "channelName": "Rick Astley",
  "publishDate": "2009-10-24T23:57:33-07:00",
  "durationSeconds": 213,
  "viewCount": 1825089739,
  "likeCount": 19481552,
  "category": "Music",
  "language": "en",
  "languageName": "English",
  "captionType": "manual",
  "languageMatched": true,
  "availableLanguages": [
    { "code": "en", "name": "English", "type": "manual" },
    { "code": "en", "name": "English (auto-generated)", "type": "auto" },
    { "code": "de-DE", "name": "German (Germany)", "type": "manual" }
  ],
  "text": "[♪♪♪] ♪ We're no strangers to love ♪ ♪ You know the rules and so do I ♪ …",
  "wordCount": 487,
  "segments": [
    { "start": 1.36, "end": 3.04, "text": "[♪♪♪]" },
    { "start": 18.64, "end": 21.88, "text": "♪ We're no strangers to love ♪" }
  ],
  "extraHours": 0,
  "source": { "type": "video", "value": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" }
}
```

A video without a transcript:

```json
{ "videoId": "aaaaaaaaaaa", "status": "unavailable", "errorMessage": "This video is unavailable", "title": null }
```

### How much does it cost to scrape YouTube transcripts?

| Job | Free plan | Starter |
|---|---|---|
| 100 videos | $0.45 | $0.42 |
| A 1,000-video channel | $4.50 | $4.20 |
| 200 podcast episodes of 3 hours (1 transcript + 2 extra hours each) | $1.10 | $1.04 |

Set a maximum cost per run in the run options and the Actor stops cleanly when it's reached.

### Status codes

Every video ends with one status. Only `ok` is charged.

| Status | Meaning |
|---|---|
| `ok` | Transcript returned |
| `no-captions` | The video has no captions at all |
| `language-not-available` | None of your languages exists and fallback is off |
| `age-restricted` | YouTube wants a signed-in adult |
| `members-only` | Only for channel members |
| `private` | Private video |
| `unavailable` | Removed, or never existed |
| `live` | A live stream still running or a premiere that hasn't started; captions are final once it ends |
| `region-blocked` | Not available in the proxy's country |
| `blocked` | YouTube refused every attempt from fresh IPs (rare; try again later) |
| `error` | Something else; the message says what |

### What YouTube does not give

- **Transcripts for videos without captions.** This Actor reads YouTube's caption tracks; it does not transcribe audio.
- **Translations.** YouTube's machine translation is rate-limited for everyone, so we return original tracks only.
- **Private, members-only or age-restricted videos.** They need a signed-in account, and this Actor never logs in.
- **Speaker names and word-level timing.** Captions are timed per line, without speakers.
- **Punctuation on auto-generated captions.** They come as YouTube wrote them.

### FAQ

**Is it legal to scrape YouTube transcripts?** The Actor reads the public captions YouTube shows to every visitor, without logging in. Transcripts are the creator's words: use them for analysis, research and search, not to republish someone's content.

**Why residential proxies?** YouTube answers data-center IPs, including Apify's, with "Sign in to confirm you're not a bot". Residential IPs get through. Their cost is included in the price.

**What if YouTube blocks the run?** A few blocked requests are normal and retried on fresh IPs. If YouTube starts blocking most of them, the run stops early and tells you so, instead of grinding on. Blocked videos are never charged; try again later.

**Why is a language missing?** The video has no captions in it. `availableLanguages` lists what exists.

**Can I use it from n8n, Make, Zapier, an API or an AI agent?** Yes. Use the Apify integration or API with the input above, or connect it to an AI agent through Apify's MCP server.

**How is it different from other transcript scrapers?**

| | This Actor | Typical |
|---|---|---|
| Videos, channels and playlists in one run | Yes | Often one video per run |
| Spoken language by default | Yes | First track, often the wrong language |
| Reason for every missing transcript | Yes, free row | Empty result or failed run |
| Publish date, likes, chapters | Included | Often missing or extra cost |
| AI chunks with timestamps and links | Yes | Rare |
| Only new videos for schedules | Yes | Rare |

### Feedback

Found a video that should work but doesn't? Open an issue with the video URL and we'll look at it. Reviews help other people find the Actor. See the [changelog](./CHANGELOG.md) for updates.

# Changelog

This Actor's version history is a separate document: https://apify.com/apimint/youtube-transcript-scraper/changelog.md

# Actor input Schema

## `urls` (type: `array`):

One per line, mixed freely: video URLs (watch, youtu.be, Shorts, live, embed) or 11-character video IDs, channel URLs (@handle, /channel/UC…, /c/…, /user/…) and playlist URLs. A channel URL ending in /shorts or /streams lists those instead of regular videos.

## `maxVideosPerChannel` (type: `integer`):

Newest first for channels, playlist order for playlists. 0 = all of them.

## `channelContent` (type: `string`):

Which uploads of a channel to list. A /shorts or /streams channel URL overrides this for that channel.

## `publishedAfter` (type: `string`):

Channels and playlists only. A date (2026-09-01) or a period back from today ("30 days", "6 months"). Inclusive.

## `publishedBefore` (type: `string`):

Channels and playlists only. Same format as "Published after". Inclusive.

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

Stop after this many charged transcripts across all URLs. 0 = no limit (the run's maximum charge still applies).

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

Caption languages in order of preference, as codes: "en", "es", "pt-BR". Leave empty to get the language spoken in the video (YouTube's own speech recognition tells us which it is). The first track YouTube lists is often not the spoken one.

## `captionType` (type: `string`):

Creator captions are typed by the uploader; auto-generated ones come from YouTube's speech recognition and often lack punctuation.

## `fallbackToAnyLanguage` (type: `boolean`):

When none of your languages exists, return the spoken language (charged, marked languageMatched: false). Off: return a free "language-not-available" row instead.

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

Each caption line with its start and end in seconds (field segments).

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

The whole transcript as one string (field text).

## `chunkWords` (type: `integer`):

For RAG and embeddings: split the transcript into chunks of about this many words, on caption boundaries, each with start, end and a link to that moment. 0 = off. 200–500 is typical.

## `extraFormats` (type: `array`):

Add the transcript as SRT and/or WebVTT text in each row.

## `includeFailures` (type: `boolean`):

Add a free row with a status (no-captions, private, age-restricted, members-only, unavailable, live, region-blocked, blocked) for every video that has no transcript. They are never charged.

## `onlyNewVideos` (type: `boolean`):

Skip videos this Actor already returned for the same URL and settings in your earlier runs. Use with a schedule to collect each new upload once. Skipped videos are not charged.

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

Residential is needed: YouTube answers data-center IPs with "Sign in to confirm you're not a bot". Proxy traffic is included in the price. If you pick another proxy and YouTube blocks it, the Actor switches to residential by itself.

## Actor input object example

```json
{
  "urls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "https://www.youtube.com/@TED"
  ],
  "maxVideosPerChannel": 3,
  "channelContent": "videos",
  "maxVideos": 0,
  "languages": [],
  "captionType": "any",
  "fallbackToAnyLanguage": true,
  "includeTimestamps": true,
  "includePlainText": true,
  "chunkWords": 0,
  "extraFormats": [],
  "includeFailures": true,
  "onlyNewVideos": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

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

One row per video: transcript, segments, language and video details, or the reason there is none.

## `summary` (type: `string`):

Per URL: videos listed, transcripts, rows without a transcript by status, blocks, timings, and why the run stopped.

# 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 = {
    "urls": [
        "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
        "https://www.youtube.com/@TED"
    ],
    "maxVideosPerChannel": 3,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("apimint/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 = {
    "urls": [
        "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
        "https://www.youtube.com/@TED",
    ],
    "maxVideosPerChannel": 3,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("apimint/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 '{
  "urls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "https://www.youtube.com/@TED"
  ],
  "maxVideosPerChannel": 3,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call apimint/youtube-transcript-scraper --silent --output-dataset

```

## MCP server setup

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