# YouTube Transcript Scraper (`vakitek/youtube-transcript`) Actor

Get transcripts from YouTube videos, whole channels or playlists in one run. JSON with timestamps, SRT, VTT or plain text. 2 USD per 1,000 transcripts, and videos without captions are free.

- **URL**: https://apify.com/vakitek/youtube-transcript.md
- **Developed by:** [Brahim Sliti](https://apify.com/vakitek) (community)
- **Categories:** Videos, AI
- **Stats:** 1 total users, 0 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

Paste YouTube links, get the transcripts back. Videos, channels and playlists can be mixed in the same input list, and you get one dataset with one row per video.

You get the text with timestamps, plus the video title and channel. Pick JSON, SRT, VTT or plain text depending on whether it's going into an LLM, a subtitle editor or a spreadsheet.

### What people use it for

- **Feeding videos to an LLM or a RAG pipeline.** Every row has a `text` field with the full transcript as one string, ready to chunk and embed, and a `transcript` field with timed segments if you need to cite the exact moment.
- **Turning a channel into written content.** Point it at a creator's channel and get their last 50 videos as text for blog posts, newsletters or show notes.
- **Research across many videos.** Pull every talk from a conference playlist and search them like documents.
- **Subtitles.** Ask for `srt` or `vtt` and drop the file into your editor.

### How to use it

1. Paste your links in **YouTube URLs**, one per line. Mix them freely:
   - videos: `https://www.youtube.com/watch?v=dQw4w9WgXcQ`, `youtu.be/...`, `/shorts/...`, or just the 11-character id
   - channels: `@mkbhd` or any channel URL
   - playlists: any URL with `list=`
2. For channels and playlists, set **Max videos per channel or playlist** (default 10).
3. Pick a **format** and, if you want a specific language, a **Target language** (`fr`, `es`, `en-US`...).
4. Click **Start**. Export as JSON, CSV or Excel, or pull the results through the API.

```json
{
  "urls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ", "@mkbhd"],
  "maxVideos": 20,
  "format": "json"
}
```

### What you get

```json
{
  "videoId": "dQw4w9WgXcQ",
  "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "title": "Rick Astley - Never Gonna Give You Up",
  "channelName": "Rick Astley",
  "language": "en",
  "originalLanguage": "en",
  "isAutoGenerated": false,
  "format": "json",
  "transcript": [{ "start": 0.0, "dur": 1.6, "text": "We're no strangers to love" }],
  "text": "We're no strangers to love...",
  "wordCount": 231
}
```

- `transcript`: timed segments in JSON, or one SRT / VTT / TXT string if you picked another format.
- `text`: the whole transcript as plain text, always there whatever the format.
- `language` and `isAutoGenerated`: which caption track you got. Without a target language you get the video's original language, never an auto-translated track.
- Turn on **List available languages** to also get every caption track the video offers.

### Pricing

**2 USD per 1,000 transcripts.** You pay only when a transcript comes back. A video with no captions, a private video or a bad link returns an error row, and error rows are free. No monthly fee, no charge for the Apify platform usage on top.

Example: the last 100 videos of 10 channels (1,000 videos) costs 2 USD at most, less if some of them have no captions.

### Good to know

- It reads the captions YouTube already has, manual or auto-generated. It does not run speech-to-text, so a video without any captions comes back as `NO_TRANSCRIPT`.
- It does not translate. If the language you asked for isn't on the video you get `LANGUAGE_NOT_AVAILABLE`; turn on **List available languages** to see what exists.
- Age-restricted and private videos can't be read without a signed-in account, so they come back as errors.
- You don't need a proxy or a YouTube account. Blocking is our problem, not yours.

### Use it from n8n, Make or an API

- **Small inputs**: call `run-sync-get-dataset-items` and read the items back directly. Set **Time budget (seconds)** (default 240) to how long you are willing to wait: the actor stops starting new channel/playlist pages and transcript batches before that point and returns what it already has, so you never hit Apify's 300 second run-sync limit and lose the whole run. Anything left unfinished comes back as a `TIME_BUDGET_EXCEEDED` row instead of being silently dropped.
- **Big inputs**: start the run with `POST /v2/acts/vakitek~youtube-transcript/runs`, poll `GET /v2/actor-runs/<runId>` (or add `waitForFinish=60`) until the status is `SUCCEEDED`, then read `GET /v2/datasets/<datasetId>/items`. Set **Time budget (seconds)** to `0` (no limit) for this pattern.

### Errors

A bad link never crashes the run. It becomes a row with an `error` code, and you are not charged for it.

| `error` | What happened | Retry? |
| --- | --- | --- |
| `NO_CAPTIONS` | The video has no captions at all | No |
| `LANGUAGE_NOT_AVAILABLE` | No caption track in the language you asked for | No |
| `NOT_FOUND` | The video, channel or playlist was removed or never existed | No |
| `AGE_RESTRICTED` | Needs a signed-in account | No |
| `PRIVATE` | The video is private | No |
| `MEMBERS_ONLY` | Members-only video, nobody can fetch it without a membership, not charged | No |
| `NO_RESULTS` | The channel or playlist has no videos | No |
| `INVALID_URL` | The line isn't a YouTube video, channel or playlist | No |
| `INVALID_INPUT` | An option value is invalid | No |
| `PARSE_ERROR` | YouTube changed its page and we could not read it; we fix these fast, open an issue | No |
| `BLOCKED` | YouTube refused us for now, even after trying every proxy tier | Yes |
| `UPSTREAM_ERROR` | Something went wrong on YouTube's side or ours | Yes |
| `UPSTREAM_UNAVAILABLE` | Our API was unreachable | Yes |
| `TIME_BUDGET_EXCEEDED` | The time budget ran out before this video, channel or playlist finished | Yes |

Some rows also carry a `legacyError` field with the old, pre-unification code name (for example `NO_TRANSCRIPT`, `VIDEO_UNAVAILABLE`, `CHANNEL_NOT_FOUND`, `PLAYLIST_NOT_FOUND` or `LOGIN_REQUIRED`) so a pipeline still matching on it keeps working while it migrates to `error`.

### Reliability

Success rate, last 30 days: 97.3 % of 72 results across 36 runs (updated 2026-10-10). Failures counted: blocks, errors on our side, and runs that failed or timed out. Not counted: videos with no captions, private or deleted videos, bad links.

### FAQ

**Can I get every transcript from a channel?**
Yes. Put the channel in **YouTube URLs** and set **Max videos per channel or playlist** to how far back you want to go.

**I already use another transcript actor. Do I have to change my code?**
Probably not much. We accept `videoUrl` and `targetLanguage` as input names too, and the segment format is `{start, dur, text}`.

**Can I call it from my code or from an AI agent?**
Yes, like any Apify actor: REST API, the Python and JavaScript clients, or the Apify MCP server. The examples are in the API tab.

**Can I use the transcripts to train a model?**
The captions belong to the creators, and YouTube's terms limit automated access. Check your use case against them and the copyright rules where you operate.

**Something broke or a field is missing?**
Open an issue on this page. We read every one and usually answer within a day.

# Changelog

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

# Actor input Schema

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

Videos, channels or playlists, one per line. A channel or playlist URL expands to its latest videos (see Max videos). Video ids also work.

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

Maximum number of videos to fetch transcripts for, for each channel or playlist entry in urls.

## `targetLanguage` (type: `string`):

Requested transcript language, as an ISO 639-1 code (e.g. fr, de, ja) or a full track code (e.g. en-US). Leave empty to get whichever language YouTube offers by default (see preferManual). Applies to every video in this run.

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

Transcript format: json (list of {start, dur, text} segments), srt, vtt or txt (plain text).

## `preferManual` (type: `boolean`):

Prefer a manually-created caption track over an auto-generated one when both exist for the chosen language. Falls back to whichever exists when only one does.

## `listLanguages` (type: `boolean`):

Adds the availableLanguages field: every transcript language/track YouTube offers for that video, not just the one returned.

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

Interface language (hl) requested from YouTube, as an ISO 639-1 code (e.g. fr, de, ja). It does NOT change the transcript or video content language, only YouTube's own UI strings - use targetLanguage to pick the transcript's language.

## `country` (type: `string`):

Geolocation (gl) requested from YouTube, as an ISO 3166-1 alpha-2 code (e.g. FR, DE, JP).

## `timeBudgetSecs` (type: `integer`):

Stop and return what is ready after this many seconds, so n8n/Make run-sync calls never time out. 0 = no limit.

## Actor input object example

```json
{
  "urls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
  ],
  "maxVideos": 10,
  "format": "json",
  "preferManual": true,
  "listLanguages": false,
  "language": "en",
  "country": "US",
  "timeBudgetSecs": 240
}
```

# Actor output Schema

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

One item per video: the transcript (segments and plain text), language, word count and available caption languages. Error items carry an `error` code and the `input` that failed.

# 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("vakitek/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("vakitek/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 vakitek/youtube-transcript --silent --output-dataset

```

## MCP server setup

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