# YouTube Transcript Scraper (`jcwilk/youtube-transcripts`) Actor

YouTube transcript API: bulk transcripts and captions for videos, Shorts, channels, playlists and searches. Timestamps, text, SRT/VTT, RAG-ready chunks, language fallback, translation. $2.50 per 1,000 transcripts; no captions = no charge.

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

## Pricing

from $2.50 / 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?

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 API & Scraper: Bulk Videos, Channels, Shorts, RAG-Ready

Get **YouTube transcripts, captions and subtitles in bulk** with timestamps, as JSON, plain text, SRT, or WebVTT. It works with **single videos, YouTube Shorts, whole channels, playlists, and YouTube search results**. Output is ready for **RAG, LLMs, AI agents, content repurposing, and research**: time-aligned chunks with deep links, language fallback, and YouTube's built-in caption translation.

**$2.50 per 1,000 transcripts. Videos without captions, private or deleted videos, and errors are free.**

### Why this YouTube transcript API?

- **Bulk input:** paste any mix of video URLs, Shorts URLs, `youtu.be` links, video IDs, channel URLs or `@handles`, playlist URLs, and search queries.
- **Whole channels and playlists:** takes the newest N videos, Shorts, or live streams of a channel, with an optional **published-after date filter**.
- **Shorts transcripts:** Shorts use the same code path as regular videos.
- **Timestamps:** segments with `start` and `duration` in seconds, text with `[mm:ss]` markers, SRT, and VTT.
- **RAG-ready chunks:** time-aligned `chunks[]` with `start`, `end`, `text`, and a `&t=123s` deep link for citations.
- **Language control:** a preferred-language list (`en`, `es`, `de`…), human-made captions preferred over auto-generated, fallback to the original language, and optional **translation** (`translateTo`).
- **Reliable on Apify:** residential proxy by default, a fresh IP on every retry, and only a few KB transferred per video, so it stays fast and cheap.
- **Fair pricing:** you pay only for transcripts you receive. A clean `status` and `errorCode` on every row (`no_captions`, `unavailable`, `private`, `age_restricted`, `blocked`…).
- **Metadata included:** title, channel, channel ID, duration, views, Shorts flag, available caption languages, and a metadata-only mode.

### Input

| Field | Default | Description |
|---|---|---|
| `urls` | – | Videos, Shorts, youtu.be links, IDs, channels (`@handle`, `/channel/UC…`), playlists |
| `searchQueries` | – | Get transcripts of top search results |
| `maxVideosPerSource` | `20` | Newest N per channel tab, playlist, or search |
| `channelContent` | `videos` | `videos`, `shorts`, `videos+shorts`, `streams`, `all` |
| `publishedAfter` | – | `YYYY-MM-DD` (approximate for listings) |
| `languages` | `["en"]` | Preferred caption languages, in order |
| `preferManualCaptions` | `true` | Human captions before auto-generated |
| `fallbackToAnyLanguage` | `true` | Otherwise use the original language |
| `translateTo` | – | e.g. `en`: YouTube auto-translation |
| `includeText` / `includeSegments` | `true` | Plain text / timestamped segments |
| `includeTimestampedText` | `false` | Text with `[m:ss]` every ~30 s |
| `includeChunks` / `chunkSizeChars` | `false` / `2000` | RAG chunks |
| `includeSrt` / `includeVtt` | `false` | Subtitle files as strings |
| `metadataOnly` | `false` | Skip transcripts and return video metadata only |
| `maxVideos` | `0` | Overall cap (0 = none) |
| `proxyConfiguration` | Residential | Keep the default for best reliability |

#### Example input

```json
{
  "urls": [
    "https://www.youtube.com/watch?v=T-D1OfcDW1M",
    "https://www.youtube.com/shorts/aNI2jv7lBPQ",
    "https://www.youtube.com/@IBMTechnology",
    "https://www.youtube.com/playlist?list=PLFgquLnL59alCl_2TQvOiD5Vgm1hCaGSI"
  ],
  "searchQueries": ["retrieval augmented generation explained"],
  "maxVideosPerSource": 10,
  "publishedAfter": "2025-01-01",
  "languages": ["en"],
  "includeChunks": true
}
```

### Output

One dataset item per video:

```json
{
  "videoId": "T-D1OfcDW1M",
  "url": "https://www.youtube.com/watch?v=T-D1OfcDW1M",
  "status": "success",
  "title": "What is Retrieval-Augmented Generation (RAG)?",
  "channelName": "IBM Technology",
  "channelId": "UCKWaEZ-_VweaEx1j62do_vQ",
  "durationSec": 395,
  "isShort": false,
  "viewCount": 1500000,
  "language": "en",
  "isAutoGenerated": false,
  "availableLanguages": ["en", "en (auto)"],
  "wordCount": 1062,
  "text": "Large language models. They are everywhere...",
  "segments": [{ "start": 0.0, "duration": 4.2, "text": "Large language models." }],
  "chunks": [{ "index": 0, "start": 0.0, "end": 98.4, "text": "...", "url": "https://www.youtube.com/watch?v=T-D1OfcDW1M&t=0s" }]
}
```

Videos without captions return `"status": "no_captions"` and are **not charged**. Failures return `"status": "error"` with `errorCode` and `error`. Run statistics are saved to the `RUN_SUMMARY` key in the key-value store.

### Pricing (pay per event)

| Event | Price |
|---|---|
| Actor start (charged by Apify, per GB of run memory) | $0.0005 per GB |
| Transcript returned | **$0.0025** ($2.50 per 1,000) |
| Video metadata (metadata-only mode) | $0.0005 |

