# YouTube Transcript Scraper — Fast, Reliable, Pay Per Result (`fetchworks/youtube-transcript-scraper`) Actor

Extract YouTube transcripts (timestamped segments, plain text, SRT, VTT) from videos, channels, playlists, and search. Honest per-video statuses; you only pay for delivered transcripts.

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

## Pricing

Pay per event

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

Extract **YouTube transcripts** — timestamped segments, plain text, SRT, and VTT — from videos, Shorts, channels, playlists, and search results. Built for RAG pipelines, LLM agents, content research, and SEO workflows.

**1,000 transcripts = $5.** You pay only for transcripts actually delivered. Videos without captions, blocked videos, or failures are reported honestly — and never billed as transcripts.

### Why this scraper

- **Honest per-video statuses.** Every video yields one dataset item with an explicit `status`: `ok`, `no_captions`, `blocked`, `live_stream`, `age_restricted`, `unavailable`, `translation_unavailable`, `po_token_required`, or `error`. Never a "successful" run with silently empty transcripts.
- **Success-only billing.** The `transcript` event is charged only for `status: "ok"` items. A failed video costs you nothing (at most a fractional metadata event when full video metadata was still delivered and requested).
- **Built to survive YouTube changes.** Layered client strategies with residential proxy fallback, refreshed player parameters, and continuous monitoring. When YouTube shifts, this actor is patched within hours — not weeks.
- **Batch, channel, playlist, and search input in one run.** No artificial 200-video caps.
- **Languages done right.** Priority list (`["en", "de"]`), manual-vs-auto-generated preference, full list of available caption tracks on every item, and best-effort translation.
- **Metadata comes free.** Title, channel, duration, views, likes, publish date, thumbnails, category — included by default at no extra charge.

### Input

```json
{
  "videoUrls": [
    "https://www.youtube.com/watch?v=jNQXAC9IVRw",
    "https://youtu.be/aqz-KE-bpKQ",
    "https://www.youtube.com/shorts/abcdefghijk"
  ],
  "channelUrls": ["@3blue1brown"],
  "maxVideosPerChannel": 25,
  "playlistUrls": ["https://www.youtube.com/playlist?list=PLZHQObOWTQDMsr9K-rj53DwVRMYO3t5Yr"],
  "searchQueries": ["neural networks explained"],
  "maxSearchResults": 10,
  "languages": ["en"],
  "preferAutoGenerated": false,
  "outputFormats": ["segments", "text", "srt", "vtt"],
  "includeMetadata": true,
  "includeChapters": false
}
```

Any combination of `videoUrls`, `channelUrls`, `playlistUrls`, and `searchQueries` works. Bare 11-character video IDs, `@handles`, and playlist IDs are accepted too.

### Output

One dataset item per video:

```json
{
  "videoId": "jNQXAC9IVRw",
  "url": "https://www.youtube.com/watch?v=jNQXAC9IVRw",
  "status": "ok",
  "language": "en",
  "isAutoGenerated": false,
  "availableLanguages": [
    { "languageCode": "en", "kind": "manual", "name": "English", "isTranslatable": true },
    { "languageCode": "en", "kind": "asr", "name": "English (auto-generated)" }
  ],
  "segments": [
    { "start": 1.3, "dur": 3.4, "text": "All right, so here we are" }
  ],
  "text": "All right, so here we are in front of the elephants…",
  "srt": "1\n00:00:01,300 --> 00:00:04,700\nAll right, so here we are\n",
  "vtt": "WEBVTT\n\n00:00:01.300 --> 00:00:04.700\nAll right, so here we are\n",
  "metadata": {
    "title": "Me at the zoo",
    "author": "jawed",
    "channelId": "UC4QobU6STFB0P71PMvOGN5A",
    "lengthSeconds": 19,
    "viewCount": 353000000,
    "publishDate": "2005-04-23",
    "category": "People & Blogs"
  }
}
```

A video without captions comes back as an honest item, not a fake success:

```json
{
  "videoId": "kJQP7kiw5Fk",
  "url": "https://www.youtube.com/watch?v=kJQP7kiw5Fk",
  "status": "no_captions",
  "availableLanguages": [],
  "error": "video has no caption tracks",
  "metadata": { "title": "…", "author": "…" }
}
```

### Pricing

| Event | Price | When |
| --- | --- | --- |
| `transcript` | $0.005 | A transcript was successfully extracted (`status: "ok"`) |
| `video-metadata` | $0.0005 | No transcript possible, but full video metadata was delivered (only if metadata was requested) |

That's **$5 per 1,000 transcripts**, success-only. No actor-start fee.

### Using from n8n

Use the **Apify node** (or an HTTP Request node):

1. `Run Actor` → this actor, with your input JSON (e.g. `{"videoUrls": ["{{ $json.url }}"], "outputFormats": ["text"]}`).
2. `Get Dataset Items` → map `text` or `segments` into your workflow (summarization, embeddings, Notion, Sheets…).

Single-video calls return in seconds, so synchronous `run-sync-get-dataset-items` works well:

