# YouTube Transcript & Subtitles Scraper API (`johnvc/youtubetranscripts`) Actor

Scrape YouTube transcripts, subtitles, and captions in bulk, the cheapest pay-per-video YouTube transcript API on Apify. Callable from any MCP client (Claude, Cursor, ChatGPT). Supports YouTube videos, Shorts, and every URL format.

- **URL**: https://apify.com/johnvc/youtubetranscripts.md
- **Developed by:** [John](https://apify.com/johnvc) (community)
- **Categories:** Videos, AI, Developer tools
- **Features:** Limited permissions
- **Stats:** 12,040 total users, 6,707 monthly users, 100.0% runs succeeded, 30 bookmarks
- **User rating**: 4.78 out of 5 stars

## Pricing

from $2.00 / 1,000 video processeds

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 & Subtitles Scraper: Pay-Per-Video MCP API

Scrape **YouTube transcripts, subtitles, and captions** in bulk at near-free pay-per-video rates. Built for **MCP clients** (Claude, Cursor, VS Code, ChatGPT), AI agents, content pipelines, and SEO research. Works with standard videos, **YouTube Shorts**, and every URL format.

> 💡 Pricing: **pay-per-video at near-free rates** - the cheapest pay-per-video YouTube transcript API on Apify. Failed videos are never charged. No subscription, no proxy fees. Current per-video rates are always on this page's pricing card.

***

> **Example code (Python):** <https://github.com/johnisanerd/Apify-Youtube-Transcripts-API>

### ⚡ What you get back

For every video, the dataset receives:

- `non_timestamped` - full transcript text, ready for LLMs.
- `timestamped` - every snippet with `text`, `start`, and `duration` in seconds.
- `language`, `language_code`, `is_generated`, `is_translatable`, `translation_languages`.
- `total_seconds` - video duration.
- `video_id`, `url`, `success`, `timestamp`.
- `cached` and `fetched_at` - whether the transcript came from our cache and when it was fetched from YouTube (see **Cached results**).
- With the `channel` input: one row per channel video (`result_type: "channel_video"`) with title, duration, view count, and thumbnail - newest first.
- On failure: structured `error`, `error_message`, `error_type` (no crash; the run continues). When a video has no usable captions, the row also lists the caption tracks YouTube does offer (`available_transcripts`) and, with metadata on, the video's basic metadata.

***

### 🎯 Use cases

- **LLM training data** - bulk-collect transcripts for fine-tuning or RAG.
- **AI agents that watch YouTube** - let Claude or Cursor pull a transcript on demand via MCP.
- **Content repurposing** - turn videos into blog posts, summaries, or social clips.
- **SEO research** - mine transcripts for keywords, topics, and competitor analysis.
- **Podcast & video search** - index thousands of videos by their actual spoken content.
- **Whole-channel transcription** - point `channel` at any creator to list every video, or transcribe the whole channel in one run.

***

### 🤖 Use with MCP (Claude, Cursor, VS Code, ChatGPT)

This Actor is callable as a tool from any [Model Context Protocol](https://modelcontextprotocol.io) client. The [Apify MCP server](https://docs.apify.com/platform/integrations/mcp) exposes every Actor on the store: **no installation, no separate package**.

#### Step 1: Get an Apify API token

Grab a token from your [Apify account → Integrations](https://console.apify.com/account/integrations?fpr=9n7kx3).

#### Step 2: Connect your MCP client

**Claude Desktop / Claude Code**: edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com",
      "headers": { "Authorization": "Bearer YOUR_APIFY_TOKEN" }
    }
  }
}
```

**Cursor**: add `.cursor/mcp.json` in your project (or `~/.cursor/mcp.json` globally):

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com",
      "headers": { "Authorization": "Bearer YOUR_APIFY_TOKEN" }
    }
  }
}
```