Listing channels, playlists and searches is free; videos without captions, private/deleted videos and errors are free. Run with low memory (512 MB is enough) and batch many videos per run to keep the start fee small. Set a **maximum cost per run** in Apify to cap spend; the Actor stops cleanly at the limit.

### Use cases

- **RAG / AI knowledge bases:** index a channel's videos into a vector database with timestamped citations.
- **Content repurposing:** turn videos and Shorts into blog posts, newsletters, threads, and show notes.
- **Research and monitoring:** analyse what creators, competitors, or experts say over time.
- **Subtitles:** export SRT or VTT in any language YouTube provides or can translate to.
- **AI agents and MCP:** call it from Claude, ChatGPT, LangChain, n8n, Make, or Zapier through the Apify API or MCP server.

### API example

```bash
curl -X POST "https://api.apify.com/v2/acts/jcwilk~youtube-transcripts/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls":["https://www.youtube.com/watch?v=dQw4w9WgXcQ"],"includeSegments":false}'
```

### FAQ

**Can I use it as a YouTube transcript API from code or an AI agent?** Yes: call `run-sync-get-dataset-items` (example above) or the Apify MCP server; you get JSON back in seconds for single videos.

**Does it work for Shorts?** Yes. Paste a `/shorts/` URL, or set `channelContent` to `shorts` for a channel.

**What if a video has no captions?** You get `status: no_captions` and pay nothing. This Actor reads YouTube's own captions (manual or auto-generated); it does not run speech-to-text.

**Which languages?** Every caption track YouTube has for the video, plus YouTube's translation into ~100 languages via `translateTo`.

**Why a residential proxy?** YouTube blocks many datacenter IPs. Each video needs only a few KB through the proxy, so the cost is included in the price.

**Limitations:** private, members-only, and some age-restricted videos cannot be read. Live streams have captions only after they end. Listing dates are approximate ("2 weeks ago").

This tool reads publicly available caption data. You are responsible for using it in line with YouTube's terms and applicable law.

# Actor input Schema

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

Any mix of video URLs, Shorts URLs, youtu.be links, 11-character video IDs, channel URLs or @handles, and playlist URLs or IDs.

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

Optional. Get transcripts of the top YouTube search results for each query.

## `maxVideosPerSource` (type: `integer`):

How many of the newest videos to take from each channel tab, playlist or search query.

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

Which channel tabs to read for channel inputs.

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

Only channel/playlist/search videos published on or after this date (YYYY-MM-DD). Dates in listings are approximate (from '3 weeks ago').

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

Caption language codes in order of preference, e.g. en, es, de. Regional variants match (en matches en-GB).

## `preferManualCaptions` (type: `boolean`):

Use creator-uploaded captions over auto-generated ones when both exist.

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

If none of the preferred languages exist, return the video's original-language captions. Off = mark the video as no_captions (free).

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

Optional language code (e.g. en). Uses YouTube's built-in caption translation when the chosen track is in another language.

## `includeText` (type: `boolean`):

Full transcript as one string.

## `includeSegments` (type: `boolean`):

Array of {start, duration, text} in seconds.

## `includeTimestampedText` (type: `boolean`):

Readable text with a timestamp every ~30 seconds.

## `includeChunks` (type: `boolean`):

Time-aligned chunks with start/end seconds and a deep link, ready for embeddings.

## `chunkSizeChars` (type: `integer`):

Approximate max characters per RAG chunk (about 4 characters per token).

## `includeSrt` (type: `boolean`):

Add an SRT subtitle file as a string.

## `includeVtt` (type: `boolean`):

Add a WebVTT subtitle file as a string.

## `metadataOnly` (type: `boolean`):

Return title, channel, duration, views, description, keywords and caption languages without the transcript. Charged as video-metadata.

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

Optional overall cap across all inputs (0 = no cap).

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

Videos fetched in parallel.

## `maxRetries` (type: `integer`):

Attempts per video, each with a fresh proxy IP.

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

YouTube blocks many cloud IPs. Residential proxy (default) is the most reliable; the Actor only transfers a few KB per video.

## Actor input object example

```json
{
  "urls": [
    "https://www.youtube.com/watch?v=T-D1OfcDW1M",
    "https://www.youtube.com/shorts/aNI2jv7lBPQ"
  ],
  "maxVideosPerSource": 20,
  "channelContent": "videos",
  "languages": [
    "en"
  ],
  "preferManualCaptions": true,
  "fallbackToAnyLanguage": true,
  "includeText": true,
  "includeSegments": true,
  "includeTimestampedText": false,
  "includeChunks": false,
  "chunkSizeChars": 2000,
  "includeSrt": false,
  "includeVtt": false,
  "metadataOnly": false,
  "maxVideos": 0,
  "maxConcurrency": 5,
  "maxRetries": 5,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

One row per video with transcript and metadata.

## `summary` (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 = {
    "urls": [
        "https://www.youtube.com/watch?v=T-D1OfcDW1M",
        "https://www.youtube.com/shorts/aNI2jv7lBPQ"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("jcwilk/youtube-transcripts").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=T-D1OfcDW1M",
        "https://www.youtube.com/shorts/aNI2jv7lBPQ",
    ],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("jcwilk/youtube-transcripts").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=T-D1OfcDW1M",
    "https://www.youtube.com/shorts/aNI2jv7lBPQ"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call jcwilk/youtube-transcripts --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,jcwilk/youtube-transcripts"
        }
    }
}
```

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/ZnlSPVykgXT8qz3z1/builds/JHhgXZ1E1T8opRBt7/openapi.json
