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

Extract YouTube transcripts and subtitles from any video, channel, playlist or search. Plain text, timestamps, SRT, VTT and RAG-ready chunks with real timings. No API key, no cookies, no browser.

- **URL**: https://apify.com/teamsasuke/youtube-transcript-scraper.md
- **Developed by:** [Team Sasuke](https://apify.com/teamsasuke) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 transcript delivereds

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?

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 — Subtitles, Captions & Timestamps

Get the transcript of any YouTube video as text, timestamped segments, SRT, VTT
or chunks ready to embed. Point it at a **video, a whole channel, a playlist or
a search term** and it returns every transcript behind it.

No API key. No login cookie. No headless browser. Proxy included and
configured by default.

***

### Why this one works when others return blank rows

In 2025 YouTube began requiring a proof-of-origin token on caption downloads
made through its public web client. Requests without one still get **HTTP 200 —
with an empty body**. That is why so many transcript tools now return rows with
the metadata filled in and the transcript field empty, and why "it worked last
month" is such a common complaint.

This scraper talks to two YouTube clients that are still exempt from that
requirement, so caption URLs download normally with no token and no browser.

The second half of the problem is where the request comes from. YouTube
challenges cloud datacenter addresses with "Sign in to confirm you're not a
bot", and a challenged address stays challenged. So every worker here holds its
own proxy address, and the moment one is challenged it is thrown away and the
video retried on a fresh one.

Measured on the same 25-video channel:

| Setting | Transcripts returned |
| --- | --- |
| No proxy | 0 of 25 |
| Datacenter proxies | 24 of 25 |
| **Residential proxies (default)** | **25 of 25** |

In testing, the only videos that came back without a transcript were private,
deleted, or genuinely had captions turned off — and the run says which, and why.

**Apart from a small fixed fee when a run starts, you only pay for transcripts
you actually receive.** Videos with no captions, private videos and dead links
cost you nothing.

***

### What you can feed it

| Paste this | You get |
| --- | --- |
| `https://www.youtube.com/watch?v=VIDEO_ID` | that video |
| `https://youtu.be/VIDEO_ID` | that video |
| `https://www.youtube.com/shorts/VIDEO_ID` | that Short |
| `https://www.youtube.com/@channel` | every video on the channel |
| `https://www.youtube.com/playlist?list=PLAYLIST_ID` | the whole playlist |
| `machine learning basics` in **Search terms** | top results for the term |
| `dQw4w9WgXcQ` | a bare video ID works too |

Channels and playlists page automatically — ask for 500 videos and it collects
500, as long as the channel has that many. Use **Max videos per channel,
playlist or search** to set the ceiling.

***

### What each row contains

```json
{
  "videoId": "M7lc1UVf-VE",
  "url": "https://www.youtube.com/watch?v=M7lc1UVf-VE",
  "title": "YouTube Developers Live: Embedded Web Player Customization",
  "channelName": "Google for Developers",
  "channelUrl": "https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw",
  "durationSeconds": 1667,
  "viewCount": 234571,
  "description": "...",
  "keywords": ["youtube api", "embed"],
  "thumbnailUrl": "https://i.ytimg.com/vi/M7lc1UVf-VE/maxresdefault.jpg",

  "language": "en",
  "languageName": "English",
  "isAutoGenerated": false,
  "isTranslated": false,
  "availableLanguages": ["en", "es"],

  "transcript": "JEFF POSNICK: Hey, everybody. Welcome to this week's show...",
  "wordCount": 3778,
  "characterCount": 21044,
  "segmentCount": 466,
  "segments": [
    { "start": 10.349, "duration": 1.0, "end": 11.349, "text": "JEFF POSNICK: Hey, everybody." }
  ],

  "sourceType": "channel",
  "sourceInput": "https://www.youtube.com/@GoogleDevelopers",
  "scrapedAt": "2026-09-09T18:22:04+00:00",
  "error": null
}
```

`srt`, `vtt` and `chunks` are added when you switch them on.

***

### Built for AI pipelines

Turn on **Split transcript into chunks** and each transcript comes back already
divided into overlapping passages sized for an embedding model — and every
chunk keeps the real start and end time of the passage it covers:

```json
"chunks": [
  { "index": 0, "start": 10.349, "end": 58.17, "text": "JEFF POSNICK: Hey, everybody..." },
  { "index": 1, "start": 54.02, "end": 96.44, "text": "...so the first thing we do is..." }
]
```

That timestamp is the part most tools drop. Keeping it means your RAG answers
can cite the exact moment in the video, and your users can click straight to it
at `https://youtu.be/VIDEO_ID?t=54`.

Chunks are built from whole caption lines, never by cutting at a fixed
character count, so sentences stay intact.

***

### Common uses

- **Feed a RAG or LLM pipeline** — chunked, timestamped, citable.
- **Summarise a whole channel** — competitor research, podcast archives, conference talks.
- **Repurpose video into written content** — blog posts, newsletters, show notes.
- **Subtitle files** — export SRT or WebVTT for editors and players.
- **Translate** — have YouTube translate the transcript into another language.
- **Keyword and SEO research** — search what is actually said, not just titles.
- **Accessibility and compliance** — archive spoken content as text.

***

### Languages

Set **Preferred caption languages** in order, for example `en`, `es`, `fr`.
Regional variants match automatically, so `en` accepts `en-US` and `en-GB`.

When a video offers both human-written and auto-generated captions, the
human ones are used by default — they are meaningfully more accurate with
names, jargon and numbers. Turn off **Prefer human-written captions** to take
whichever comes first.

Set **Translate transcript to** for a machine translation into any language
YouTube supports. If translation is unavailable for a video, you get the
original transcript rather than an empty row.

***

### Speed

In testing, **58 videos** across a channel, a playlist and a search finished in
**40 seconds**, container start-up included. Speed varies with video length and
with how hard YouTube is rate-limiting at the time. Raising concurrency helps up
to a point, then YouTube begins rate-limiting; the scraper backs off and
retries rather than dropping rows.

***

### Use it as an API

Every run is callable over HTTP. Start a run and collect the dataset:

```bash
curl -X POST "https://api.apify.com/v2/acts/YOUR_USERNAME~youtube-transcript-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "videoUrls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"],
        "chunkSize": 1000,
        "chunkOverlap": 150
      }'
```

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_TOKEN")
run = client.actor("YOUR_USERNAME/youtube-transcript-scraper").call(run_input={
    "videoUrls": ["https://www.youtube.com/@channel"],
    "maxVideosPerSource": 100,
    "languages": ["en"],
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["title"], item["wordCount"])
    print(item["transcript"][:200])
```

Works the same from JavaScript, n8n, Make, Zapier and LangChain.

***

### Limits, stated plainly

- **A video must have captions.** This reads YouTube's caption tracks; it does
  not transcribe audio. Most videos over a few minutes have at least
  auto-generated captions, but some creators disable them.
- **Private, deleted, members-only and age-restricted videos cannot be read.**
  The run log gives the reason, and that video is not charged. Turn on **List
  videos with no transcript** to also save every such video, with its reason,
  to the run's key-value store.
- **Auto-generated captions have no punctuation** on some videos. That is how
  YouTube produces them, not a fault in the output.
- **Live streams** only have transcripts once the recording is processed.
- **A proxy is required on the platform.** It is already configured; you only
  need to touch it if you want to switch residential for datacenter.

***

### Pricing

A small fixed fee when a run starts, then a charge per transcript delivered.
Videos with no captions, private videos and failed lookups are **free**.

Start a run with the defaults and see what comes back — the sample input is
already filled in.

# Actor input Schema

## `videoUrls` (type: `array`):

Video, channel, playlist or Shorts URLs. Paste a channel and it takes every video on it; paste a playlist and it takes the whole list. Bare video IDs and @handles work too.

## `searchQueries` (type: `array`):

Search YouTube and transcribe the top results for each term. Useful when you want the conversation about a topic rather than a specific channel.

## `maxVideosPerSource` (type: `integer`):

Caps how many videos are taken from each channel, playlist or search term. Individual video URLs are never capped.

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

Two-letter language codes in order of preference, for example en, es, fr. Regional variants match too, so en also accepts en-US and en-GB.

## `fallbackToAnyLanguage` (type: `boolean`):

If none of your preferred languages exist, use whatever captions the video does have. Turn this off to skip videos that are not in your languages.

## `preferManualCaptions` (type: `boolean`):

When a video has both human-written and auto-generated captions, take the human ones. They are noticeably more accurate, especially with names and technical terms.

## `translateTo` (type: `string`):

Two-letter code to have YouTube translate the transcript, for example es or de. Leave empty for the original language. If translation is unavailable the original is returned rather than nothing.

## `includeVideoMetadata` (type: `boolean`):

Add title, channel, description, duration, view count, keywords and thumbnail to each row.

## `includeTimestampedSegments` (type: `boolean`):

Add the segments array: every caption line with its start time, end time and duration in seconds. Turn off for a smaller dataset when you only need the text.

## `includeSrt` (type: `boolean`):

Add a ready-to-save .srt subtitle file as a string field.

## `includeVtt` (type: `boolean`):

Add a ready-to-save .vtt subtitle file as a string field, for HTML5 video players.

## `chunkSize` (type: `integer`):

For RAG and embeddings. Splits the transcript into passages of roughly this many characters, each keeping its real start and end time so answers can cite the moment in the video. Set 0 to skip chunking. 1000 is a sensible default for most embedding models.

## `chunkOverlap` (type: `integer`):

How much text each chunk repeats from the one before it, so a sentence split across the boundary still makes sense in both. Ignored when chunking is off.

## `includeVideosWithoutTranscript` (type: `boolean`):

Save a list of every video that had no captions or could not be read, with the reason for each, to the run's key-value store under VIDEOS\_WITHOUT\_TRANSCRIPT. They are never written to the dataset, so they are never billed. Useful for auditing a whole channel.

## `concurrency` (type: `integer`):

How many videos to fetch at once. 5 is a good balance. Raising it speeds up large runs but YouTube starts rate-limiting, which slows things down again.

## `blockRetries` (type: `integer`):

How many different proxy addresses to try before giving up on a video. Each retry is one extra request; you are never charged for a video that stays blocked.

## `proxyConfiguration` (type: `object`):

YouTube refuses its player API from cloud datacenters, so a proxy is required when running on Apify. Residential is the default because it is the only setting that returned every transcript in testing. Datacenter proxies are cheaper and got about 96%, if you prefer that trade.

## Actor input object example

```json
{
  "videoUrls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
  ],
  "searchQueries": [],
  "maxVideosPerSource": 50,
  "languages": [
    "en"
  ],
  "fallbackToAnyLanguage": true,
  "preferManualCaptions": true,
  "translateTo": "",
  "includeVideoMetadata": true,
  "includeTimestampedSegments": true,
  "includeSrt": false,
  "includeVtt": false,
  "chunkSize": 0,
  "chunkOverlap": 0,
  "includeVideosWithoutTranscript": false,
  "concurrency": 5,
  "blockRetries": 4,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

Every transcript in full, including timestamped segments and any formats you enabled.

## `transcriptsCsv` (type: `string`):

One row per video for a spreadsheet. Leaves out the nested segment and chunk arrays, which a CSV would expand into thousands of columns.

## `datasetView` (type: `string`):

Open the results table in Apify Console.

# 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 = {
    "videoUrls": [
        "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("teamsasuke/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 = { "videoUrls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"] }

# Run the Actor and wait for it to finish
run = client.actor("teamsasuke/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 '{
  "videoUrls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
  ]
}' |
apify call teamsasuke/youtube-transcript-scraper --silent --output-dataset

```

## MCP server setup

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