**VS Code**: add `.vscode/mcp.json`:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com",
      "headers": { "Authorization": "Bearer YOUR_APIFY_TOKEN" }
    }
  }
}
```

**ChatGPT / web-only clients**: use the hosted [Apify Tester MCP Client](https://apify.com/jiri.spilka/tester-mcp-client?fpr=9n7kx3) in the browser.

#### Step 3: Ask your agent

Once connected, prompt naturally:

> *"Use Apify to get the transcript of https://www.youtube.com/watch?v=1UL8iZJD38U"*

> *"Pull YouTube transcripts for these three URLs and summarize each: …"*

> *"Find the YouTube transcript scraper on Apify and run it for this Shorts URL."*

Your client will discover this Actor via the `search-actors` tool, register it as a callable function, and feed the transcript back into the conversation.

Full reference: [Apify MCP integration docs](https://docs.apify.com/platform/integrations/mcp) · [MCP server source](https://github.com/apify/apify-mcp-server).

***

### 💸 Pay per run with crypto (x402)

The YouTube Transcripts API supports agentic payments via the [x402 protocol](https://docs.apify.com/platform/integrations/x402).
AI agents and MCP clients can pay for runs in USDC (on Base) with no Apify account or API token needed:
point your agent at the [Apify MCP server](https://mcp.apify.com/?tools=actors,docs,johnvc/YoutubeTranscripts) and it can
discover, pay for, and run this Actor autonomously. Read the
[Apify x402 announcement](https://apify.com/change-log/pay-for-apify-actors-with-x402?fpr=9n7kx3) for details.

### 🪢 Use in n8n

Prefer a visual workflow? A dedicated n8n community node wraps this transcript API so you can drop it into any pipeline with no code.

1. In n8n, open **Settings → Community Nodes → Install** and enter the package name `n8n-nodes-youtube-transcripts-api`.
2. Add your Apify API token as an **Apify API** credential (grab the token from your [Apify account → Integrations](https://console.apify.com/account/integrations?fpr=9n7kx3)).
3. Drop the **YouTube Transcripts** node into a workflow, pass one URL or an array of URLs, and wire the transcript output into your next step: Google Sheets, a database, or an LLM node.

The node is published on npm with build provenance: [`n8n-nodes-youtube-transcripts-api`](https://www.npmjs.com/package/n8n-nodes-youtube-transcripts-api). It calls this same Actor, so pricing and output fields match the API and MCP paths above.

**Ready-made n8n template.** [Summarize new YouTube videos and log them to Notion and Slack](https://n8n.io/workflows/22042-summarize-new-youtube-videos-with-apify-and-openai-and-log-to-notion-and-slack/) is a free template that checks a channel each morning, pulls the transcript for every video it has not seen with this API, has an AI model write a summary, takeaways and a relevance score into Notion, and posts to Slack only when a video mentions the topics you track.

***

### 🧰 Use it as a Claude or agent skill

Prefer to drive this API from an AI agent? These open agent skills (the [agentskills.io](https://agentskills.io/specification) standard) each wrap this Actor around a ready-made workflow and install in Claude Code, Cursor, and other skills-compatible agents:

- YouTube transcript API: fetch any video's captions as JSON, SRT, or VTT. [Repo](https://github.com/johnisanerd/claude-skill-youtube-transcript-api) - `npx skills add johnisanerd/claude-skill-youtube-transcript-api`
- YouTube transcripts as LLM training data: build a documented text corpus from a URL list. [Repo](https://github.com/johnisanerd/claude-skill-youtube-transcripts-llm-training-data) - `npx skills add johnisanerd/claude-skill-youtube-transcripts-llm-training-data`

***

### 🔧 Input

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `youtube_url` | string **or** array of strings | ✅ unless `channel` is set | - | One URL or many (**max 100 per run**). Works with standard videos, Shorts, `youtu.be`, embed, and mobile URLs. Channel URLs are detected automatically and treated as the `channel` input. Oversized batches are truncated to the first 100 with a dataset notice - split the rest into another run. |
| `languages` | array of strings | - | `["en"]` | Ordered list of ISO 639-1 language codes. First match wins. |
| `translate_to` | string | - | - | If set, translate the picked transcript into this language code. Source must be translatable (see `list_only`). |
| `transcript_type` | enum `any` / `manual` / `generated` | - | `any` | Filter by manually-created vs. auto-generated captions. |
| `output_formats` | array of strings | - | `[]` | Extras: `srt`, `vtt`, `text`. The structured fields are always included; these add formatted-subtitle strings. |
| `preserve_formatting` | boolean | - | `false` | Keep YouTube's inline `<i>` / `<b>` tags in transcript text. |
| `list_only` | boolean | - | `false` | Discovery mode: do not fetch transcripts; return the list of available transcripts per video. Free of the per-video charge. Metadata is not fetched in this mode (`include_metadata` is ignored). |
| `include_metadata` | boolean | - | `true` | Attach video metadata from the same YouTube response that carries the captions: title, description, channel name, channel ID and URL, view count, video duration, thumbnail URL, tags, was_live. No extra request, no extra charge. Ignored when `list_only` is true. |
| `include_extended_metadata` | boolean | - | `false` | Also run a second lookup for the fields the caption response does not carry: like count, upload date, categories, availability. Adds ~1-3 seconds per video and is charged as a `videometadata` event when it succeeds. Requires `include_metadata`. |
| `channel` | string **or** array of strings | - | - | One or more channels to list: an `@handle`, a bare handle, a channel ID (`UC...`), or any channel URL (`/channel/`, `/c/`, `/user/`, `/@handle`). Returns one row per video (newest first, up to `max_videos`) with `video_id`, `url`, `title`, duration, view count, and thumbnail, marked `result_type: "channel_video"`. Listing is free of the per-video transcript charge. |
| `max_videos` | integer | - | `100` | How many videos to list per channel, newest first. Max 1000. |
| `channel_transcripts` | boolean | - | `false` | Also fetch a transcript for every listed channel video (deduplicated against `youtube_url` entries, max 100 transcripts per run). Each successful transcript is charged exactly like one passed via `youtube_url`. |

#### Single video

```json
{ "youtube_url": "https://www.youtube.com/watch?v=p8gV_7zFN44" }
```

#### Batch (processed in parallel)

```json
{
  "youtube_url": [
    "https://www.youtube.com/watch?v=5kcaHAuGxmY",
    "https://www.youtube.com/watch?v=p8gV_7zFN44",
    "https://www.youtube.com/shorts/s4UkCaf_scs"
  ]
}
```

#### Pick a language

```json
{
  "youtube_url": "https://www.youtube.com/watch?v=p8gV_7zFN44",
  "languages": ["es", "en"]
}
```

Tries Spanish first, falls back to English.

#### Translate to another language

```json
{
  "youtube_url": "https://www.youtube.com/watch?v=p8gV_7zFN44",
  "languages": ["en"],
  "translate_to": "fr"
}
```

Picks the English transcript and translates it to French. If the source isn't translatable, you'll get the original English transcript back without an error.

#### Get SRT + VTT alongside JSON

```json
{
  "youtube_url": "https://www.youtube.com/watch?v=p8gV_7zFN44",
  "output_formats": ["srt", "vtt", "text"]
}
```

Adds `srt`, `vtt`, and `text` fields to each dataset item in addition to the structured `timestamped` and `non_timestamped` fields.

#### Discover what transcripts exist (no charge)

```json
{
  "youtube_url": "https://www.youtube.com/watch?v=p8gV_7zFN44",
  "list_only": true
}
```

Returns `available_transcripts` per video without fetching any captions. Useful to discover which languages and which translations are available before committing to a transcript-fetch run. Discovery runs fetch no video metadata either, so `list_only` rows carry no metadata fields and incur no `videometadata` charge.

#### List every video on a channel

```json
{
  "channel": "@mkbhd",
  "max_videos": 200
}
```

Accepts an `@handle`, a bare handle, a channel ID, or any channel URL - `{ "channel": "https://www.youtube.com/channel/UCBJycsmduvYEL83R_U4JriQ" }` works the same way. Each video becomes one dataset row (newest first) with `video_id`, `url`, `title`, `video_duration_seconds`, `view_count`, `thumbnail_url`, and `result_type: "channel_video"`. Pasting a channel URL into `youtube_url` does the same thing. Listing is not charged per video.

#### Transcribe an entire channel

```json
{
  "channel": "@mkbhd",
  "max_videos": 50,
  "channel_transcripts": true
}
```

Lists the newest 50 videos, then fetches a transcript for each one through the normal pipeline (language selection, translation, metadata, and output formats all apply). Transcripts are capped at 100 videos per run across all channels plus explicit URLs; each successful transcript is charged as one `videoprocessed` event.

> 💡 **Batch limit: 100 URLs per run.** Larger inputs are truncated to the first 100 URLs; a `truncated: true` summary row is written to the dataset with `urls_submitted` / `urls_skipped`, and the Console status message explains what happened. Split bigger jobs into multiple runs. Failed or skipped videos are still recorded (`success: false`, `error_message`) so you can retry only the missing ones.

***

### 📤 Example output

With `include_metadata: true` (the default), each dataset item carries both the transcript and the video metadata:

```json
{
  "url": "https://www.youtube.com/watch?v=p8gV_7zFN44",
  "video_id": "p8gV_7zFN44",
  "language": "English",
  "language_code": "en",
  "is_generated": false,
  "is_translatable": true,
  "translation_languages": ["es", "fr", "de"],
  "total_seconds": 4782.52,
  "duration_human": "1h 19m 42s",
  "source_type": "Manual",
  "snippet_count": 1238,
  "timestamped": [
    { "text": "Hello and welcome to this video", "start": 0.08, "duration": 3.5 }
  ],
  "non_timestamped": "Hello and welcome to this video...",
  "title": "Some video title",
  "channel_name": "Some Channel",
  "channel_id": "UCxxxxxxxxxxxxxxxxxxxxxx",
  "channel_url": "https://www.youtube.com/channel/UCxxxxxxxxxxxxxxxxxxxxxx",
  "view_count": 12345678,
  "like_count": 234567,
  "video_duration_seconds": 4783,
  "upload_date": "2024-05-12",
  "thumbnail_url": "https://i.ytimg.com/vi/p8gV_7zFN44/hqdefault.jpg",
  "tags": ["education", "tutorial"],
  "categories": ["Education"],
  "availability": "public",
  "was_live": false,
  "timestamp": "2026-06-30T10:30:00",
  "success": true
}
```

The title, description, channel, view count, duration, thumbnail, tags and `was_live` fields come from the same YouTube response that carries the captions, so they are free and add no time (`metadata_source: "innertube"`). `like_count`, `upload_date`, `categories` and `availability` need a second lookup: set `include_extended_metadata: true` to get them (`metadata_source: "yt-dlp"`, ~1-3 seconds per video, charged as `videometadata`). Set `include_metadata: false` to omit metadata entirely.

If you asked for a translation and YouTube throttled the translated caption download, the item carries the original-language transcript, no `translated_to`, and a `translation_error` explaining why. You are charged for that transcript once, and no residential retries are spent chasing the translation.

#### A video with no captions

A video with no usable captions does not fail the run and is free of the per-video transcript charge. It returns an error row that says what YouTube does offer for that video:

```json
{
  "error": true,
  "success": false,
  "error_type": "NoTranscriptFound",
  "error_message": "No captions are available for this video in the requested language(s); available_transcripts lists the caption tracks YouTube offers.",
  "is_permanent_error": true,
  "url": "https://www.youtube.com/watch?v=VIDEO_ID",
  "video_id": "VIDEO_ID",
  "available_transcripts": [
    {
      "language": "Spanish (auto-generated)",
      "language_code": "es",
      "is_generated": true,
      "is_translatable": true,
      "translation_languages": ["en", "fr", "de"]
    }
  ],
  "title": "Some video title",
  "channel_name": "Some Channel",
  "view_count": 12345678,
  "video_duration_seconds": 252,
  "cached": false,
  "fetched_at": "2026-10-01T12:14:34+00:00",
  "timestamp": "2026-10-01T12:14:34"
}
```

Here the video has no English captions but does have Spanish ones, so a second request with `languages: ["es"]` returns the transcript, with no separate `list_only` run needed. When the creator has turned captions off, `error_type` is `TranscriptsDisabled` and `available_transcripts` is empty. The title, channel and the other basic metadata fields are the same ones a successful row carries (omitted with `include_metadata: false`).

***

### ♻️ Cached results

Popular videos are requested again and again, so recently fetched transcripts are kept and can be returned from our cache instead of being fetched from YouTube a second time. A cached row arrives faster and is identical to a fresh fetch: same fields, same text, same `srt`, `vtt` and `text` formats.

- **Every transcript row says where it came from.** `cached` is `true` or `false`, and `fetched_at` is the UTC time the transcript was fetched from YouTube. Cached rows also carry `cache_age_days`.
- **Cached copies are reused for up to 90 days.** A transcript fetched within the last 90 days is returned from the cache. After that it is fetched from YouTube again on the next request.
- **Basic metadata on a cached row is as of `fetched_at`.** Title, channel, view count, description and tags were captured when the transcript was fetched, so counts on a cached row can be up to 90 days old.
- **Removed videos age out.** A transcript can stay available for up to 90 days after its video is deleted or made private.
- **Videos without captions are remembered too.** A "no captions" verdict is kept like a transcript, so asking again returns the same row at once, with `cached: true`, the caption tracks that do exist and the basic metadata. It is never charged. YouTube is checked again once the verdict is older than 90 days.
- **Extended metadata is always live.** `include_extended_metadata: true` is never answered from the cache.

***

### 💰 Pricing

Pay-per-event, engineered to be the lowest-cost YouTube transcript API on Apify:

| Event | Charged for |
|---|---|
| `videoprocessed` | each video successfully transcribed |
| `videometadata` | each successful extended metadata enrichment (`include_extended_metadata: true` and the like count, upload date, categories and availability lookup returned data); the basic metadata attached by default is free |
| `apify-actor-start` | one event per GB of memory at run start (Apify-managed) |
| `apify-default-dataset-item` | per item stored in the dataset (Apify-managed) |

The exact per-event rates for your plan are always shown on this page's pricing card (they vary slightly by plan tier). Unlike actors that charge per second of video, this one charges **per video**: a 5-hour lecture costs the same as a 60-second Short. Channel listing rows (`result_type: "channel_video"`) are never charged as `videoprocessed`. No subscription, no proxy fees. Failed videos and videos with no transcript are **not charged**. Transcripts served from the cache are charged the same `videoprocessed` event as fresh ones. Set `include_metadata: false` to avoid the metadata charge. `list_only` discovery runs fetch no metadata, so they never incur a `videometadata` charge.

***

### 🚀 Quickstart

1. Open this Actor on the [Apify store](https://apify.com/johnvc/YoutubeTranscripts?fpr=9n7kx3).
2. Paste a YouTube URL into `youtube_url` (or an array of URLs).
3. Click **Start**. Results appear in the dataset within seconds.

Prefer the API? Every Actor on Apify has an [API endpoint](https://docs.apify.com/platform/integrations) and can be called from any language, scheduled, or wired into webhooks.

***

### 🔗 Related Tools

Building a video or audio pipeline? These tools from the same catalog chain directly into this one:

- [YouTube Download API](https://apify.com/johnvc/youtube-download-api?fpr=9n7kx3): get the audio or video file for the same links, for videos with no captions or when you need the media itself for your own speech-to-text.
- [YouTube Shorts API](https://apify.com/johnvc/youtube-shorts-api?fpr=9n7kx3): list a channel's Shorts with views, likes and dates, then pull transcripts for the ones that matter.
- [All Alpha OSINT Actors on Apify](https://apify.com/johnvc?fpr=9n7kx3): the full portfolio.

### 🤖 Ask an AI assistant about this API

Open a ready-to-send prompt about the YouTube Transcripts API in the AI of your choice:

- 💬 [ChatGPT](https://chatgpt.com/?q=Using%20the%20YouTube%20Transcripts%20API%20on%20Apify%20%28https://apify.com/johnvc/YoutubeTranscripts?fpr=9n7kx3%29%2C%20walk%20me%20through%20this%20use%20case:%20%22Run%20the%20YouTube%20transcript%20API%20without%20IP%20blocks%22.%20Show%20me%20the%20input%20JSON%2C%20the%20output%20fields%2C%20and%20how%20to%20automate%20it%20with%20the%20API%20or%20MCP.)
- 🧠 [Claude](https://claude.ai/new?q=Using%20the%20YouTube%20Transcripts%20API%20on%20Apify%20%28https://apify.com/johnvc/YoutubeTranscripts?fpr=9n7kx3%29%2C%20walk%20me%20through%20this%20use%20case:%20%22Run%20the%20YouTube%20transcript%20API%20without%20IP%20blocks%22.%20Show%20me%20the%20input%20JSON%2C%20the%20output%20fields%2C%20and%20how%20to%20automate%20it%20with%20the%20API%20or%20MCP.)
- 🔍 [Perplexity](https://www.perplexity.ai/search?q=Using%20the%20YouTube%20Transcripts%20API%20on%20Apify%20%28https://apify.com/johnvc/YoutubeTranscripts?fpr=9n7kx3%29%2C%20walk%20me%20through%20this%20use%20case:%20%22Run%20the%20YouTube%20transcript%20API%20without%20IP%20blocks%22.%20Show%20me%20the%20input%20JSON%2C%20the%20output%20fields%2C%20and%20how%20to%20automate%20it%20with%20the%20API%20or%20MCP.)
- 🅒 [Copilot](https://copilot.microsoft.com/?q=Using%20the%20YouTube%20Transcripts%20API%20on%20Apify%20%28https://apify.com/johnvc/YoutubeTranscripts?fpr=9n7kx3%29%2C%20walk%20me%20through%20this%20use%20case:%20%22Run%20the%20YouTube%20transcript%20API%20without%20IP%20blocks%22.%20Show%20me%20the%20input%20JSON%2C%20the%20output%20fields%2C%20and%20how%20to%20automate%20it%20with%20the%20API%20or%20MCP.)

### ❓ FAQ

**What if the video has no transcript?**
The run does not fail. The Actor records an error row in the dataset (`success: false`, `error_type`, `error_message`) and moves on to the next URL. The row tells you what the video does have: `available_transcripts` lists its caption tracks, so when a video has no English captions but does have Spanish ones you can ask again with `languages: ["es"]`. The list is empty when the creator has turned captions off. With metadata on, the row also carries the video's title, channel and view count, so you can see which video it was. See **A video with no captions** under Example output. You are **only charged for successful transcriptions** of the requested videos.

**Does it work for YouTube Shorts?**
Yes. `https://www.youtube.com/shorts/VIDEO_ID` works out of the box, as do `youtu.be/`, `/embed/`, and `m.youtube.com` formats.