```
POST https://api.apify.com/v2/acts/fetchworks~youtube-transcript-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN
{ "videoUrls": ["https://www.youtube.com/watch?v=VIDEO_ID"], "outputFormats": ["text"] }
```

### Using from Make (Integromat)

Add the **Apify app** → *Run an Actor* module → pick this actor and paste your input JSON. Then a *Get Dataset Items* module reads the transcripts. The same pattern powers Airtable, Sheets, and Slack scenarios.

### Using from AI agents (MCP)

This actor is exposed through Apify's MCP server. Point your agent at `https://mcp.apify.com` with your Apify token, and it can call `fetchworks/youtube-transcript-scraper` as a tool — ask for a video URL's transcript and read `text` from the dataset. Works with Claude, OpenAI-compatible agents, and any MCP client.

### FAQ

**Is scraping YouTube transcripts legal?**
This actor accesses only publicly available caption data — the same data any signed-out viewer sees. No login, no private data, no personal data collection. Whether your specific use complies with YouTube's ToS and your jurisdiction is your call; for many research and indexing uses, publicly accessible data scraping has repeatedly been upheld (e.g. *hiQ v. LinkedIn*). Not legal advice.

**What about rate limits?**
Handled internally: requests ride rotating residential-class sessions and caption downloads are spread across egress paths. You don't need to throttle your runs. For very large batches (10,000+), split into a few runs for best throughput.

**Which languages are supported?**
Every language YouTube has a caption track for. Give a priority list (`["de", "en"]`) and the first available track wins; `availableLanguages` on each item shows everything the video offers. `translateTo` requests YouTube auto-translation (best-effort — YouTube throttles translated tracks globally; failures are reported as `translation_unavailable` and not billed).

**Auto-generated vs manual captions?**
Manual (creator-uploaded) captions are preferred by default; auto-generated (ASR) tracks are used when no manual track exists. Set `preferAutoGenerated: true` to flip that. Each item tells you which you got via `isAutoGenerated`.

**How long can videos be?**
4-hour-plus videos are fine — transcripts are fetched as compact caption payloads, not media, so memory stays flat.

**Live streams?**
Finished live VODs with captions work like normal videos. Offline/upcoming live streams return `status: "live_stream"`.

**Unlisted videos?**
Work, as long as you have the URL.

### Changelog

#### 0.1

- Initial release: video/channel/playlist/search input, segments/text/SRT/VTT output, honest per-video statuses, success-only pay-per-event pricing, metadata and chapters.

# Actor input Schema

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

YouTube video URLs (watch, youtu.be, Shorts, embed, live) or bare 11-character video IDs. One transcript is extracted per video.

## `channelUrls` (type: `array`):

Channel URLs (youtube.com/@handle, /channel/UC…, /c/…, /user/…) or bare @handles. All uploads are enumerated newest first, bounded by 'Max videos per channel'.

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

Upper bound on videos taken from each channel (newest first). No hard cap.

## `playlistUrls` (type: `array`):

Playlist URLs (youtube.com/playlist?list=…) or bare playlist IDs (PL…, UU…).

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

YouTube search queries; top video results are transcribed, bounded by 'Max search results'.

## `maxSearchResults` (type: `integer`):

Upper bound on videos taken per search query.

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

Language priority list (ISO codes, e.g. \["en", "de"]). The first available matching caption track wins. If none match, the video's default track is used and the actual language is reported on the item.

## `preferAutoGenerated` (type: `boolean`):

When a language has both a manual and an auto-generated (ASR) track, prefer the auto-generated one. Default prefers manual captions.

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

Optional target language code (e.g. "es"). Uses YouTube's caption auto-translation. Best-effort: YouTube throttles translated tracks globally; failures are reported as translation\_unavailable and never billed.

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

Transcript representations to include on each item.

## `includeMetadata` (type: `boolean`):

Include title, channel, duration, views, publish date, thumbnails, etc. Comes free with extraction.

## `includeChapters` (type: `boolean`):

Include video chapters. Costs one extra request per video.

## `concurrency` (type: `integer`):

How many videos are processed in parallel.

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

Proxy used for YouTube API calls. Residential proxy is strongly recommended — datacenter IPs are bot-checked by YouTube on most videos.

## Actor input object example

```json
{
  "videoUrls": [
    "https://www.youtube.com/watch?v=jNQXAC9IVRw"
  ],
  "maxVideosPerChannel": 100,
  "maxSearchResults": 50,
  "languages": [
    "en"
  ],
  "preferAutoGenerated": false,
  "outputFormats": [
    "segments",
    "text"
  ],
  "includeMetadata": true,
  "includeChapters": false,
  "concurrency": 10,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

All items — every video with its status, transcript segments/text/SRT/VTT, and metadata.

## `delivered` (type: `string`):

Only items with status=ok (the billed ones).

# 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"
    ],
    "languages": [
        "en"
    ],
    "outputFormats": [
        "segments",
        "text"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

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

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

```

## MCP server setup

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