# YouTube Transcript Scraper — Captions & Subtitles, No API Key (`herus13/youtube-transcript-scraper`) Actor

Scrape YouTube video transcripts (captions/subtitles) with timestamps, plus video metadata and available caption languages. HTTP-first, fast, CSV/JSON output. No API key.

- **URL**: https://apify.com/herus13/youtube-transcript-scraper.md
- **Developed by:** [bootforge](https://apify.com/herus13) (community)
- **Categories:** AI, Developer tools, Marketing
- **Stats:** 2 total users, 1 monthly users, 94.1% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $8.00 / 1,000 transcripts

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## YouTube Transcript Scraper — Captions & Subtitles, No API Key

YouTube Transcript Scraper is an Apify actor that extracts the full transcript (captions/subtitles) — with per-line timestamps — from any public [YouTube](https://www.youtube.com) video, plus the video's metadata (title, channel, view count, duration, publish date, description, thumbnail) and every caption language available for it. No official API key required.

Use it to feed transcripts into a RAG pipeline or LLM summarizer, build searchable video archives, generate subtitles/blog posts from spoken content, monitor a channel's talking points, or bulk-export captions for research — exported to JSON, CSV, or Excel.

### Table of contents

- [What the YouTube Transcript Scraper does](#what-the-youtube-transcript-scraper-does)
- [Use cases](#use-cases)
- [How to scrape YouTube transcripts](#how-to-scrape-youtube-transcripts)
- [YouTube Transcript Scraper input](#youtube-transcript-scraper-input)
- [What data you get](#what-data-you-get)
- [Pricing](#pricing)
- [Recommended proxies](#recommended-proxies)
- [Why this YouTube Transcript Scraper](#why-this-youtube-transcript-scraper)
- [FAQ](#faq)
- [Rate this actor](#rate-this-actor-)
- [Related actors](#related-actors)

### What the YouTube Transcript Scraper does

- 📝 **Full transcript with timestamps** — every caption line as `{text, start, duration}`, plus a `transcript_text` string (one caption per line) ready for an LLM prompt.
- 📚 **Videos, playlists, and channels** — paste a playlist or channel URL and every video in it is discovered and transcribed; mix them with individual video URLs in one run. Duplicates across sources are scraped and billed once.
- 🌐 **Language selection** — pick a caption language code (`en`, `es`, `ja`, …) and prefer human-made captions over auto-generated ones when both exist.
- 🎬 **Video metadata included** — title, channel name/ID, duration, publish date, view count, description, and thumbnail URL, in the same row as the transcript.
- 🗣️ **Available-languages list** — every caption track YouTube offers for the video (code, name, human vs. auto-generated), so you know what else you could pull.
- ⚡ **HTTP-only, no anti-bot browser tier** — no login, no CAPTCHA solving; the actor replicates the same InnerTube requests the YouTube web/app player itself makes.

### Use cases

**RAG pipelines and LLM knowledge bases** — Point it at a conference playlist or a course channel and every video in it comes back as `transcript_text`, a single newline-separated string ready to chunk and embed. Because the transcript ships in the same row as the video's title, channel, and publish date, each chunk keeps the provenance metadata a retrieval step needs for citations — no second call to a metadata scraper, no join key to maintain.

**Content repurposing** — Turn spoken content into written content: blog drafts, newsletters, LinkedIn posts, podcast show notes. Marketing and content teams feed `transcript_text` straight into a summarizer prompt; one 40-minute webinar reliably yields a long-form post plus a week of social copy.

**Video SEO and competitor content analysis** — Pull transcripts from the top-ranking videos for a keyword you care about and analyze what those creators actually say, not just what their titles and descriptions claim. Surfaces topic coverage, recurring hooks, and the gaps your own script should fill. `view_count` and `published_at` arrive in the same row, so you can weight findings by how well each video performed.

**Podcast, webinar, and interview archives** — Convert long-form recordings into timestamped searchable text. `transcript_segments` gives `{text, start, duration}` per line, which is what you need to build a jump-to-timestamp index over an archive, or to let researchers locate the exact moment a claim was made.

**Subtitle and caption files** — The same `transcript_segments` array converts directly into `.srt` or `.vtt` with no re-timing work, for accessibility compliance, localization workflows, or re-uploading captions to another platform. `available_languages` tells you every track YouTube already has for a video before you commit to translating one yourself.

**Media monitoring and research** — Track what a set of channels is saying over time by scheduling a run against their channel URLs; each run picks up whatever they've published since. Academic and journalism workflows use the bulk export (JSON, CSV, Excel) to build a corpus, then run their own analysis over it. Failed videos come back flagged via `error` rather than silently dropped, so a longitudinal dataset never develops invisible holes.

### How to scrape YouTube transcripts

1. Click **Try for free** and open the actor.
2. Add one or more `video_urls` — **videos, playlists, and channels can be mixed freely** in the same list. Videos accept watch URLs, `youtu.be` links, Shorts/embed links, or bare 11-character IDs; playlists accept `/playlist?list=PL…`; channels accept `/@handle`, `/channel/UC…`, `/c/Name`, or `/user/Name`.
3. Optionally set `language` (e.g. `en`) — leave empty to use the video's default caption track.
4. Toggle `prefer_manual` (default **on**) to prefer human-made captions over auto-generated ones when a video has both for your chosen language.
5. Set `max_videos_per_source` if you're passing a playlist or channel — it caps how many videos each one expands into (default **100**).
6. Click **Start** and watch rows stream into the dataset — one row per video.
7. Export as **JSON, CSV, or Excel**, or pull from the [Apify API](https://docs.apify.com/api/v2).

Single video, default (English track if present, human captions preferred):

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

Multiple videos, explicit language:

```json
{
  "video_urls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "https://youtu.be/9bZkp7q19f0"
  ],
  "language": "en"
}
```

Prefer auto-generated captions over manual ones, capped to 5 videos this run:

```json
{
  "video_urls": ["dQw4w9WgXcQ", "9bZkp7q19f0", "kJQP7kiw5Fk"],
  "prefer_manual": false,
  "max_records": 5
}
```

A whole playlist — every video in it, up to the default 100:

```json
{
  "video_urls": ["https://www.youtube.com/playlist?list=PLrAXtmRdnEQy6nuLMfO6uJbgxLD1ExgGF"]
}
```

A channel's 20 most recent uploads, plus two specific videos, in one run:

```json
{
  "video_urls": [
    "https://www.youtube.com/@RickAstleyYT",
    "dQw4w9WgXcQ",
    "https://youtu.be/9bZkp7q19f0"
  ],
  "max_videos_per_source": 20
}
```

A video that appears both in the channel and in the explicit list is scraped — and billed — **once**.

### YouTube Transcript Scraper input

| Field | Type | Default | Description |
|---|---|---|---|
| `video_urls` | string\[] | — | Videos, playlists, and channels, mixed freely. **Videos:** `watch?v=…`, `youtu.be/…`, `/shorts/…`, `/embed/…`, or a bare 11-char ID. **Playlists:** `/playlist?list=PL…` or a bare playlist ID. **Channels:** `/@handle`, `/channel/UC…`, `/c/Name`, `/user/Name`. Required — at least one valid entry. |
| `language` | string | `""` (video default) | Preferred caption language code, e.g. `en`, `es`, `ja`. |
| `prefer_manual` | boolean | `true` | When both human and auto-generated captions exist for the chosen language, pick the human ones. |
| `max_videos_per_source` | integer | `100` | How many videos to take from **each** playlist or channel. Ignored for entries that are already a single video. |
| `max_records` | integer | — | Hard cap on videos processed across the whole run, applied after playlists and channels expand. |
| `proxy` | object | bundled residential | **Managed by the actor — nothing to configure.** It runs on its own residential proxy, which YouTube requires. Advanced users may override via the API with a provider/`raw_urls` object. |

### What data you get

One row per requested video. Sample from a real local run (`dQw4w9WgXcQ`, trimmed for length):

```json
{
  "video_id": "dQw4w9WgXcQ",
  "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "scraped_at": "2026-07-18T09:03:17.597050+00:00",
  "title": "Rick Astley - Never Gonna Give You Up (Official Video) (4K Remaster)",
  "channel_id": "UCuAXFkgsw1L7xaCfnd5JJOw",
  "channel_name": "Rick Astley",
  "duration_seconds": 213,
  "published_at": "2009-10-24T23:57:33-07:00",
  "view_count": 1793953812,
  "description": "The official video for “Never Gonna Give You Up” by Rick Astley. ...",
  "thumbnail_url": "https://i.ytimg.com/vi_webp/dQw4w9WgXcQ/sddefault.webp",
  "language": "en",
  "is_generated": false,
  "transcript_text": "[♪♪♪]\n♪ We're no strangers to love ♪\n♪ You know the rules and so do I ♪\n...",
  "transcript_segments": "[{\"text\": \"[♪♪♪]\", \"start\": 1.36, \"duration\": 1.68}, {\"text\": \"♪ We're no strangers to love ♪\", \"start\": 18.64, \"duration\": 3.24}, ...]",
  "available_languages": "[{\"code\": \"en\", \"name\": \"English\", \"is_generated\": false}, {\"code\": \"en\", \"name\": \"English (auto-generated)\", \"is_generated\": true}, {\"code\": \"de-DE\", \"name\": \"German (Germany)\", \"is_generated\": false}, {\"code\": \"ja\", \"name\": \"Japanese\", \"is_generated\": false}, {\"code\": \"pt-BR\", \"name\": \"Portuguese (Brazil)\", \"is_generated\": false}, {\"code\": \"es-419\", \"name\": \"Spanish (Latin America)\", \"is_generated\": false}]",
  "error": null
}
```

| Field | Description |
|---|---|
| `video_id` / `url` | The normalized 11-character video ID and its canonical watch URL. |
| `title` / `channel_id` / `channel_name` | Video title and uploading channel's ID/name. |
| `duration_seconds` / `published_at` / `view_count` | Video length, publish timestamp, and view count at scrape time. |
| `description` / `thumbnail_url` | Full video description and a thumbnail image URL. |
| `language` / `is_generated` | The caption track actually delivered — its language code and whether it's auto-generated (`true`) or human-made (`false`). **Note: `transcript_text` is machine-generated when `is_generated` is `true`** — expect the usual auto-caption quirks (missing punctuation, mis-heard words). |
| `transcript_text` | The full transcript as one string, **one caption per line** (newline-separated) — ready to paste into an LLM prompt. |
| `transcript_segments` | JSON array of `{text, start, duration}` per caption line, for timestamp-accurate use (e.g. jump-to-timestamp links, subtitle files). |
| `available_languages` | JSON array of every caption track YouTube offers for the video (`code`, `name`, `is_generated`), whether or not you requested it. |
| `error` | `null` on success; otherwise a reason the row has no transcript (blocked fetch, no captions, no track for the requested language, etc.) — the row is still delivered, just unbilled (see [Pricing](#pricing)). |
| `scraped_at` | Capture timestamp (UTC ISO 8601). |

### Pricing

This actor uses **pay-per-event** pricing — you pay for delivered transcripts, not for time. Pricing below is **provisional** until Console monetization is finalized (see the Monetization tab for current live pricing).

| Event | USD | Per 1,000 |
|---|---|---|
| Actor start (per run) | $0.001 | — |
| Transcript scraped (`transcript`) | $0.008 | $8 |

| Typical run | Cost |
|---|---|
| 1 video | ~$0.009 |
| 10 videos | ~$0.081 |
| 100 videos | ~$0.801 |

A "transcript" event is charged only for a video that actually yields a transcript. Videos that fail (blocked, no captions available, no track for the requested language) are still delivered as rows — flagged via `error` — but are **never charged**.

### Recommended proxies

**Proxy is bundled — there is nothing to set up.** YouTube blocks transcript requests coming from datacenter IPs, so the actor always runs behind its own residential proxy. Verified on-platform 2026-07-31: the same channel input returned 0 transcripts unproxied and full transcripts through residential.

You only need this section if you want to supply your **own** proxy. Pass a provider or `raw_urls` object via the API and it is used as-is, billed to you rather than bundled.

If you run your own scrapers (inside or outside Apify) and need reliable residential proxies for scale, we use **[DataImpulse](https://dataimpulse.com/?aff=404588\&utm_source=apify)** — pay-as-you-go IPs with per-country targeting and no monthly minimum — recommended for your own scrapers too:

👉 **[Get DataImpulse proxies](https://dataimpulse.com/?aff=404588\&utm_source=apify)** (referral link)

### Why this YouTube Transcript Scraper

- **Transcript + metadata + language list in one call** — no chaining a separate metadata scraper and a separate captions tool.
- **No anti-bot browser tax** — HTTP-only, no browser, no CAPTCHA solving, so runs are fast and cheap as long as requests stay unblocked.
- **No official API key needed** — the actor replicates the same InnerTube requests the YouTube web/app player itself makes; there's no public "get me a transcript" API to authenticate against.
- **Validated output** — every row is Pydantic-validated before it's pushed; failed videos are flagged via `error`, never silently dropped or shipped with garbage fields.
- **Only successful transcripts are billed** — a blocked or caption-less video still shows up in your dataset, but you don't pay for it.
- **Open source** — the underlying `youtube-transcript-scraper` Python package ships a Typer CLI and a FastAPI server; the Apify wrapper is a thin layer.

### FAQ

**Is this the official YouTube API?** No — YouTube/Google do not offer a public API for fetching caption text. This actor calls the same InnerTube endpoints (`/youtubei/v1/player`, `/api/timedtext`) that the youtube.com web player and YouTube apps call when a human plays the video and captions render.

**Do I need a proxy?** No — one is bundled. YouTube blocks transcript requests from datacenter IPs, so the actor always routes through its own residential proxy at no extra configuration and no separate proxy bill. Supply your own only if you specifically want to.

**Why did some videos come back with no transcript?** Common reasons, all surfaced in the `error` field: the video has no captions at all, no track exists for your requested `language`, or the request was rate-limited/blocked (retry, or add a proxy).

**Can I scrape a whole playlist or channel?** Yes — paste the playlist or channel URL into `video_urls` and every video in it is discovered and transcribed. Each playlist/channel expands to at most `max_videos_per_source` videos (default 100), and `max_records` caps the run as a whole. Discovery itself is never charged — you pay only for transcripts actually delivered. Not supported: mixes/radio (`list=RD…`), which are infinite algorithmic feeds with no end, and private lists like Watch Later or Liked.

**Can I get auto-generated captions specifically?** Yes — set `prefer_manual` to `false`. If a video has no human captions at all, auto-generated ones are used regardless of this setting whenever they're the only track available.

**What is `transcript_segments` for?** It's the same transcript as `transcript_text`, but split into `{text, start, duration}` objects with second-accurate timestamps — useful for jump-to-timestamp links, building `.srt`/`.vtt` subtitle files, or aligning quotes to video time.

**Is scraping YouTube transcripts legal?** This actor collects only publicly available caption text and metadata that YouTube itself displays to any viewer — no private data, no login required. You are responsible for complying with YouTube's Terms of Service and applicable law for your use case (e.g. attribution, fair use, copyright of the underlying spoken content).

### Rate this actor ⭐

If the YouTube Transcript Scraper saved you time, please **leave a review on its Apify Store page** — ratings help other people find it and tell us what to build next. Hit a bug or missing field? Open an issue or contact us through the actor's **Issues** tab and we'll fix it fast — recency and reliability are what keep this actor ranking.

### Related actors

Building a content-intelligence or research pipeline? Pair this actor with our other scrapers — same proxy config format, same Pydantic-validated output, all open source.

- **[Google Trends Scraper](https://apify.com/herus13/google-trends-scraper)** — check whether a video's topic is trending before or after you summarize it.
- **[levels.fyi Salary Scraper](https://apify.com/herus13/levels-fyi-salary-scraper)** — another HTTP-only, no-anti-bot actor if you're assembling a lightweight-scrape toolkit.
- **[Google Play App Search & Reviews Scraper](https://apify.com/herus13/google-play-reviews-scraper)** — pair video content with real-world app reviews for market research.

# Actor input Schema

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

<p>Mix any of three kinds of entry. <b>Videos:</b> <code>watch?v=…</code>, <code>youtu.be/…</code>, <code>/shorts/…</code>, <code>/embed/…</code>, or a bare 11-character ID. <b>Playlists:</b> <code>/playlist?list=PL…</code> or a bare playlist ID — every video in the playlist is scraped. <b>Channels:</b> <code>/@handle</code>, <code>/channel/UC…</code>, <code>/c/Name</code>, or <code>/user/Name</code> — the channel's uploads are scraped. Note that a <code>watch?v=…&amp;list=…</code> URL is treated as the single video, not the playlist. Mixes and radio (<code>list=RD…</code>) and private lists (Watch Later, Liked) are not supported. Use Max videos per playlist/channel to cap how far each one expands.</p>
## `language` (type: `string`):

Language code for the transcript, e.g. <code>en</code>, <code>es</code>, <code>ja</code>. Leave empty for the video's default track.

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

When both human and auto-generated captions exist for the chosen language, pick the human ones.

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

How many videos to take from each playlist or channel. Defaults to 100. Ignored for entries that are already a single video.

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

Hard cap on videos processed across the entire run, after playlists and channels expand. Leave empty for no cap.

## `proxy` (type: `object`):

<p>Managed by the actor. Proxy is bundled — the scraper runs on its own residential proxy (DataImpulse, with Apify residential fallback), which YouTube requires: transcript requests from datacenter IPs are blocked. No proxy configuration is needed. Advanced users may override via the API with a <code>raw_urls</code> / provider proxy object.</p>

## Actor input object example

```json
{
  "video_urls": [
    "dQw4w9WgXcQ"
  ],
  "prefer_manual": true
}
```

# Actor output Schema

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

Per-video transcript, metadata, and available caption languages

# 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("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=herus13/youtube-transcript-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/mv4pc9VeaHSBQejw9/builds/pSaTMbGCsAtbReM2w/openapi.json
