# YouTube Video Transcript & Channel Scraper (`thirdwatch/youtube-transcripts-scraper`) Actor

Extract full YouTube transcripts with timestamps and rich video metadata. Process individual videos or entire channels, filter by publish date, and select any manual or auto-generated caption language. No API key.

- **URL**: https://apify.com/thirdwatch/youtube-transcripts-scraper.md
- **Developed by:** [Thirdwatch](https://apify.com/thirdwatch) (community)
- **Categories:** AI, Videos, Social media
- **Features:** Limited permissions
- **Stats:** 25 total users, 9 monthly users, 99.9% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 results

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 Video Transcript & Channel Captions Scraper

> **Thirdwatch portfolio:** 8,800+ users across 140+ public Actors, 2M+ records delivered, >95% run success. [Explore all Thirdwatch Actors](https://apify.com/thirdwatch).

> Pull transcripts from individual videos or complete YouTube channels — full text, timestamps, rich metadata, every language track, no API key.

Pull **transcripts and closed captions** from individual YouTube videos or whole channels, at scale, with no API key. Filter channel videos by exact publish date. Every successful record combines the transcript with title, description, views, duration, engagement, channel identity, thumbnails, tags, and publication metadata.

Built for **RAG pipelines over video libraries**, **sentiment analysis**, **video summarization**, **accessibility workflows**, and **agentic tools** that read video content.

### What you get

A clean, structured transcript and metadata record per video. Choose a preferred language or let the actor fall back to whatever is available. Returns both the full joined `transcript_text` (ideal for vector stores) and a `segments` array with per-line timestamps (ideal for subtitle overlays and chapter generation).

Use `videoUrls` for an ad-hoc batch or `channelUrls` to build a dated transcript corpus from one or more channels. Both modes use the same low-cost extraction path and automatically retry blocked requests through an alternate connection only when necessary.

### Output fields

| Field | Description |
|-------|-------------|
| `video_id` | 11-character YouTube video ID |
| `video_url` | Canonical watch URL |
| `title`, `description` | Video title and full description |
| `published_at`, `upload_date` | Exact publication dates |
| `view_count`, `like_count`, `comment_count` | Public engagement metrics when YouTube exposes them |
| `duration_seconds` | Full video duration |
| `channel_id`, `channel_name`, `channel_url` | Channel identity |
| `channel_username`, `channel_thumbnail`, `subscriber_count` | Public channel profile fields when available |
| `thumbnail`, `category`, `tags` | Video discovery metadata |
| `language_code` | Actual caption language returned (ISO 639-1) |
| `language_name` | Human-readable language name |
| `is_auto_generated` | `true` if auto-captions, `false` if human-written |
| `auto_translated` | `true` if auto-translated into the requested language |
| `available_languages` | Array of `{code, name, is_auto_generated}` for every track on the video |
| `transcript_text` | Full transcript joined into one string |
| `segments` | Array of `{text, start, duration}` per caption line |
| `segment_count` | Number of caption lines |
| `total_duration_seconds` | Total covered duration |
| `data_source` | Origin tag |
| `used_residential_proxy` | `true` only when the initial request was blocked and the automatic retry succeeded |

Failures are not written to the paid result dataset. They are available in the run's `ERRORS` key-value-store record with codes such as `no_captions_available`, `private_video`, `region_locked`, `age_restricted_or_login_required`, `video_unavailable`, `no_player_response`, `transcript_fetch_failed`, and `empty_transcript`.

### Example output

```json
{
    "video_id": "dQw4w9WgXcQ",
    "video_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "title": "Never Gonna Give You Up",
    "channel_name": "Rick Astley",
    "published_at": "2009-10-25",
    "view_count": 1700000000,
    "duration_seconds": 213,
    "thumbnail": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
    "language_code": "en",
    "language_name": "English",
    "is_auto_generated": false,
    "auto_translated": false,
    "available_languages": [
        {"code": "en", "name": "English", "is_auto_generated": false},
        {"code": "es", "name": "Spanish", "is_auto_generated": false},
        {"code": "en", "name": "English (auto-generated)", "is_auto_generated": true}
    ],
    "transcript_text": "We're no strangers to love You know the rules and so do I ...",
    "segment_count": 58,
    "total_duration_seconds": 212.48,
    "segments": [
        {"text": "We're no strangers to love", "start": 18.8, "duration": 7.0},
        {"text": "You know the rules and so do I", "start": 25.8, "duration": 3.5},
        {"text": "A full commitment's what I'm thinking of", "start": 29.3, "duration": 3.7}
    ],
    "data_source": "youtube_timedtext"
}
```

#### Example unbilled error diagnostic (no captions published)

```json
{
    "failed_count": 1,
    "failed_videos": [
        {
            "video_id": "abc123XYZ_0",
            "video_url": "https://www.youtube.com/watch?v=abc123XYZ_0",
            "error": "no_captions_available",
            "available_languages": [],
            "data_source": "youtube_innertube"
        }
    ]
}
```

### Input parameters

| Parameter | Required | Description |
|-----------|----------|-------------|
| `videoUrls` | One of | YouTube URLs. Supports `watch?v=`, `youtu.be/`, `shorts/`, `embed/`. |
| `videoIds` | One of | Raw 11-character video IDs. Either `videoUrls` or `videoIds` must be provided. |
| `channelUrls` | One of | YouTube channel URLs or handles. Supports `@handle`, `/channel/`, `/c/`, and `/user/`. |
| `maxVideosPerChannel` | No | Maximum matching videos per channel. Default 10, max 200. |
| `startDate` | No | Only channel videos published on or after this `YYYY-MM-DD` date. |
| `endDate` | No | Only channel videos published on or before this `YYYY-MM-DD` date. |
| `languageCode` | No | Preferred caption language. Default `en`. |
| `preferManual` | No | Prefer human-written captions over auto-generated. Default `true`. |
| `includeTimestamps` | No | Include the `segments` array. Default `true`. Turn off for smaller RAG payloads. |
| `includeAutoTranslate` | No | Fall back to YouTube auto-translate if the requested language isn't available. Default `false`. |
| `includeMetadata` | No | Include rich video, engagement, and channel metadata. Default `true`. |
| `useResidentialProxy` | No | Allow an automatic retry over an alternate connection if the initial request is blocked. Default `true`. |
| `maxResults` | No | Global cap on transcripts returned. Default 10, max 10000. |

### Use cases

- **AI engineers (RAG)**: index thousands of talks, lectures, tutorials for semantic search. `transcript_text` drops straight into your vector store.
- **Content marketers**: generate written blog posts, newsletters, and social clips from podcast and YouTube content at scale.
- **Product & research teams**: run sentiment analysis across competitor channels and track topic drift over time.
- **Accessibility & compliance**: build closed-caption corpora in bulk for ADA / WCAG compliance.
- **Agent builders**: feed a YouTube URL in and get back structured text any automated workflow can read.
- **Language learners & translators**: grab multilingual caption tracks side-by-side for study material.
- **Video summarization tools**: feed full transcripts into an LLM to generate chapter markers, key takeaways, and tl;drs.

### Limitations

- **Not all videos have transcripts** — some uploaders disable captions entirely. Those videos are listed in the unbilled `ERRORS` record with `no_captions_available`.
- **Auto-generated transcripts are lower quality** — especially for music, accents, and technical content. Set `preferManual: true` (default) to pick human-written tracks whenever available.
- **Age-restricted and private videos are blocked** — they appear in the unbilled `ERRORS` record rather than the paid result dataset. Region-locked videos behave the same way.
- YouTube occasionally throttles heavy uninterrupted runs; the actor backs off automatically on rate limits.

### Compared to alternatives

- **starvibe/youtube-video-transcript** — both actors support video and channel transcripts, date filters, languages, timestamps, and metadata. This Actor adds native batch URL/ID input, manual-caption preference, optional auto-translation, and explicit unbilled failure diagnostics.
- **Apify's pintostudio/youtube-transcript-scraper** — similar scope at $0.01 per transcript; this Actor keeps failed videos unbilled so you never pay for a transcript that wasn't delivered.
- **youtube-transcript-api (Python library)** — free to run yourself, but you handle the proxy, consent cookie, and retry logic. This actor is a hosted drop-in with built-in reliability.
- **Official YouTube Data API** — captions endpoint requires OAuth and channel ownership; not usable for third-party videos.

### Pairs well with

- [YouTube Scraper](https://apify.com/thirdwatch/youtube-scraper?fpr=9m2cd6) — pull video metadata (title, description, views, likes, channel) first, then feed the IDs here for transcripts.
- [Reddit Scraper](https://apify.com/thirdwatch/reddit-scraper?fpr=9m2cd6) — cross-reference discussion threads with the video's transcript.

### FAQ

**Does this work on YouTube Shorts?**
Yes — `youtube.com/shorts/{id}` URLs work identically to regular videos.

**Do I need an API key?**
No. The actor uses YouTube's public caption endpoints — no OAuth, no Google Cloud project.

**Can I get transcripts for private videos?**
No. Private videos return a `private_video` error. Only publicly published videos are supported.

**Which languages are supported?**
Every language a video has published captions for. Use `languageCode` to pick your preferred track, or enable `includeAutoTranslate` to cross-translate.

**What happens if captions are disabled?**
The video is omitted from paid results and recorded under `ERRORS` with `error: "no_captions_available"`, so your pipeline can filter, retry, or skip it without paying for a transcript that was not delivered.

**Can I feed this straight into a vector DB?**
Yes — the `transcript_text` field is a single joined string designed for RAG ingestion. Turn off `includeTimestamps` to drop the `segments` array and shrink payloads further.

***

Built by [Thirdwatch](https://thirdwatch.dev). Questions? Open an issue or reach out on the Apify Store listing.

Last verified: 2026-08

# Actor input Schema

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

List of YouTube video URLs. Supports youtube.com/watch?v=..., youtu.be/..., youtube.com/shorts/..., and youtube.com/embed/... formats.

## `videoIds` (type: `array`):

Alternative: pass raw 11-character YouTube video IDs directly. Either videoUrls or videoIds must be provided.

## `channelUrls` (type: `array`):

Fetch transcripts for videos from one or more YouTube channels. Supports @handle, /channel/, /c/, and /user/ URLs.

## `maxVideosPerChannel` (type: `integer`):

Maximum matching videos to return from each channel (1-200). Date filters are applied to each video's exact publish date.

## `startDate` (type: `string`):

Optional channel-video start date in YYYY-MM-DD format.

## `endDate` (type: `string`):

Optional channel-video end date in YYYY-MM-DD format.

## `languageCode` (type: `string`):

Preferred caption language (ISO 639-1, e.g. 'en', 'es', 'fr', 'de', 'ja'). Falls back to the first available track if this language is not published for the video.

## `preferManual` (type: `boolean`):

Prefer human-written captions over auto-generated captions when both exist. Manual captions are more accurate.

## `includeTimestamps` (type: `boolean`):

Include the segments array with per-line text, start time, and duration. Turn off to return only the joined transcript text (smaller output, ideal for RAG ingestion).

## `includeAutoTranslate` (type: `boolean`):

If the preferred language is not directly available, try YouTube's auto-translate from another available track. May reduce accuracy.

## `includeMetadata` (type: `boolean`):

Include title, description, publish date, views, duration, channel details, thumbnail, category, and tags with every transcript.

## `useResidentialProxy` (type: `boolean`):

Automatically retries with an alternate connection path if the initial request is blocked. Keep this on — turning it off will cause most videos to fail with an age-restricted or login-required error.

## `maxResults` (type: `integer`):

Maximum number of transcripts to return. Start small; raise once you've verified the output.

## Actor input object example

```json
{
  "videoUrls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
  ],
  "videoIds": [],
  "channelUrls": [],
  "maxVideosPerChannel": 10,
  "languageCode": "en",
  "preferManual": true,
  "includeTimestamps": true,
  "includeAutoTranslate": false,
  "includeMetadata": true,
  "useResidentialProxy": true,
  "maxResults": 5
}
```

# Actor output Schema

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

No description

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

// Run the Actor and wait for it to finish
const run = await client.actor("thirdwatch/youtube-transcripts-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"],
    "maxResults": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("thirdwatch/youtube-transcripts-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"
  ],
  "maxResults": 5
}' |
apify call thirdwatch/youtube-transcripts-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,thirdwatch/youtube-transcripts-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/TtrXTde3PH2wcLWMU/builds/HouCLsIdgdygqRiB2/openapi.json
