# YouTube Transcript Scraper — Captions, Subtitles & Video Data (`tortuga/youtube-transcript-scraper`) Actor

Returns YouTube transcripts (full text + timed segments), language, auto vs manual flag, caption languages, optional translation and video metadata for video, playlist and channel URLs.

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

## Pricing

from $3.50 / 1,000 video 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/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 — Captions, Subtitles & Video Data

Get the transcript of any YouTube video, playlist or channel: the full text, every caption line with its start time and duration, the language, whether the captions are auto-generated or uploaded by the creator, the list of all caption languages, an optional translation, and the video's metadata (title, channel, publish date, duration, views, likes, tags, description, thumbnail).

Built for reliability. Every video is retried on fresh IPs and through two different YouTube app clients until the captions come back. In our test runs, every public video with captions was delivered: 282 of 282 deliveries (240 distinct videos) from a playlist and 13 channels (videos and Shorts tabs), in English, Spanish, German, Hindi, Punjabi, Chinese and more. Videos that have no captions at all are skipped and **not charged**.

### What data does YouTube Transcript Scraper extract?

| Field | Description |
|---|---|
| `videoId`, `url` | Video ID and watch URL |
| `title`, `description`, `tags`, `category` | Video title, full description, creator tags, YouTube category |
| `channelName`, `channelId`, `channelHandle`, `channelUrl` | Channel that published the video |
| `publishDate`, `uploadDate` | ISO timestamps with time zone |
| `durationSeconds`, `viewCount`, `likeCount` | Length, views and likes as YouTube shows them |
| `thumbnailUrl` | Highest-resolution thumbnail |
| `isLiveContent`, `isFamilySafe`, `isUnlisted` | Flags |
| `transcript` | Full transcript as one plain-text string (ready for LLMs, search or summaries) |
| `segments` | Caption lines: `{start, duration, text}` with times in seconds |
| `language`, `languageName`, `isAutoGenerated` | Language of the returned transcript and whether YouTube generated it automatically |
| `availableLanguages` | Every caption track on the video: code, name, auto-generated or manual |
| `isTranslatable` | Whether YouTube offers machine translation for the track |
| `translationLanguage`, `translationStatus`, `translatedTranscript`, `translatedSegments` | Present when **Translate to** is set |
| `transcriptStatus` | `ok`, or `no_captions` when you choose to keep videos without captions |
| `sourceUrl`, `positionInSource`, `scrapedAt` | Which input produced the video, its position in the playlist/channel list, and when it was scraped |

### How to scrape YouTube transcripts

1. Paste video URLs or IDs, playlist URLs or channel URLs into **YouTube URLs or video IDs**, one per line. All usual forms work: `watch?v=`, `youtu.be/`, `/shorts/`, `/live/`, `/embed/`, `/playlist?list=`, `/@handle`, `/channel/UC…`, `/c/…`, `/user/…`.
2. For playlists and channels, set **Max videos per playlist / channel** (newest first for channels). Use **Channel tab** to pick Videos, Shorts or past Live streams.
3. Optionally set **Preferred transcript language** (for example `en`) and **Translate to** (for example `es`).
4. Click **Start**. Results appear in the **Dataset** tab. Download them as JSON, CSV or Excel, or fetch them through the API.

### Input example