**Can I call this from an AI agent?**
Yes, that is the primary design goal. See the **Use with MCP** section above for Claude, Cursor, VS Code, and ChatGPT setup.

**Can I get transcripts for an entire channel?**
Yes. Pass the channel's @handle, ID, or URL in the `channel` input to list its videos newest first (up to `max_videos`), and set `channel_transcripts: true` to also fetch a transcript for each listed video in the same run (up to 100 transcripts per run). Listing alone is free of the per-video transcript charge.

**What about translation?**
Built in: set `translate_to` to a language code (e.g. "es", "fr", "de") and the Actor returns the transcript translated through YouTube's own translation pipeline. Each result also lists `is_translatable` and `translation_languages`, the translation targets YouTube exposes for that transcript (usually around 18 common languages). If your requested target is not available, the original transcript is returned unchanged - check the `translated_to` field to confirm a translation happened. For languages YouTube does not offer, pair the plain-text output with your favorite LLM.

**Are results cached? How fresh is the data?**
Recently fetched transcripts can be returned from our cache (see **Cached results**). Each row's `cached` and `fetched_at` fields tell you exactly which rows came from the cache and how old they are. A cached copy is never more than 90 days old; older ones are fetched from YouTube again.

**Why is this so much cheaper than other YouTube transcript scrapers?**
It reads YouTube's own caption tracks directly, with no headless browser and no AI transcription, so each video takes only a couple of small requests. Failed videos and videos without captions are never charged.

