# YouTube Transcript Scraper: Subtitles & Captions, SRT/VTT (`altronite/youtube-transcripts`) Actor

YouTube transcript scraper: subtitles and captions from public videos as timed segments, plain text, SRT or VTT. Errors and blocked videos are never charged; you pay only for transcripts delivered.

- **URL**: https://apify.com/altronite/youtube-transcripts.md
- **Developed by:** [Altronite](https://apify.com/altronite) (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.00 / 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 Scraper: Subtitles & Captions, SRT/VTT

A YouTube transcript scraper and API. Get the transcript, subtitles and captions of any public YouTube video from the video's own caption tracks: timed segments, plain text, SRT or WebVTT, plus title and channel. Paste video links, Shorts links or video ids, run it, and download JSON, CSV or Excel.

**Errors and blocked videos are never charged.** You pay only for transcripts you receive. A video with captions turned off, a private or removed video, a video YouTube keeps blocking, or a bad link gives an error row with the reason, free.

![Two YouTube links in, one transcript row per video out](https://api.apify.com/v2/key-value-stores/aZVzOVjoal7bHifMa/records/youtube-transcripts-demo.gif)

### Who it's for

- **LLM and RAG builders** who need clean transcript text to summarise, tag, search or chat with videos.
- **Automation users** (n8n, Make, Zapier) whose own server gets blocked by YouTube: this runs on Apify's IPs and handles the blocks.
- **Creators and editors** who want SRT or VTT subtitle files.
- **Researchers** analysing what is said across many videos.

### What you get

One row per video:

| Field | Example |
|---|---|
| `status` | `ok` or `error` |
| `videoId`, `url` | `dQw4w9WgXcQ`, `https://www.youtube.com/watch?v=dQw4w9WgXcQ` |
| `title`, `channel`, `channelUrl`, `thumbnailUrl` | from YouTube's public oEmbed data |
| `language`, `languageCode` | `English`, `en` |
| `isAutoGenerated` | `false` for human-made captions, `true` for YouTube's automatic ones |
| `isTranslated`, `languageFallback` | whether YouTube translated the track, or no preferred language existed |
| `availableLanguages` | every caption language the video has |
| `text` | the whole transcript as one plain string (always included; ready for search or an LLM) |
| `segments` / `srt` / `vtt` | the timed form you chose |
| `segmentCount`, `wordCount`, `captionsEndSec` | size of the transcript; time of the last caption |
| `reason`, `error` | on error rows only: `no_captions`, `unavailable`, `unplayable`, `age_restricted`, `invalid_input`, `blocked`, `time_limit`, ... |

A real row (`outputFormat: "segments"`, text and segments shortened):

```json
{
  "status": "ok",
  "videoId": "jNQXAC9IVRw",
  "url": "https://www.youtube.com/watch?v=jNQXAC9IVRw",
  "title": "Me at the zoo",
  "channel": "jawed",
  "channelUrl": "https://www.youtube.com/@jawed",
  "language": "English",
  "languageCode": "en",
  "isAutoGenerated": false,
  "isTranslated": false,
  "languageFallback": false,
  "availableLanguages": ["de", "en"],
  "segmentCount": 6,
  "wordCount": 39,
  "captionsEndSec": 18.9,
  "text": "All right, so here we are, in front of the elephants ...",
  "segments": [
    { "text": "All right, so here we are, in front of the\nelephants", "start": 1.2, "duration": 2.16 }
  ]
}
```

An error row looks like `{"status": "error", "videoId": "...", "reason": "no_captions", "error": "Captions are turned off or missing for this video."}`.

### Input: how to get a YouTube transcript

```json
{
  "videos": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "https://youtu.be/jNQXAC9IVRw",
    "https://www.youtube.com/shorts/VIDEO_ID",
    "dQw4w9WgXcQ"
  ],
  "languages": ["en"],
  "outputFormat": "segments"
}
```

- **videos**: watch, youtu.be, Shorts, embed or live links, or bare 11-character ids. Up to 1,000 per run; duplicates are merged. Playlists, channels and search pages are not expanded (add video links instead).
- **languages**: caption languages in order of preference. Human-made captions come before automatic ones in the same language. If the video has none of them, you get its first available track, flagged with `languageFallback: true`.
- **allowAutoGenerated** (default on): use YouTube's automatic captions when there are no human-made ones.
- **translateTo** (optional): a language code. Uses YouTube's own caption translation, where the track allows it.
- **outputFormat**: `segments` (text, start and duration in seconds), `text`, `srt` or `vtt`.
- **includeMetadata** (default on): title, channel, channel URL and thumbnail.

More examples:

Plain transcript text for an LLM or RAG pipeline, with the metadata left out:

```json
{ "videos": ["https://www.youtube.com/watch?v=jNQXAC9IVRw"], "outputFormat": "text", "includeMetadata": false }
```

YouTube subtitles as SRT, human-made captions only, Spanish first and then English:

```json
{ "videos": ["https://youtu.be/jNQXAC9IVRw"], "languages": ["es", "en"], "allowAutoGenerated": false, "outputFormat": "srt" }
```

Translated captions as WebVTT, where YouTube allows it for the track:

```json
{ "videos": ["https://www.youtube.com/watch?v=jNQXAC9IVRw"], "translateTo": "fr", "outputFormat": "vtt" }
```

### Use it from code or an AI agent

Run it with the Apify API, the Apify Python or JavaScript clients, or as a tool through the Apify MCP server. Pass the same input JSON through any of these.

For small lists, one call returns the rows directly: `POST https://api.apify.com/v2/acts/altronite~youtube-transcripts/run-sync-get-dataset-items` with your token in the `Authorization: Bearer` header and the input JSON as the body. For long lists, start a run and read the dataset when it finishes.

Check each row's `status` before using it: `ok` rows have the transcript, `error` rows say why not.

### Pricing

Pay per event:

- **Transcript**: charged once for each video whose transcript is delivered.
- **Actor start**: a tiny charge once per run.

Error rows are free. Check the Pricing tab for the current prices.

### How it works, and what it does not do

- It reads the caption tracks YouTube itself publishes for a video, using the open-source `youtube-transcript-api` library. No sign-in, no browser and no audio download.
- **No captions, no transcript.** It does not run speech-to-text. A video whose owner turned captions off (and that has no automatic captions) returns `no_captions`, free of charge.
- Age-restricted and members-only videos need a signed-in account, so they return an error instead.
- YouTube sometimes refuses requests from cloud servers. The actor uses Apify Proxy, retries each video on a fresh IP, then makes a second pass over any blocked videos through a different IP pool before giving up. A video still blocked after that returns `blocked` and is not charged; run it again later.
- Every video gets a row before the run's timeout. If the time runs out first, the remaining videos return `time_limit` (not charged); raise the timeout in Run options for very long lists.
- Only public data is collected. Transcripts can be copyrighted by the video owner, so check your use is allowed (for example research, accessibility or quoting with attribution).

### Use cases

- Feed transcripts to ChatGPT, Claude or your own LLM pipeline to summarise, tag or search videos.
- Build subtitle files (SRT/VTT) for your own videos.
- Research and content analysis across many videos: keywords, topics, mentions.
- Repurpose talks and tutorials into notes, blog drafts or study material.

### FAQ

**How do I download YouTube subtitles or captions?**
Set `outputFormat` to `srt` or `vtt` and pass the video link. The subtitle text is in the `srt` or `vtt` field of the row; save it as a `.srt` or `.vtt` file. This reads existing captions only, human-made or automatic.

**Does it work for YouTube Shorts?**
Yes, when the Short has captions. Shorts links are accepted as input like any other video link.

**Can I get transcripts for a whole playlist or channel?**
Not directly. Playlists, channels and search pages are not expanded, so pass the video links you want, up to 1,000 per run.

**Why did a video I can watch return `no_captions`?**
Its owner turned captions off and YouTube made no automatic ones (common for music-only or very short videos). Nothing is charged.

**Why `blocked`?**
YouTube refused every IP the actor tried for that video. It is not charged; running it again later usually works.

**Can it transcribe videos without captions?**
No. It only reads existing caption tracks.

**Does it get the auto-generated captions?**
Yes, when `allowAutoGenerated` is on (the default) and the video has no human-made captions in your languages. `isAutoGenerated` tells you which you got.

### Limits

- Up to 1,000 videos per run.
- Playlists, channels and search results are not expanded; pass video links.
- Translation depends on YouTube: some tracks can't be translated (`not_translatable`).
- Titles and channels come from YouTube's oEmbed data; if that call fails, the transcript is still delivered without them.

### Integrations

Call it from the Apify API, Python or JavaScript clients, Make, Zapier, n8n or an MCP client, and get the dataset as JSON, CSV, Excel or XML.

### Related tools

- [Google Sheets Export & Import](https://apify.com/altronite/google-sheets-import-export): send any run's results to a Google Sheet.
- [Google Trends Scraper](https://apify.com/altronite/google-trends): track search interest for your keywords.

### Support

Open an issue on the Issues tab with the video link and the run id. Error rows say why a video failed, which usually answers it. Published by Altronite.

# Actor input Schema

## `videos` (type: `array`):

YouTube video URLs (watch, youtu.be, Shorts, embed or live links) or 11-character video ids. Up to 1,000 per run; duplicates are merged.

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

Caption languages in order of preference, as codes like en, de or pt-BR. Human-made captions are preferred over auto-generated ones in the same language. If none match, the video's first available track is returned and flagged with languageFallback.

## `allowAutoGenerated` (type: `boolean`):

Use YouTube's automatic captions when no human-made track exists.

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

A language code like en or es. Uses YouTube's own caption translation when the track allows it.

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

Plain text is always included; this adds the timed form you choose.

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

Adds title, channel, channel URL and thumbnail from YouTube's public oEmbed endpoint.

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

How many videos are fetched at once.

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

YouTube limits requests from cloud servers. Apify Proxy is on by default; blocked videos are retried on a fresh IP and are never charged.

## Actor input object example

```json
{
  "videos": [
    "https://www.youtube.com/watch?v=jNQXAC9IVRw"
  ],
  "languages": [
    "en"
  ],
  "allowAutoGenerated": true,
  "outputFormat": "segments",
  "includeMetadata": true,
  "maxConcurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

One row per video.

# 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 = {
    "videos": [
        "https://www.youtube.com/watch?v=jNQXAC9IVRw"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("altronite/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 = {
    "videos": ["https://www.youtube.com/watch?v=jNQXAC9IVRw"],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("altronite/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 '{
  "videos": [
    "https://www.youtube.com/watch?v=jNQXAC9IVRw"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call altronite/youtube-transcripts --silent --output-dataset

```

## MCP server setup

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