```json
{
  "urls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "https://www.youtube.com/playlist?list=PLFgquLnL59alCl_2TQvOiD5Vgm1hCaGSI",
    "https://www.youtube.com/@veritasium"
  ],
  "maxVideosPerSource": 20,
  "language": "en",
  "translateTo": "de",
  "includeSegments": true,
  "maxItems": 100,
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

### Output example

```json
{
  "videoId": "J1WoNuemKOg",
  "url": "https://www.youtube.com/watch?v=J1WoNuemKOg",
  "title": "We sent a camera to space to film the solar eclipse",
  "channelName": "Veritasium",
  "channelId": "UCHnyfMqiRRG1u-2MsSQLbXA",
  "channelHandle": "@veritasium",
  "channelUrl": "https://www.youtube.com/@veritasium",
  "publishDate": "2026-08-17T07:30:16-07:00",
  "durationSeconds": 1365,
  "viewCount": 2735031,
  "likeCount": 67673,
  "category": "Education",
  "tags": ["veritasium", "science", "physics"],
  "thumbnailUrl": "https://i.ytimg.com/vi/J1WoNuemKOg/maxresdefault.jpg",
  "transcriptStatus": "ok",
  "language": "en",
  "languageName": "English",
  "isAutoGenerated": false,
  "transcript": "In just a couple of seconds Spain is about to get its first total solar eclipse in over 100 years. …",
  "segmentCount": 476,
  "segments": [
    { "start": 0.0, "duration": 1.001, "text": "In just a couple of seconds" },
    { "start": 1.001, "duration": 3.628, "text": "Spain is about to get its first total solar eclipse in over 100 years." }
  ],
  "availableLanguages": [
    { "languageCode": "en", "name": "English", "isAutoGenerated": false },
    { "languageCode": "en", "name": "English (auto-generated)", "isAutoGenerated": true }
  ],
  "isTranslatable": true,
  "translationLanguage": "de",
  "translationStatus": "translated",
  "translatedTranscript": "In wenigen Sekunden wird Spanien die erste totale Sonnenfinsternis seit über 100 Jahren erleben. …",
  "sourceUrl": "https://www.youtube.com/@veritasium",
  "positionInSource": 3,
  "scrapedAt": "2026-10-07T20:21:10Z"
}
```

### Which transcript language do I get?

With **Preferred transcript language** empty, you get the video's original spoken language. This also holds for videos with dubbed audio, where YouTube adds an auto-generated track for every dub language. A caption track uploaded by the creator is used when there is one, and YouTube's auto-generated track otherwise. Set a language code such as `en`, `de` or `pt-BR` to prefer that language (creator track first, then auto-generated). If the video has no track in that language, the original-language transcript is returned. Set **Translate to** if you need it in your language anyway. `availableLanguages` always lists every track on the video.

### Can it translate YouTube subtitles?

Yes. Set **Translate to** (for example `en`, `es`, `fr`). If the creator uploaded a track in that language, it is used as is (`translationStatus: native_track`). Otherwise the Actor asks YouTube for its own machine translation (`translated`). YouTube rate-limits machine translation hard, so the Actor retries it on fresh IPs. In our tests 19 of 20 videos were translated. A video whose translation still fails keeps its original transcript and gets `translationStatus: rate_limited`. `not_offered` means YouTube does not allow translation for that track.

### Does it work without login or an API key?

Yes. It reads the same public caption data the YouTube apps use. No Google account, no cookies and no YouTube Data API key are needed. Age-restricted, private and members-only videos need a login, so they are skipped and not charged. The run summary (key-value store, `SUMMARY`) counts them.

### Proxy note: why residential?

YouTube answers most datacenter IPs with "Sign in to confirm you're not a bot". In our tests on Apify datacenter IPs, even 8 retries per video left about 1 in 10 videos without a transcript and many without metadata. On Apify **RESIDENTIAL** proxy (the default), every video in our tests went through. The Actor trims each YouTube answer to a few KB, so residential traffic is about 25–55 KB per video (long videos have longer transcripts). In our runs, total platform usage (residential proxy + compute) came to about $0.30–0.50 per 1,000 videos. With translation it was about $1.20 per 1,000, because of the extra retries. Platform usage is billed to your Apify account in addition to the per-result price.

### Monitor changes on a schedule

Turn on **Only new or changed items** to get a feed of new uploads: each run remembers which videos earlier runs returned and outputs only videos it has not returned before, from the newest **Max videos per playlist / channel** of each channel or playlist. Videos already returned are skipped before any request, so they cost nothing; a video that was skipped (no captions yet, upcoming premiere, members-only, failed) is not remembered and is tried again on the next run. Save the input as a task and add a Schedule in Apify (for example daily); give each watch its own **Monitoring key** if several schedules use the same channels. Every output item has `changeType: "new"`. Video URLs in the input are returned on the first run only.

### Pricing

Pay per result: **$0.0035 per video saved** ($3.50 per 1,000 transcripts), plus the small platform usage above. Videos without captions, unavailable videos and failed videos are not charged. Use **Max results** to cap the cost of a run. There is no subscription, and Apify's free plan is enough to try it.

### Integrations and API

Use it from Zapier, Make, n8n or Google Sheets, or call it from Python/Node with the Apify client (see the **API** tab). AI agents can call it through Apify's MCP server. A single video usually finishes in a few seconds. The `transcript` field is ready to drop into an LLM prompt for summaries, Q\&A, translation or search indexing.

### Is it legal to scrape YouTube transcripts?

The Actor only reads data that YouTube shows publicly to any visitor: captions, titles, descriptions and public counts. It does not log in, does not bypass age or membership gates, and does not download video or audio. Transcripts are the creators' content: check copyright and YouTube's terms for your use case, especially before republishing. You are responsible for how you use the data.

### Support

Found a video that fails, or need another field? Open an issue in the **Issues** tab with the video URL. Issues are usually answered within a day.

# Actor input Schema

## `urls` (type: `array`):

One per line. Video URLs (`watch?v=`, `youtu.be/`, `/shorts/`, `/live/`, `/embed/`) or 11-character video IDs, playlist URLs (`/playlist?list=...`), and channel URLs (`/@handle`, `/channel/UC...`, `/c/name`, `/user/name`, optionally ending in `/videos`, `/shorts` or `/streams`). Playlists and channels are expanded to their latest videos.

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

For each playlist or channel URL, take this many videos (playlist order, or newest first for channels). Video URLs are always processed.

## `channelTab` (type: `string`):

Which list a channel URL is expanded from, unless the URL itself ends in `/videos`, `/shorts` or `/streams`.

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

Language code such as `en`, `de`, `es`, `pt-BR`. A manual (creator-uploaded) track in that language is used first, then an auto-generated one. Leave empty to get the transcript in the video's original spoken language. If the video has no track in this language, the original-language transcript is returned (see **Translate to**).

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

Language code such as `en`, `fr`, `de`, `es`. Adds `translatedTranscript` using a creator-uploaded track in that language when one exists, otherwise YouTube's own auto-translation. YouTube rate-limits auto-translation heavily; the Actor retries on fresh IPs, and `translationStatus` tells you per video whether it worked. Leave empty to skip.

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

Add `segments` (each caption line with `start` and `duration` in seconds) next to the full `transcript` text. Turn off for smaller items when you only need plain text.

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

Off (default): videos that have no captions at all are skipped and not charged. On: they are saved with their metadata and `transcriptStatus: no_captions` (charged as a result).

## `maxItems` (type: `integer`):

Stop after this many videos are saved. Each saved video is one charged result, so this caps the cost.

## `onlyNewOrChanged` (type: `boolean`):

Remember which videos earlier runs of this input returned and output only new videos from the channels and playlists (feed mode). Use with a Schedule; videos already returned are skipped before any request and not charged. Videos skipped for missing captions are tried again on the next run.

## `monitoringKey` (type: `string`):

Name for this watch, so several schedules on the same channels stay separate; defaults to a key derived from the input

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

Keep Apify RESIDENTIAL proxy: YouTube answers datacenter IPs with 'Sign in to confirm you're not a bot'. Requests are trimmed to a few KB each, so residential traffic is about 25-55 KB per video; total platform usage was about $0.30-0.50 per 1,000 videos in our runs.

## Actor input object example

```json
{
  "urls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "https://youtu.be/oyRxhiAC9u8"
  ],
  "maxVideosPerSource": 20,
  "channelTab": "videos",
  "includeSegments": true,
  "includeVideosWithoutTranscript": false,
  "maxItems": 100,
  "onlyNewOrChanged": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

No description

# 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 = {
    "urls": [
        "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
        "https://youtu.be/oyRxhiAC9u8"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("tortuga/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 = {
    "urls": [
        "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
        "https://youtu.be/oyRxhiAC9u8",
    ],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("tortuga/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 '{
  "urls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "https://youtu.be/oyRxhiAC9u8"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call tortuga/youtube-transcript-scraper --silent --output-dataset

```

## MCP server setup

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