***

### 🔗 Links

- [Actor on Apify Store](https://apify.com/johnvc/YoutubeTranscripts?fpr=9n7kx3)
- [n8n community node: n8n-nodes-youtube-transcripts-api](https://www.npmjs.com/package/n8n-nodes-youtube-transcripts-api)
- [Apify MCP integration docs](https://docs.apify.com/platform/integrations/mcp)
- [Model Context Protocol](https://modelcontextprotocol.io)
- [Apify platform docs](https://docs.apify.com/)

***

### Featured Tasks

Ready-to-run examples that show this API solving a specific problem. Each opens its own setup so you can
run it on your account in one click.

- [Get YouTube transcripts in Claude Code via MCP](https://apify.com/johnvc/YoutubeTranscripts/examples/get-youtube-transcripts-in-claude-code-via-mcp?fpr=9n7kx3) - pull any video's transcript straight into [Claude Code](https://claude.ai/referral/uIlpa7nPLg) (free trial available) through MCP, with timestamped and plain text output.
- [Use YouTube transcripts in Claude Cowork via MCP](https://apify.com/johnvc/YoutubeTranscripts/examples/use-youtube-transcripts-in-claude-cowork-via-mcp?fpr=9n7kx3) - fetch transcripts inside [Claude Cowork](https://claude.ai/referral/uIlpa7nPLg) (free trial available) as an MCP plugin for research and document workflows.
- [Download a batch of YouTube transcripts in bulk](https://apify.com/johnvc/YoutubeTranscripts/examples/download-a-batch-of-youtube-transcripts-in-bulk?fpr=9n7kx3) - transcribe a whole list of videos in one run, one dataset row per video with language metadata.

#### For developers

- [Run the YouTube transcript API without IP blocks](https://apify.com/johnvc/YoutubeTranscripts/examples/run-youtube-transcript-api-without-ip-block?fpr=9n7kx3) - fetch transcripts at scale without IpBlocked or RequestBlocked errors.
- [Get YouTube transcripts without API rate limits](https://apify.com/johnvc/YoutubeTranscripts/examples/youtube-transcript-api-no-rate-limit?fpr=9n7kx3) - pull transcripts in bulk without 429 rate-limit errors.
- [Translate YouTube subtitles via API](https://apify.com/johnvc/YoutubeTranscripts/examples/translate-youtube-subtitles-via-api?fpr=9n7kx3) - render any video's captions into another language with the translate_to input.
- [YouTube transcript API in Python without IP blocks](https://apify.com/johnvc/YoutubeTranscripts/examples/youtube-transcript-api-in-python-no-ip-block?fpr=9n7kx3) and [for Node.js and JavaScript](https://apify.com/johnvc/YoutubeTranscripts/examples/youtube-transcript-api-for-node-js?fpr=9n7kx3).
- [Get a YouTube Shorts transcript by URL](https://apify.com/johnvc/YoutubeTranscripts/examples/get-youtube-shorts-transcript-by-url?fpr=9n7kx3) - paste any Shorts URL and get its transcript.
- [Hosted YouTube transcript MCP server](https://apify.com/johnvc/YoutubeTranscripts/examples/hosted-youtube-transcript-mcp-server?fpr=9n7kx3) - a no-setup MCP server that returns transcripts as a callable tool.

#### Transcripts by language

Translate any video's captions on demand. Representative pages: [Spanish](https://apify.com/johnvc/YoutubeTranscripts/examples/youtube-transcripts-in-spanish?fpr=9n7kx3), [French](https://apify.com/johnvc/YoutubeTranscripts/examples/youtube-transcripts-in-french?fpr=9n7kx3), [German](https://apify.com/johnvc/YoutubeTranscripts/examples/youtube-transcripts-in-german?fpr=9n7kx3), [Japanese](https://apify.com/johnvc/YoutubeTranscripts/examples/youtube-transcripts-in-japanese?fpr=9n7kx3), [Korean](https://apify.com/johnvc/YoutubeTranscripts/examples/youtube-transcripts-in-korean?fpr=9n7kx3), [Russian](https://apify.com/johnvc/YoutubeTranscripts/examples/youtube-transcripts-in-russian?fpr=9n7kx3), [Hindi](https://apify.com/johnvc/YoutubeTranscripts/examples/youtube-transcripts-in-hindi?fpr=9n7kx3), [Portuguese](https://apify.com/johnvc/YoutubeTranscripts/examples/youtube-transcripts-in-portuguese?fpr=9n7kx3), [Arabic](https://apify.com/johnvc/YoutubeTranscripts/examples/youtube-transcripts-in-arabic?fpr=9n7kx3), and more. See the full set on the Examples tab.

In-language landing pages: [YouTube字幕下载 API (Chinese)](https://apify.com/johnvc/YoutubeTranscripts/examples/youtube-subtitle-download-api-chinese?fpr=9n7kx3), [YouTube字幕翻译 API (Chinese)](https://apify.com/johnvc/YoutubeTranscripts/examples/youtube-subtitle-translate-api-chinese?fpr=9n7kx3), [Скачать субтитры YouTube (Russian)](https://apify.com/johnvc/YoutubeTranscripts/examples/download-youtube-subtitles-via-api-russian?fpr=9n7kx3), [Перевод субтитров YouTube (Russian)](https://apify.com/johnvc/YoutubeTranscripts/examples/translate-youtube-subtitles-via-api-russian?fpr=9n7kx3).

#### Use in your MCP client

Connect the actor as a callable MCP tool in [ChatGPT](https://apify.com/johnvc/YoutubeTranscripts/examples/youtube-transcripts-in-chatgpt-via-mcp?fpr=9n7kx3), [Cursor](https://apify.com/johnvc/YoutubeTranscripts/examples/youtube-transcripts-in-cursor-via-mcp?fpr=9n7kx3), [Visual Studio Code](https://apify.com/johnvc/YoutubeTranscripts/examples/youtube-transcripts-in-visual-studio-code-via-mcp?fpr=9n7kx3), [Windsurf](https://apify.com/johnvc/YoutubeTranscripts/examples/youtube-transcripts-in-windsurf-via-mcp?fpr=9n7kx3), or [Cline](https://apify.com/johnvc/YoutubeTranscripts/examples/youtube-transcripts-in-cline-via-mcp?fpr=9n7kx3).

***

### 📡 Where the transcripts come from

Every row in the dataset is built from the caption track that [YouTube](https://www.youtube.com) already publishes on the public video page. Nothing here is run through speech-to-text, so the wording you get back is the same wording a viewer sees in the transcript panel, delivered as `timestamped` snippets with `start` and `duration`, plus one clean `non_timestamped` block for feeding straight into an LLM. Short-form videos are covered the same way: paste a [YouTube Shorts](https://www.youtube.com/shorts) URL and it resolves to the same `video_id` as any other URL format. This Actor is an independent tool. It is not affiliated with, endorsed by, or operated by YouTube or Google, and it is not YouTube's own API.

#### Does YouTube have a public transcript API?

Not one you can point at arbitrary videos for bulk transcript text. [YouTube's](https://www.youtube.com) own Data API will list caption tracks, but actually downloading a caption file through it requires OAuth as the channel owner, so it only works on videos you already control. This Actor reads caption tracks that are public on the video page, which is why it works on videos you do not own, and it returns `language`, `language_code`, `is_generated`, `is_translatable`, and `translation_languages` next to the text so you know exactly which track you got.

#### How is this different from grabbing captions by hand?

By hand you open the video, expand the transcript panel, copy, paste, then do it again. Fine for one video, painful by the twentieth. Send an array of URLs instead and each one lands as its own dataset row with `video_id`, `total_seconds`, the transcript, and, when `include_metadata` is on, the title, channel, view count, and upload date. If you are not sure which languages a batch actually has, run it once with `list_only: true` first: that returns `available_transcripts` per video and is not charged as a `videoprocessed` event.

***

### 🌐 About Alpha OSINT

This Actor is part of [Alpha OSINT](https://www.alphaosint.com), toolset of financial and operations data sources and APIs.
See the [YouTube Transcripts API source page](https://www.alphaosint.com/sources/youtube-transcripts-api/) for related tools and use cases.
For support or requests for this actor, please start a ticket [directly on our support page](https://apify.com/johnvc/YoutubeTranscripts/issues/open?fpr=9n7kx3).

Last Updated: 2026.10.27

# Actor input Schema

## `youtube_url` (type: `string,array`):

Required unless 'channel' is provided. Provide one YouTube URL as a string, or multiple as an array (max 100 URLs per run). Works with standard videos, Shorts, youtu.be short links, embed URLs, and m.youtube.com mobile URLs. Channel URLs (youtube.com/@handle, /channel/, /c/, /user/) are detected automatically and treated as the 'channel' input. Each video URL is processed in parallel. If more than 100 URLs are submitted, only the first 100 are processed and a truncation notice is written to the dataset - split larger batches into multiple runs. Invalid URLs are recorded as errors in the dataset but do not stop the run. Charged per video successfully transcribed.

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

Ordered list of ISO 639-1 language codes. The first available transcript matching one of these languages is returned. Example: \["en", "es", "fr"] tries English first, then Spanish, then French. Defaults to \["en"] when omitted.

## `translate_to` (type: `string`):

Optional. If set to an ISO 639-1 code (e.g. "es", "fr", "de"), the picked transcript is translated to this language. Requires the source transcript to be translatable - run with list_only=true first to see translation_languages for each available transcript. If translation is unavailable, the original transcript is returned (no error).

## `transcript_type` (type: `string`):

Filter which transcript variant to consider when both manually-created and auto-generated captions exist for a language. "any" picks whichever matches the language priority first; "manual" only considers human-made captions; "generated" only considers auto-generated.

## `output_formats` (type: `array`):

Optional list of extra output formats to include in the dataset alongside the default structured fields. Allowed values: "srt" (SubRip subtitles), "vtt" (WebVTT subtitles), "text" (plain text, newline-separated). Unknown values are ignored with a warning. The structured fields (timestamped, non_timestamped, etc.) are always included.

## `preserve_formatting` (type: `boolean`):

If true, keep YouTube's inline HTML formatting tags (<i>, <b>) in transcript text. Default false (strips them for clean text).

## `list_only` (type: `boolean`):

If true, do NOT fetch any transcript. Instead, for each URL return the list of all available transcripts with their language code, generated/manual status, is_translatable flag, and translation_languages. Useful for discovering what languages exist before picking one. Not charged as a videoprocessed event. Video metadata is not fetched in discovery mode (include_metadata is ignored).

## `include_metadata` (type: `boolean`):

If true, attach video metadata from the same YouTube response that carries the captions: title, description, channel_name, channel_id, channel_url, view_count, video_duration_seconds, thumbnail_url, tags, was_live. No extra request and not charged. For like_count, upload_date, categories and availability enable include_extended_metadata. Ignored when list_only is true. Defaults to true.

## `include_extended_metadata` (type: `boolean`):

If true, run a second lookup per video for the fields the caption response does not carry: like_count, upload_date, categories, availability. Adds about 1-3 seconds per video and is charged as a videometadata event when it succeeds; if it fails, the transcript and the basic metadata are still returned and metadata_error is populated. Requires include_metadata. Defaults to false.

## `channel` (type: `string,array`):

Optional. One YouTube channel as a string, or multiple as an array. Accepts an @handle ("@mkbhd"), a bare handle ("mkbhd"), a channel ID ("UCBJycsmduvYEL83R_U4JriQ"), or any channel URL (/channel/, /c/, /user/, /@handle; a /videos, /shorts, or /streams tab URL keeps that tab). Each channel produces one dataset row per video, newest first, up to max_videos: video_id, url, title, duration, view count, and thumbnail. Rows are marked result_type="channel_video". Listing is not charged as a videoprocessed event.

## `max_videos` (type: `integer`):

Maximum number of videos to list per channel, newest first. Default 100. Values above 1000 are capped at 1000 (and values below 1 are raised to 1) with a log warning rather than failing the run. When channel_transcripts is true, at most 100 videos per run (across all channels plus explicit URLs) proceed to transcript extraction.

## `channel_transcripts` (type: `boolean`):

If true, every video listed from the channel(s) is also queued for transcript extraction (newest first, deduplicated against youtube_url entries, subject to the 100 videos per run limit). Each successful transcript is charged as a videoprocessed event, exactly like videos passed via youtube_url. Default false: channels are only listed.

## Actor input object example

```json
{
  "youtube_url": "https://www.youtube.com/watch?v=1UL8iZJD38U",
  "languages": [
    "en"
  ],
  "transcript_type": "any",
  "output_formats": [],
  "preserve_formatting": false,
  "list_only": false,
  "include_metadata": true,
  "include_extended_metadata": false,
  "max_videos": 100,
  "channel_transcripts": false
}
```

# Actor output Schema

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

Complete transcript data for all processed YouTube videos. Each item includes video metadata (URL, video ID, language info), timestamped transcript snippets with start times and durations, full non-timestamped transcript text, and processing status. Failed videos include error information.

# 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 = {
    "youtube_url": "https://www.youtube.com/watch?v=1UL8iZJD38U",
    "languages": [
        "en"
    ],
    "output_formats": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("johnvc/youtubetranscripts").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 = {
    "youtube_url": "https://www.youtube.com/watch?v=1UL8iZJD38U",
    "languages": ["en"],
    "output_formats": [],
}

# Run the Actor and wait for it to finish
run = client.actor("johnvc/youtubetranscripts").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 '{
  "youtube_url": "https://www.youtube.com/watch?v=1UL8iZJD38U",
  "languages": [
    "en"
  ],
  "output_formats": []
}' |
apify call johnvc/youtubetranscripts --silent --output-dataset

```

## MCP server setup

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

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/zPumutvB61fpEsglh/builds/r9NHnVUdQbLuf291B/openapi.json
