# YouTube Transcript Scraper: Subtitles, SRT and Metadata (`fetchrail/youtube-transcript`) Actor

Scrape YouTube transcripts and subtitles with timestamps as JSON, text, SRT or WebVTT, in your preferred language. Video metadata free on request. Built for AI and LLM pipelines. Pay only for videos that return a transcript.

- **URL**: https://apify.com/fetchrail/youtube-transcript.md
- **Developed by:** [Fetchrail](https://apify.com/fetchrail) (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 $0.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: Subtitles, SRT and Metadata

Turn any public YouTube video with captions into clean, timestamped text. Paste a
list of links, choose a language and a format, and get one result per video,
ready for an LLM, a spreadsheet or a subtitle editor.

| At a glance | |
| --- | --- |
| **Price** | $1.00 per 1,000 transcripts, platform usage included |
| **Charged when** | A video returns a transcript. Everything else is free. |
| **Formats** | Timestamped segments (JSON), plain text, SRT, WebVTT |
| **Language** | Your choice, with a sensible fallback when a video lacks it |
| **Links** | Watch, Shorts, embed, mobile, `youtu.be` and bare video ids |
| **Metadata** | Channel, duration, upload date, views, chapters, tags: free on request |
| **AI agents** | Callable as a tool from Claude, Cursor and VS Code over MCP |

### 🧾 What's in each result

- **The transcript** in the format you chose. As segments, each line carries
  `text`, `start` and `duration` in seconds.
- **Which track you got**: `language`, and `isAsr` for automatic captions.
- **What you asked for**, in `preferred`, so a fallback is easy to spot.
- **Every real caption track** the video has, in `captionTracks`.
- **The video's metadata**, when you turn it on: description, channel, duration,
  upload date, view count, thumbnails, chapters and tags.
- **A reason code** for every video that has no transcript, instead of a crashed
  run.

### 🧠 Who it's for

| You are building | Set this |
| --- | --- |
| A summarizer, Q\&A bot or RAG index | `format: json`, so every chunk keeps a timestamp that links back to the video |
| A prompt from one long video | `format: text`, one string to paste straight in |
| Subtitles for an editor or player | `format: srt` or `vtt` |
| A research dataset across channels | `includeMetadata: true`, then export to CSV or Excel |
| A blog post, newsletter or show notes | `format: text`, then hand it to your writing model |
| An agent that answers questions about videos | The MCP setup below |

### ▶️ Run it in three steps

1. Paste YouTube links, or 11 character video ids, into **YouTube video URLs**.
2. Optionally set a **Preferred language**, such as `en`, `de` or `pt-BR`.
3. Press **Start**, then download the results as JSON, CSV or Excel.

### ⚙️ Input

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `urls` | list of strings, required | | YouTube links or video ids. A link repeated in the list is dropped, so it is never charged twice in one run. |
| `language` | string | the video's own | Preferred language code. A preference, not a filter: see below. |
| `preferAutomaticCaptions` | boolean | `false` | Within each language, take automatic captions before the creator's own. |
| `format` | `json`, `text`, `srt`, `vtt` | `json` | Segments with timestamps, or the transcript as one string. |
| `includeMetadata` | boolean | `false` | Add the video's metadata to each result, at no extra charge. |

#### How the language is chosen

A video always returns a transcript when it has any caption track at all. Tracks
are tried in this order, creator captions before automatic ones within each step:

1. Your preferred language
2. The video's own language
3. English
4. The first track available

`preferred` echoes what you asked for and `language` says what you got, so
filtering out an unwanted fallback is one comparison.

#### Recipes

**A batch in mixed link formats**

```json
{
  "urls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "https://youtu.be/jNQXAC9IVRw",
    "dQw4w9WgXcQ"
  ]
}
```

The third entry repeats the first and is dropped, so you get two results.

**German subtitles as SRT**

```json
{
  "urls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"],
  "language": "de",
  "format": "srt"
}
```

`transcript` holds the contents of the SRT file.

**Plain text plus video details, for a research sheet**

```json
{
  "urls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"],
  "format": "text",
  "includeMetadata": true
}
```

### 🗂️ Output

One item per URL. Here is a transcript with metadata on, shortened. With metadata
off, the fields from `description` to `extra` are left out:

```json
{
  "inputUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "site": "youtube",
  "id": "dQw4w9WgXcQ",
  "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "title": "Rick Astley - Never Gonna Give You Up (Official Video) (4K Remaster)",
  "preferred": { "language": "en", "asr": false },
  "language": "en",
  "isAsr": false,
  "captionTracks": [
    { "language": "en", "asr": false, "name": "English" },
    { "language": "de-DE", "asr": false, "name": "German (Germany)" },
    { "language": "ja", "asr": false, "name": "Japanese" },
    { "language": "pt-BR", "asr": false, "name": "Portuguese (Brazil)" },
    { "language": "es-419", "asr": false, "name": "Spanish (Latin America)" },
    { "language": "en", "asr": true, "name": "English (auto-generated)" }
  ],
  "format": "json",
  "segmentCount": 61,
  "transcript": [
    { "text": "[♪♪♪]", "start": 1.36, "duration": 1.68 },
    { "text": "♪ We're no strangers to love ♪", "start": 18.64, "duration": 3.24 }
  ],
  "description": "The official video for “Never Gonna Give You Up” by Rick Astley...",
  "uploader": {
    "id": "UCuAXFkgsw1L7xaCfnd5JJOw",
    "name": "Rick Astley",
    "url": "https://www.youtube.com/channel/UCuAXFkgsw1L7xaCfnd5JJOw"
  },
  "durationSeconds": 213,
  "uploadDate": "2009-10-25",
  "viewCount": 1825911060,
  "isLive": false,
  "thumbnails": [
    { "url": "https://i.ytimg.com/vi_webp/dQw4w9WgXcQ/maxresdefault.webp", "width": 1920, "height": 1080 }
  ],
  "chapters": [],
  "tags": ["rick astley", "Never Gonna Give You Up", "nggyu"],
  "extra": {
    "like_count": 19485690,
    "comment_count": 2400000,
    "categories": ["Music"],
    "age_limit": 0,
    "live_status": "not_live",
    "availability": "public",
    "language": "en"
  },
  "error": null
}
```

`isAsr` is true for automatic captions. In `captionTracks` the same flag is called
`asr`.

#### When a video has no transcript

You still get an item, and it costs nothing:

```json
{
  "inputUrl": "https://www.youtube.com/watch?v=...",
  "error": {
    "code": "NO_CAPTIONS",
    "message": "This media has no caption tracks.",
    "requestId": "..."
  }
}
```

| Code | Meaning |
| --- | --- |
| `NO_CAPTIONS` | The video has no caption tracks at all. |
| `UNAVAILABLE` | Removed, private or region locked. |
| `BLOCKED` | YouTube refused the request, for example an age restricted video. |
| `UPSTREAM_RATE_LIMITED` | YouTube is throttling requests. Try again later. |
| `EXTRACTION_FAILED` | A temporary failure. Try again later. |
| `UNSUPPORTED_SITE` | Not a YouTube video URL. |
| `INVALID_URL` | Not a valid URL. |
| `FREE_PLAN_LIMIT` | Skipped: the Apify free plan's daily transcripts are used up. |
| `CHARGE_LIMIT_REACHED` | Skipped: the run reached the maximum charge you set. |

None of these is charged.

### 🏷️ Pricing

**One price, one event: $1.00 per 1,000 transcripts.** You are charged once per
video that returns a transcript, with or without metadata. A three hour lecture
costs the same as a thirty second Short. Failed videos cost nothing, and platform
usage is included.

| Videos with a transcript | Cost |
| --- | --- |
| 100 | $0.10 |
| 1,000 | $1.00 |
| 10,000 | $10.00 |
| 100,000 | $100.00 |

Set a maximum charge for a run and it stops before going over, listing the URLs
it did not process.

**On the Apify free plan, you get 30 transcripts per day**, across all your runs.
The count resets at 00:00 UTC. Once it is reached, the run stops and returns the
remaining URLs with the `FREE_PLAN_LIMIT` code, free of charge, and the run's
status message says so. Videos without a transcript do not count. Paid Apify
plans have no limit.

### 🔌 Plug it into your AI agent

Through [Apify's MCP server](https://docs.apify.com/platform/integrations/mcp),
this Actor becomes a tool your agent can call. Use this server URL, with your
[Apify API token](https://console.apify.com/settings/integrations):

```
https://mcp.apify.com?tools=fetchrail/youtube-transcript
```

| Client | How to add it |
| --- | --- |
| Claude Code | `claude mcp add --transport http apify "https://mcp.apify.com?tools=fetchrail/youtube-transcript" --header "Authorization: Bearer YOUR_TOKEN"` |
| Claude Desktop, claude.ai | Add a custom connector with the URL above and sign in to Apify |
| Cursor | In `.cursor/mcp.json`, under `mcpServers`: the URL, plus an `Authorization: Bearer YOUR_TOKEN` header |
| VS Code | In `.vscode/mcp.json`, under `servers`: `"type": "http"`, the URL and the same header |

Then just ask, for example: *"Summarize the transcript of this video and list the
three main claims with their timestamps."*

### 🧑‍💻 From code and automations

**One HTTP request.** Runs the Actor and returns the results. Replace `YOUR_TOKEN`
with your Apify API token:

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

**Python**, with `apify-client` version 3 or later:

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_TOKEN")
run = client.actor("fetchrail/youtube-transcript").call(
    run_input={"urls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"], "format": "text"}
)
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item["title"], item.get("transcript") or item["error"])
```

**No code.** Apify's Make, n8n and Zapier integrations run the Actor and pass each
result to your next step, such as a spreadsheet row or a Slack message. Schedules
and webhooks are set up from Apify Console.

### 🙋 Questions people ask

**Does it transcribe videos that have no captions?** No. It returns the captions a
video already has, from the creator or generated by YouTube, and does not run
speech-to-text. A video with no caption track returns `NO_CAPTIONS`, free of
charge.

**Why not just use YouTube's own API?** YouTube's Data API lists caption tracks,
but downloading one requires signing in as the channel owner, so it only works on
your own videos. This Actor works on any public video with captions.

**Why did I get a different language than I asked for?** The video has no track
in that language, so the fallback order chose another. Compare `language` with
`preferred` in the result.

**Why are machine translations missing?** YouTube can translate its automatic
captions into many languages on demand, but those translations fail often and
stack two layers of machine error. You get only the tracks that really exist, all
listed in `captionTracks`. For another language, send the plain text to your LLM.

**Can I pass a playlist or a channel?** Not yet. Pass each video's URL or id.

**Is this affiliated with YouTube?** No. It is an independent tool that reads
captions YouTube shows publicly to any viewer.

**Is it legal?** How you use transcripts is your responsibility: they can be
protected by copyright, and spoken content can contain personal data. If in
doubt, consult a lawyer.

### 🛟 Support

Open an issue on this Actor's **Issues** tab. If a video failed, include the
`requestId` from its result, which lets us trace exactly what happened.

# Actor input Schema

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

Video URLs or 11 character video ids, one per entry. Watch, short, embed and youtu.be links all work. On the Apify free plan, you get 30 transcripts per day across all runs, reset at 00:00 UTC.

## `language` (type: `string`):

A language code such as en, de or pt-BR. A preference, not a filter: a video without a track in it returns its own language instead, then English, then the first track available. Each result says which language it returned. Leave empty for the video's own language.

## `preferAutomaticCaptions` (type: `boolean`):

Within each language, return the automatically generated track before a creator's own. Off by default, because creator captions are usually more accurate.

## `format` (type: `string`):

Segments with start times and durations, plain text, or a subtitle file (SRT or WebVTT) as text.

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

Add the description, channel, duration, upload date, view count, thumbnails, chapters and tags to each result, at no extra charge. Off by default, which keeps results small and runs faster.

## Actor input object example

```json
{
  "urls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
  ],
  "preferAutomaticCaptions": false,
  "format": "json",
  "includeMetadata": false
}
```

# Actor output Schema

## `overview` (type: `string`):

One row per URL: title, language, whether the captions are automatic, segment count and any error.

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

One row per URL: title, language and the transcript itself, or the error for a video without one.

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

One item per URL with the transcript, the caption tracks the video has and, when asked for, its metadata, or an error code for a video without a transcript.

# 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"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("fetchrail/youtube-transcript").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"] }

# Run the Actor and wait for it to finish
run = client.actor("fetchrail/youtube-transcript").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"
  ]
}' |
apify call fetchrail/youtube-transcript --silent --output-dataset

```

## MCP server setup

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

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/AKouAtI1ODgKm0asW/builds/7QmjhQcYdjJixOF9m/openapi.json
