# YouTube Transcript API & Scraper: Subtitles (`meridianlabs/youtube-transcripts`) Actor

Get YouTube transcripts in the video's original language: full text, timestamps, timed segments, title, channel and word count. Works with Shorts. $3 per 1,000 transcripts; videos without captions are free.

- **URL**: https://apify.com/meridianlabs/youtube-transcripts.md
- **Developed by:** [Meridian Labs](https://apify.com/meridianlabs) (community)
- **Categories:** Videos, AI, Developer tools
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$3.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

Get the transcript of any public YouTube video or Short as clean text, in the language the video is actually spoken in. Paste video links or ids and you get one row per video: the full transcript, its language, whether the captions are auto-generated, the title, channel, length and word count. Timestamps and timed segments are one switch away. You pay **$3 per 1,000 transcripts** ($0.003 each), and only for transcripts delivered: videos without captions, private or blocked videos and failed requests are free rows.

The input form is pre-filled with two videos, so the quickest way to try it is to click **Start** (about $0.006).

### What do you get for each video?

| Field | What it means |
|---|---|
| `status` | `ok` = transcript delivered (charged). `no_transcript` = YouTube answered but there is nothing to deliver (free). `error` / `skipped` = not delivered (free) |
| `videoId`, `url`, `title`, `channelName`, `channelId` | The video and its channel |
| `durationSeconds`, `viewCount` | Length in seconds and views when fetched |
| `languageCode`, `languageName` | Language of the transcript (`en`, `es`, `pt-BR`, `zh-Hans`...) |
| `isAutoGenerated` | `true` = YouTube's speech recognition, `false` = captions uploaded by the creator |
| `originalLanguageCode`, `isOriginalLanguage` | The language spoken in the video, and whether the transcript is in it |
| `text` | The full transcript: one plain paragraph, or one `[m:ss]` line per caption with timestamps on |
| `wordCount`, `characterCount`, `segmentCount` | Size of the transcript |
| `segments` | With "Include timed segments" on: every caption line with `start` and `duration` in seconds |
| `captionLanguages`, `autoCaptionLanguages` | Every language YouTube has captions in for this video |
| `errorCode`, `errorMessage` | Why a video has no transcript (see the table below) |

### Why use this YouTube transcript scraper?

- **The original language, not a random translation.** Many videos carry dozens of uploaded caption tracks. Tools that take "the first track" hand you Afrikaans or Albanian for a German cooking video. This Actor works out the spoken language from YouTube's auto-captions, the video's original audio track and its title, and returns that track.
- **The same text YouTube shows.** We checked transcripts line by line against YouTube's own transcript panel and against an independent open-source library. Text is cleaned of HTML codes (no `&#39;` or `&amp;`).
- **You pay for transcripts, nothing else.** No start fee, no charge per result row. A video without captions costs nothing.
- **Shorts, live replays, embeds and youtu.be links** all work. Geo-blocked videos are retried from other countries automatically.
- **Built for AI pipelines.** One row per video with stable field names, plain text by default, word counts for chunking, and a documented output schema.

### How much does it cost?

**$0.003 per transcript delivered** ($3 per 1,000). Examples:

- 100 videos, 90 with captions: **$0.27** (the 10 without captions are free).
- 1,000 videos: at most **$3.00**.
- Apify's free $5 monthly credit covers about **1,600 transcripts**.

Free (never charged): videos with no captions, unavailable, private, age-restricted, members-only or geo-blocked videos, links that aren't YouTube videos, failed requests, and anything past your spending limit or the run's time limit. Set a maximum cost per run in the run options and the Actor stops fetching before it would go over.

### How do I get the transcript of a YouTube video?

1. Paste one or more video links or ids into **YouTube videos** (one per line, up to 10,000 per run).
2. Leave **Preferred languages** empty to get each video in its original spoken language, or list codes like `en` or `es`.
3. Click **Start**. Each video takes about 1 to 5 seconds and many run at once.
4. Download the results as JSON, CSV or Excel, or read them through the API.

#### How do I get transcripts with timestamps?

Turn on **Timestamps in the text** for lines like `[1:05] and that's when it broke`. Turn on **Include timed segments** for a `segments` list with exact start and duration in seconds (good for subtitles, clips or jumping to a moment).

#### How do I get a transcript in a specific language?

List language codes in order of preference, for example `en`, `es`. Creator-uploaded captions are used before auto-generated ones. If a video has none of your languages you get its original language instead, and `matchedRequestedLanguage` is `false`. Turn off **Fall back to the original language** to get a free `LANGUAGE_NOT_AVAILABLE` row instead.

### Input example

```json
{
  "videos": [
    "https://www.youtube.com/watch?v=arj7oStGLkU",
    "https://youtu.be/jNQXAC9IVRw",
    "https://www.youtube.com/shorts/48BnYG54694"
  ],
  "languages": [],
  "includeTimestamps": false,
  "includeSegments": false
}
```

### Output example

```json
{
  "status": "ok",
  "videoId": "arj7oStGLkU",
  "url": "https://www.youtube.com/watch?v=arj7oStGLkU",
  "input": "https://www.youtube.com/watch?v=arj7oStGLkU",
  "position": 1,
  "videoType": "video",
  "title": "Inside the Mind of a Master Procrastinator | Tim Urban | TED",
  "channelName": "TED",
  "channelId": "UCAuUUnT6oDeKwE6v1NGQxug",
  "durationSeconds": 844,
  "viewCount": 62073613,
  "originalLanguageCode": "en",
  "captionLanguages": [
    "ar",
    "hy",
    "bg",
    "my",
    "ca",
    "ckb",
    "..."
  ],
  "autoCaptionLanguages": [
    "en"
  ],
  "languageCode": "en",
  "languageName": "English",
  "isAutoGenerated": false,
  "isOriginalLanguage": true,
  "matchedRequestedLanguage": null,
  "text": "So in college, I was a government major, which means I had to write a lot of papers. Now, when a normal student writes a paper, they might spread the work out a little like this. So, you know -- (Laughter) you get ...",
  "wordCount": 2277,
  "characterCount": 12671,
  "segmentCount": 315,
  "errorCode": null,
  "errorMessage": null,
  "charged": true,
  "fetchedAt": "2026-10-18T12:09:55Z",
  "dataSource": "YouTube public captions (uploaded or auto-generated), read logged out. No account or cookies used."
}
```

### How do I use YouTube transcripts with an AI agent or LLM?

Add the Actor to any MCP client (Claude, Cursor, VS Code and others) with `https://mcp.apify.com?tools=meridianlabs/youtube-transcripts`, then ask in plain words, for example: *"Get the transcripts of these three videos and summarise what each one says about pricing."* The agent gets one row per video with `text`, `languageCode` and `wordCount`, which is enough to decide how to chunk long videos.

From code, call the run-sync endpoint and read the rows straight back:

```bash
curl -X POST "https://api.apify.com/v2/acts/meridianlabs~youtube-transcripts/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"videos": ["https://www.youtube.com/watch?v=arj7oStGLkU"]}'
```

It also works from Make, Zapier, n8n, Google Sheets and Apify schedules. For schedules, turn on **Fail the run on errors** so Apify alerts you when a video can't be read.

### Error codes (all free)

| Code | Meaning |
|---|---|
| `NO_CAPTIONS` | The video has no captions or subtitles at all |
| `LANGUAGE_NOT_AVAILABLE` | None of your languages exists and fallback is off |
| `VIDEO_UNAVAILABLE` | Removed, deleted or never existed |
| `PRIVATE_VIDEO`, `MEMBERS_ONLY` | Not public |
| `AGE_RESTRICTED` | YouTube only shows it to signed-in users; this Actor never signs in |
| `LIVE_OR_UPCOMING` | A live stream or premiere that hasn't finished |
| `GEO_BLOCKED` | Blocked in every country tried; set **Proxy country** to one where it plays |
| `INVALID_INPUT` | Not a YouTube video link or id (channel and playlist links aren't expanded) |
| `BLOCKED`, `NETWORK_ERROR` | YouTube kept refusing after several retries on new IP addresses; run it again |
| `YOUTUBE_CHANGED` | YouTube changed how captions are served; the Actor is being updated |
| `SPENDING_LIMIT`, `RUN_TIME_LIMIT` | Not attempted because of your cost limit or the run's timeout |

### FAQ

#### Which language does it return?

By default the language spoken in the video. `originalLanguageCode` is our reading of that language and `isOriginalLanguage` tells you whether the transcript is in it. A few dubbed videos have captions only for their dubbed versions; then you get one of those (uploaded captions first, then English) with `isOriginalLanguage: false`.

#### Why do some lines start with `>>`?

That is how YouTube's captions mark a change of speaker. It is part of the caption file, so we keep it; YouTube's transcript panel hides it on screen.

#### Can it transcribe videos that have no captions?

No. It reads the captions YouTube already has (uploaded or auto-generated). Most videos in common languages have auto-generated captions; music-only clips and some Shorts don't. Those come back as free `NO_CAPTIONS` rows.

#### Does it work with channels or playlists?

Not yet. Give it individual video or Shorts links. Channel and playlist links come back as free `INVALID_INPUT` rows.

#### Is it legal to scrape YouTube transcripts?

The Actor reads only public videos, logged out: no account, no cookies, nothing behind a sign-in. Captions are published by the uploader or by YouTube for anyone to read. Whether you may reuse a transcript depends on the video's copyright and your use; check that for your project. YouTube's Terms of Service restrict automated access, so read them and use the data responsibly.

#### Does it collect personal data?

Only what the uploader made public about the video: its title, channel name and channel id. No viewer, commenter or account data.

#### What happens if a run times out or restarts?

Videos already delivered are not fetched or charged again after a restart. When the time limit gets close, unstarted videos become free `skipped` rows and the run still succeeds; run them again or raise the timeout.

### More from Meridian Labs

- **[Keyword Search Volume & CPC API + Google Trends](https://apify.com/meridianlabs/keyword-search-volume)**: monthly search volume, CPC, competition, difficulty and intent for keyword lists, plus a Google Trends summary per keyword. From $3 per 1,000 keywords.
- **[Google Trends Scraper & API](https://apify.com/meridianlabs/google-trends-scraper)**: interest over time, by region and rising related searches for hundreds of keywords. $5 per 1,000 keyword lookups.
- **[Competitor Keyword Research](https://apify.com/meridianlabs/competitor-keywords)**: every keyword a website ranks for in Google, with position, ranking page and search volume. $0.03 per domain + $2 per 1,000 keywords.
- **[Backlink Checker](https://apify.com/meridianlabs/backlink-checker)**: referring domains, backlinks, domain rank and spam score for up to 1,000 domains a run.
- **[Facebook Ads Library Scraper](https://apify.com/meridianlabs/facebook-ads-library-scraper)**: every ad a competitor runs on Facebook and Instagram, with days running and a plain-English summary per advertiser. $0.50 per 1,000 ads.

### Support

Found a video that comes back wrong? Open an issue on the **Issues** tab with the link. We read every one.

# Actor input Schema

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

Video links or 11-character video ids, one per line, up to 10,000 per run. Works with watch links, youtu.be links, Shorts, live replays and embeds. Channel and playlist links are not expanded. Duplicates are removed.

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

Language codes in order of preference, such as en, es, pt-BR or zh-Hans. Leave empty to get each video in its ORIGINAL spoken language (recommended). Uploaded captions are used before auto-generated ones in the same language.

## `languageFallback` (type: `boolean`):

When none of your preferred languages exists for a video, return the original-language transcript instead (charged as normal; matchedRequestedLanguage is false). Turn off to get a free LANGUAGE_NOT_AVAILABLE row instead.

## `includeTimestamps` (type: `boolean`):

Off: text is one plain paragraph (best for LLMs and search). On: one line per caption with its start time, like \[1:05] text.

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

Adds a segments list to each row: every caption line with its start and duration in seconds (for subtitles, clipping or jumping to a moment).

## `proxyCountry` (type: `string`):

Leave on Automatic. Pick a country only if your videos are blocked outside it (a video "not available in your country").

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

Videos fetched at the same time.

## `failOnErrors` (type: `boolean`):

For schedules and integrations: mark the run FAILED if any video could not be read (videos that simply have no captions don't count). Free error rows are saved either way.

## Actor input object example

```json
{
  "videos": [
    "https://www.youtube.com/watch?v=arj7oStGLkU",
    "https://youtu.be/jNQXAC9IVRw"
  ],
  "languageFallback": true,
  "includeTimestamps": false,
  "includeSegments": false,
  "proxyCountry": "",
  "maxConcurrency": 10,
  "failOnErrors": false
}
```

# Actor output Schema

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

One row per video: status, title, channel, language, word count (dataset view 'overview').

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

Video, language and the full transcript text (dataset view 'transcripts').

## `runStats` (type: `string`):

Counts, timing and route health for the run.

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

Every field of every row.

# 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=arj7oStGLkU",
        "https://youtu.be/jNQXAC9IVRw"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("meridianlabs/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=arj7oStGLkU",
        "https://youtu.be/jNQXAC9IVRw",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("meridianlabs/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=arj7oStGLkU",
    "https://youtu.be/jNQXAC9IVRw"
  ]
}' |
apify call meridianlabs/youtube-transcripts --silent --output-dataset

```

## MCP server setup

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