# YouTube Transcript Scraper - Captions & Subtitles (`herus13/youtube-transcript-scraper`) Actor

Turn any YouTube video, playlist or channel into text. Input: video URLs or IDs and an optional language code (en, es, zh). Output per video: transcript_text, timestamped segments, language, is_generated (auto vs manual captions), title, channel_name, view_count, published_at. No API key.

- **URL**: https://apify.com/herus13/youtube-transcript-scraper.md
- **Developed by:** [herus13](https://apify.com/herus13) (community)
- **Categories:** Videos, AI, Social media
- **Features:** Limited permissions
- **Stats:** 14 total users, 2 monthly users, 96.6% 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

YouTube Transcript Scraper extracts the full transcript of any public [YouTube](https://www.youtube.com) video, playlist or channel as clean text with timestamps. Get YouTube subtitles and captions, human-written or auto-generated, in any language the video offers, plus title, channel, views and publish date. YouTube to text for AI, RAG and research. No API key.

### What you get

One dataset row per video transcript, ready to export as JSON, CSV or Excel.

```json
{
  "video_id": "dQw4w9WgXcQ",
  "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "title": "Rick Astley - Never Gonna Give You Up (Official Video) (4K Remaster)",
  "channel_name": "Rick Astley",
  "duration_seconds": 213,
  "published_at": "2009-10-24T23:57:33-07:00",
  "view_count": 1793953812,
  "language": "en",
  "is_generated": false,
  "transcript_text": "[♪♪♪]\n♪ We're no strangers to love ♪\n♪ You know the rules and so do I ♪",
  "scraped_at": "2026-07-18T09:03:17.597050+00:00",
  "_meta": {"author": "https://apify.com/herus13", "status": "ok", "message": null}
}
```

- `transcript_text` is the whole transcript as one string, one caption line per line: the shape an LLM prompt, a summarizer or a search index wants.
- `transcript_segments` is the same transcript with timestamps, as a JSON list of `{text, start, duration}` (seconds). Use it for jump-to-moment links and quotes with a time.
- `transcript_formatted` is empty by default. Set **Subtitle file format** to `srt` or `vtt` and it holds the whole transcript as a ready-to-save subtitle file.
- `language` is the caption track you got, and `is_generated` says whether it was auto-generated by speech recognition (`true`) or written by a person (`false`).
- `available_languages` lists every caption track the video offers, so you can see what else to ask for.
- Rows also carry `channel_id`, `description` and `thumbnail_url`.

`_meta` is run metadata, not scraped data. A video that yields no transcript appears as a row with only `_meta` (status `failed`, the reason in `message`), and those rows are never charged. Every other row is a delivered transcript.

### What it costs

| Event | Price |
|---|---|
| Transcript (`transcript`) | $0.003 |

You pay per transcript delivered, not per minute: $0.003 a video, with no fee to start the run. A hundred videos is about $0.30. A video with no captions, or none in the language you asked for, returns only a `_meta` row, is named in the run log and is not charged. Expanding a playlist or channel into its videos costs nothing, so you only pay for what comes back. The residential proxy the run needs is included in that price.

### Input

| Field | Type | Required | Default | What it does |
|---|---|---|---|---|
| `video_urls` | array | no | — | YouTube links to transcribe, one per entry, mixing three kinds freely. A video: `watch?v=…`, `youtu.be/…`, `/shorts/…`, `/embed/…`, or a bare 11-character ID. A playlist: `/playlist?list=PL…` — every video in it is transcribed. A channel: `/@handle`, `/channel/UC…`, `/c/Name` or `/user/Name` — its uploads, newest first. A `watch?v=…&list=…` link counts as the single video. Duplicates are dropped, and an entry that is not a YouTube link or ID is skipped. Leave empty and nothing runs. |
| `language` | string | no | — | Caption language as a language code, for example `en`, `es`, `zh`, `ja`, `pt-BR`. A short code also matches its regional tracks: `zh` takes `zh-CN` or `zh-TW`. Leave empty to take the track the video treats as its default. Captions come only in languages the video has a track for; nothing is machine-translated. A video with no track in that language returns only a `_meta` row, is named in the run log, and is not charged. Each row's `available_languages` lists every track the video offers. |
| `prefer_manual` | boolean | no | `true` | When a video has both human-written captions and auto-generated (speech-recognition) captions in the language you asked for, take the human ones, which are punctuated and more accurate. Default true. Set it to false to take the auto-generated track instead. A video with only one kind of track returns that kind regardless. Each row's `is_generated` field says which kind you got. |
| `language_fallback` | boolean | no | `false` | Default false (off): a video with no caption track in the language you asked for returns no transcript and is not charged. Turn on to take the video's default track instead, so the video is delivered in another language. Each row's `language` says which track you got. |
| `output_format` | string | no | `"text"` | Default text: only `transcript_text` and `transcript_segments`. Choose `srt` (SubRip) or `vtt` (WebVTT) to also get the transcript as ready-to-save subtitle text in the `transcript_formatted` column. |
| `max_videos_per_source` | integer | no | — | How many videos to take from each playlist or channel you paste, at least 1. Leave empty to use 100, the built-in cap that stops one channel link from turning into a very large run. It does nothing to entries that are already a single video. |
| `max_records` | integer | no | — | Ceiling on how many videos the whole run processes, counted after playlists and channels have expanded, at least 1. Leave empty for no ceiling. It is the cost ceiling for a run that starts from a channel whose size you do not know. |
| `proxyUrls` | array | no | — | Leave empty and the run uses the residential proxy this actor ships with, included in the price of the run. To route the run through your own account instead, add one gateway URL per entry, for example http://user:pass@host:port — works with any provider that issues URLs. When set, the run uses these first and switches to the included proxy if they stop working, so a broken proxy never fails the run. |

### How to run it

1. Open the actor on Apify Console and click **Try for free**.
2. Paste video or playlist links into **Video, playlist, or channel URLs**. You can mix videos, playlists and channels in one run.
3. Type a caption language code if you want a specific one (`en`, `es`, `zh`), then Run. Export JSON, CSV or Excel from the dataset.

```json
{
  "video_urls": [
    "https://www.youtube.com/watch?v=arj7oStGLkU",
    "https://youtu.be/UF8uR6Z6KLc",
    "https://www.youtube.com/playlist?list=PLOU2XLYxmsIKpaV8h0AGE05so0fAwwfTw"
  ],
  "language": "en",
  "prefer_manual": true,
  "max_videos_per_source": 50,
  "max_records": 200
}
```

Minimal input:

```json
{
  "video_urls": ["dQw4w9WgXcQ"]
}
```

AI agents can call it as the MCP tool `herus13--youtube-transcript-scraper` on mcp.apify.com, or through the Apify API, with the same JSON. Pass one or more video, playlist or channel links in `video_urls`, and read `transcript_text` from each row.

### Why this scraper

- **Transcript, timestamps and metadata in one row.** No chaining a captions tool to a separate metadata scraper and joining the two on a video ID.
- **Playlists and channels are valid input.** Paste one playlist or channel link and every video in it is found and transcribed, capped where you tell it to stop.
- **Ready for a prompt or for a subtitle file.** `transcript_text` is clean newline-separated text; `transcript_segments` keeps the timings, so `.srt` and `.vtt` need no re-timing.
- **No API key to obtain.** There is no official key that returns caption text, so there is nothing to apply for, meter or rotate.
- **You are charged for transcripts, not attempts.** A video with no captions returns only a `_meta` row and is never billed; the run log names it.

### Use cases

- **RAG pipelines and LLM knowledge bases.** A conference playlist or a course becomes a set of `transcript_text` strings ready to chunk and embed, each keeping the title, channel and publish date a citation needs.
- **Podcast and interview transcripts.** Hour-long episodes come back as full text with timestamps, ready for show notes, quotes and search.
- **Content repurposing.** Turn a webinar, lecture or talk into blog drafts, newsletters and social copy by feeding the transcript to a summarizer.
- **Video SEO and competitor research.** Read what the top videos for a keyword actually say, not what their titles claim, weighted by `view_count` and `published_at`.
- **Subtitles, translation and accessibility.** Set **Subtitle file format** to get `.srt` or `.vtt` text in `transcript_formatted`, or pull an existing Spanish, Chinese or Japanese track after checking `available_languages`.
- **Channel monitoring.** Re-run a channel weekly and follow how its talking points change over time.

### FAQ

**Which languages can I get?** Any language the video has a caption track in. Put a language code in **Preferred caption language**: `en`, `es`, `fr`, `de`, `ja`, `ko`, `pt-BR` and so on. A short code also matches regional tracks, so `zh` returns `zh-CN` or `zh-TW`. Leave it empty for the video's default track. Every row lists the video's other tracks in `available_languages`.

**Does it translate transcripts?** No. It returns caption tracks that exist on YouTube, written by the uploader or generated by YouTube's speech recognition. Many popular videos carry human translations as their own tracks, and those come back like any other language.

**Auto-generated or manual captions: which do I get?** Human-written captions by default, when the video has them in your language, because they are punctuated and more accurate. Set **Prefer human (manual) captions** to `false` to take the auto-generated track. A video with only auto-generated captions returns those whatever the setting says. `is_generated` on each row tells you which one you got.

**What happens with a video that has no captions?** It returns only a `_meta` row with status `failed` and the reason in `message` (no captions at all, none in the language you asked for, deleted or private), is named in the run log, and is not charged. A run where the videos simply have no captions ends as succeeded, with no transcript rows; a run where every video was blocked ends as failed.

**Can I transcribe a whole playlist or channel?** Yes. Paste the playlist link (`/playlist?list=…`) or channel link (`/@handle`, `/channel/UC…`) and its videos are transcribed, newest first for a channel. **Max videos per playlist/channel** caps each at 100 by default, and **Max videos (whole run)** caps the run. Mixes and radio lists (`list=RD…`) and private lists such as Watch Later or Liked are not supported.

**Do I get timestamps?** Yes. `transcript_segments` gives every caption line with its `start` and `duration` in seconds. `transcript_text` is the same transcript without timings.

**Does it work with YouTube Shorts?** Yes. Paste a `/shorts/…` link or its ID like any other video.

**Is a proxy included?** A residential proxy is included in the price of the run. To use your own, paste gateway URLs into **Your own proxy URLs**.

**What happens if my input is wrong?** The run stops before it scrapes anything, so no results are charged. Its status and log name each field that is wrong and say what to change — fix the input and run again.

**Is scraping YouTube transcripts legal?** This actor collects only publicly available caption text and metadata that YouTube shows to any viewer. You are responsible for complying with YouTube's terms and applicable law for your use case, including attribution and the copyright of the spoken content itself.

### Related actors

Building a content-intelligence pipeline? Pair this actor with:

- [Google Trends Scraper](https://apify.com/herus13/google-trends-scraper) — check whether a video's topic is rising before you write about it.
- [Google Play App Search & Reviews Scraper](https://apify.com/herus13/google-play-reviews-scraper) — pair video content with what app users actually say.
- [Apple App Store Reviews Scraper](https://apify.com/herus13/apple-app-store-scraper) — the iOS side of the same review picture.
- [levels.fyi Salary Scraper](https://apify.com/herus13/levels-fyi-salary-scraper) — compensation data for career and tech-content research.

# Actor input Schema

## `video_urls` (type: `array`):

YouTube links to transcribe, one per entry, mixing three kinds freely. A video: `watch?v=…`, `youtu.be/…`, `/shorts/…`, `/embed/…`, or a bare 11-character ID. A playlist: `/playlist?list=PL…` — every video in it is transcribed. A channel: `/@handle`, `/channel/UC…`, `/c/Name` or `/user/Name` — its uploads, newest first. A `watch?v=…&list=…` link counts as the single video. Duplicates are dropped, and an entry that is not a YouTube link or ID is skipped. Leave empty and nothing runs.

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

Caption language as a language code, for example `en`, `es`, `zh`, `ja`, `pt-BR`. A short code also matches its regional tracks: `zh` takes `zh-CN` or `zh-TW`. Leave empty to take the track the video treats as its default. Captions come only in languages the video has a track for; nothing is machine-translated. A video with no track in that language returns only a `_meta` row, is named in the run log, and is not charged. Each row's `available_languages` lists every track the video offers.

## `prefer_manual` (type: `boolean`):

When a video has both human-written captions and auto-generated (speech-recognition) captions in the language you asked for, take the human ones, which are punctuated and more accurate. Default true. Set it to false to take the auto-generated track instead. A video with only one kind of track returns that kind regardless. Each row's `is_generated` field says which kind you got.

## `language_fallback` (type: `boolean`):

Default false (off): a video with no caption track in the language you asked for returns no transcript and is not charged. Turn on to take the video's default track instead, so the video is delivered in another language. Each row's `language` says which track you got.

## `output_format` (type: `string`):

Default text: only `transcript_text` and `transcript_segments`. Choose `srt` (SubRip) or `vtt` (WebVTT) to also get the transcript as ready-to-save subtitle text in the `transcript_formatted` column.

## `max_videos_per_source` (type: `integer`):

How many videos to take from each playlist or channel you paste, at least 1. Leave empty to use 100, the built-in cap that stops one channel link from turning into a very large run. It does nothing to entries that are already a single video.

## `max_records` (type: `integer`):

Ceiling on how many videos the whole run processes, counted after playlists and channels have expanded, at least 1. Leave empty for no ceiling. It is the cost ceiling for a run that starts from a channel whose size you do not know.

## `proxyUrls` (type: `array`):

Leave empty and the run uses the residential proxy this actor ships with, included in the price of the run. To route the run through your own account instead, add one gateway URL per entry, for example http://user:pass@host:port — works with any provider that issues URLs. When set, the run uses these first and switches to the included proxy if they stop working, so a broken proxy never fails the run.

## Actor input object example

```json
{
  "video_urls": [
    "dQw4w9WgXcQ"
  ],
  "language": "",
  "prefer_manual": true,
  "language_fallback": false,
  "output_format": "text"
}
```

# Actor output Schema

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

Per-video transcript, metadata, and available caption languages. Each row also carries \_meta: run metadata, not scraped data.

# 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 = {
    "video_urls": [
        "dQw4w9WgXcQ"
    ],
    "language": ""
};

// Run the Actor and wait for it to finish
const run = await client.actor("herus13/youtube-transcript-scraper").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 = {
    "video_urls": ["dQw4w9WgXcQ"],
    "language": "",
}

# Run the Actor and wait for it to finish
run = client.actor("herus13/youtube-transcript-scraper").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 '{
  "video_urls": [
    "dQw4w9WgXcQ"
  ],
  "language": ""
}' |
apify call herus13/youtube-transcript-scraper --silent --output-dataset

```

## MCP server setup

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

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/mv4pc9VeaHSBQejw9/builds/Sa65eYGPId9MDxutY/openapi.json
