# YouTube Channel Transcript Scraper : $5 per 1,000 Videos (`workware/youtube-channel-transcript-scraper`) Actor

Give it a YouTube channel handle, playlist, or list of video URLs and get every video's transcript as clean JSON: segments, plain text, or SRT, with language selection and channel metadata, at $5 per 1,000 videos. Bundles the per-video transcript job into one channel, every transcript, in one run.

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

## Pricing

from $5.00 / 1,000 transcript returneds

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/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 Channel Transcript Scraper: All Videos in Bulk, $5/1K (Channel · Playlist · JSON/SRT)

**Give it a YouTube channel handle, a playlist, or a list of video URLs: get every video's transcript
as clean JSON in one run, at $5 per 1,000 videos.** Paste `@mkbhd` and walk away with the whole
back-catalogue as text: timed segments, plain text, or SRT, with language selection and channel metadata.
Built for RAG and AI pipelines that need a creator's entire library, and for researchers who want a whole
channel's words, not one video at a time.

- **What you put in:** one or more sources. The fastest path is a channel handle (`@mkbhd`), but you can
  also pass a channel URL or ID, a playlist, or an explicit list of video URLs.
- **What you get back:** one record per video with a validated transcript, plus one status record per
  source telling you exactly what happened: `found`, `no_channel`, or `error`.
- **Bulk is the whole point:** the per-video transcript scrapers make you feed URLs one at a time. Here a
  single channel handle becomes every video's transcript in one run.
- **Boosted by the Apify platform:** full REST API access, scheduling, monitoring, webhooks and
  integrations, residential proxy rotation, and dataset export to JSON, CSV, or Excel.

### What is the YouTube Channel Transcript Scraper and what can it do?

The YouTube Channel Transcript Scraper is a **bulk YouTube transcript API**: name a channel, playlist, or
video list and it returns every video's captions as normalized JSON: segments, plain text, or SRT. It is
built for the job the per-video scrapers make tedious: turning a creator's *whole* library into text for a
vector store, a search index, or an analysis pipeline, in one call instead of hundreds.

It reads YouTube's own innertube endpoints over a rotating residential proxy and extracts the existing
captions: it does **not** download audio or run speech-to-text, so a run stays cheap and fast. You choose
which caption languages you want, in priority order, and whether to accept YouTube's auto-generated captions
when a manual one is not available. Every transcript echoes the language actually returned and whether it
was auto-generated, so you always know what you got.

**Per-video vs. bulk: why one handle beats a list of URLs:**

| | This Actor (bulk) | Per-video transcript scrapers |
|---|---|---|
| What you paste | One channel handle, playlist, or video list | Every video URL, one at a time |
| A whole channel | One run enumerates and transcribes it | You assemble and paste the URL list yourself |
| Price | **$5 per 1,000 videos** | $5–$10 per 1,000, and you still gather the URLs |
| Output | segments · plaintext · SRT | Usually one fixed shape |
| Coverage record | A row per video with no captions, so gaps are explicit | Silent gaps |

What it deliberately does **not** do: it does not download audio or transcribe speech with AI (that is a
different, 10× pricier class of tool), it does not translate or summarize the transcript (your pipeline's
job: the record stays raw), it does not scrape comments or analytics, and it does not access private or
members-only videos.

### What data does it extract? (output fields)

Every transcript record carries the `videoId`, watch `url`, `channelId`, the `language` actually returned,
`isAutoGenerated`, the `availableLanguages` the video offered, the chosen `format`, the `transcript` itself
(timed `[{start,dur,text}]` for `segments`, or a single string for `plaintext`/`srt`), and a
`transcriptCharCount`. With metadata on (the default) it also carries the title, channel name and handle,
publish date, duration, and view count; turn metadata off for slim, transcript-only records.

Two more row types share the dataset:

- **`video-no-transcript`**: a coverage record for a video definitively proven to have no usable transcript
  (captions disabled, or none in your requested languages with the auto-generated fallback off). You see
  which videos have no text, and why: never a silent gap.
- **`channel-status`**: one row per input source: `{ source, ref, channelId?, videoCount, transcriptsFound,
  noTranscript, errors, status }`, where status is `found`, `no_channel`, or `error`. This is what makes an
  empty result unambiguous: you always know whether a channel has no videos, does not exist, or could not
  be reached.

### How to use the YouTube Channel Transcript Scraper (tutorial)

1. Click **Start** with the example input already filled in: one channel handle, a small cap.
2. Watch the run finish. A small channel typically completes in well under a minute.
3. Open the **Dataset** tab. You will see one transcript record per video, plus one `channel-status` row.
4. Export as JSON, CSV, or Excel, or call the run from your own code through the Apify API.

To run it on your own sources, replace the `sources` array. Each entry needs exactly one of:

- **`channel`**: a handle (`@mkbhd`), a channel URL (`/@…`, `/channel/UC…`, `/c/…`, `/user/…`), or a bare
  `UC…` id. The Actor resolves it and enumerates the uploads, newest-first.
- **`playlist`**: a playlist URL or `PL…`/`UU…` id, enumerated directly.
- **`videos`**: an explicit list of video URLs or IDs, which bypasses enumeration.

Use `maxVideos` to cap how many videos per source (0 = the whole source, up to roughly the newest ~10,000
videos; a channel takes the newest first, a playlist takes the first videos in its own order: for larger
channels narrow with a date range or a playlist), `languages` to set the caption
language priority, `publishedAfter`/`publishedBefore` to bound the window, and `titleFilter` for a plain
case-insensitive substring match on the title. Sources in a batch are processed one after another at a
polite request rate; if you poll on a schedule, prefer several small runs over one very large one.

### How much does it cost to scrape a whole channel's transcripts?

Pay-per-event, with three events, all anchored on **$5 per 1,000 transcripts**:

- **$0.002 per channel processed**: charged once per source that reached a definitive answer, whether that
  answer is "here are the videos" or "there is no such channel". Enumeration's proxy cost is amortized into
  the per-transcript price, not double-charged here.
- **$0.005 per transcript returned**: charged per video with a validated, non-empty transcript ($5 per
  1,000). This matches the cheapest high-volume per-video scraper and undercuts the one channel-scale
  competitor ($15/1K) threefold.
- **$0.001 per no-transcript video**: a fifth of the transcript price, for a video proven to have no
  captions. You never pay the full price for an empty result.

**Failed lookups are free.** A source whose status is `error` (an anti-bot block, a timeout) is never
charged, and neither are the transcripts of a source whose run failed part-way through; they are discarded
rather than delivered. We charge for answers, not for our own failures.

A worked example: a 300-video channel where 280 have captions costs `1 × $0.002 + 280 × $0.005 + 20 × $0.001 = $1.42`. Platform usage costs are absorbed at launch, not passed through.

### Input and output examples

Input: a whole channel as plain text, capped at 25 videos:

```json
{ "sources": [{ "channel": "@mkbhd" }], "maxVideos": 25, "outputFormat": "plaintext" }
```

Input: a playlist and an explicit video list, German captions preferred:

```json
{
  "sources": [
    { "playlist": "https://www.youtube.com/playlist?list=PLxxxxxxxxxxxxxxxx" },
    { "videos": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"] }
  ],
  "languages": ["de", "en"],
  "outputFormat": "srt"
}
```

Output: one transcript record (trimmed) and its source's status row:

```json
{
  "videoId": "dQw4w9WgXcQ",
  "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "title": "…",
  "channelHandle": "@mkbhd",
  "language": "en",
  "isAutoGenerated": false,
  "availableLanguages": ["en", "es", "de"],
  "format": "segments",
  "transcript": [{ "start": 0.0, "dur": 1.6, "text": "…" }],
  "transcriptCharCount": 4200,
  "source": "youtube"
}
```

```json
{ "source": "channel", "ref": "@mkbhd", "channelId": "UC…", "videoCount": 25, "transcriptsFound": 24, "noTranscript": 1, "errors": 0, "status": "found" }
```

| Event | What it is | Charged for | Price |
|---|---|---|---|
| `channel-processed` | Channel processed (definitive answer) | each run | $2.00 / 1,000 |
| `transcript-result` | Transcript returned | each record delivered | $5.00 / 1,000 |
| `video-no-transcript` | Video with no transcript (coverage) | each record delivered | $1.00 / 1,000 |

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `sources` | `array` | Yes | - | The channels, playlists, or explicit video lists to transcribe: one run handles the whole batch, one source at a time. Each entry needs exactly one of `channel`, `playlist`, or `videos`. |
| `maxVideos` | `integer` | No | `0` | Caps the videos processed per source. 0 means no cap: the whole source. For a channel it takes the newest videos first; for a playlist it takes the first videos in the playlist’s own order (a playlist has no single "newest" order). Enumeration reaches roughly the newest ~10,000 videos per source; for larger channels, narrow with a date range or a playlist. The primary cost and scope control. |
| `publishedAfter` | `string` | No | - | Keep only videos published on or after this ISO date (YYYY-MM-DD). Applied during enumeration. |
| `publishedBefore` | `string` | No | - | Keep only videos published on or before this ISO date (YYYY-MM-DD). Applied during enumeration. |
| `languages` | `array` | No | `["en"]` | Priority list of caption language codes (e.g. `en`, `de`, `pt-BR`). The first language available on a video wins; a manual (creator-uploaded) caption is preferred to an auto-generated one. |
| `allowAutoGenerated` | `boolean` | No | `true` | When no manual caption exists in the requested languages, fall back to YouTube auto-generated captions (flagged `isAutoGenerated: true`). When off, such videos are reported as having no transcript. |
| `outputFormat` | `segments` | `plaintext` | `srt` | No | `segments` | How each transcript is shaped: `segments` (timed `[{start,dur,text}]`), `plaintext` (joined prose), or `srt` (a single SRT document). |
| `titleFilter` | `string` | No | - | Keep only videos whose title contains this text, case-insensitively. Plain substring matching: not a regular expression. |
| `includeMetadata` | `boolean` | No | `true` | Include title, channel name/handle, publish date, duration, and view count in each record. When off, emit the transcript and ids only (smaller records). |

### Other Actors you might need

This Actor is a media-to-text probe rather than a hiring tool, so the siblings below solve a different
problem. They are listed because they come from the same account and hold to the same two rules: read the
source live rather than serving a cached index, and never charge you for a run that could not answer.

| Actor | Use it when |
|---|---|
| [Job Postings API](https://apify.com/workware/job-postings-api) | You want every live job at a company, whichever applicant tracking system it uses, in one normalized schema. |
| [Greenhouse Jobs Scraper](https://apify.com/workware/greenhouse-jobs-scraper) | You already know the company runs on Greenhouse and want its board as JSON. |
| [Ashby Jobs Scraper](https://apify.com/workware/ashby-jobs-scraper) | You are tracking startups on Ashby and want employer-stated salary ranges included. |

### FAQ, legality, and support

#### How do I download all transcripts from a YouTube channel?

Paste the channel handle (e.g. `@mkbhd`) as a single `channel` source and click **Start**. The Actor
resolves the channel, enumerates its uploads newest-first, fetches each video's captions, and returns one
transcript record per video. Use `maxVideos` to cap the count, or leave it at 0 for the whole channel.

#### Does it work if captions are auto-generated?

Yes. Set `allowAutoGenerated` (on by default): when no manual, creator-uploaded caption exists in your
requested languages, the Actor falls back to YouTube's auto-generated captions and flags them
`isAutoGenerated: true`. Turn it off to accept only human captions: videos with none then return a
`video-no-transcript` coverage record instead.

#### What languages are supported?

Any language YouTube offers captions in. Pass `languages` as a priority list of codes: `["de", "en"]` means
"German if available, otherwise English". A manual caption is always preferred to an auto-generated one, and
base-language matching means asking for `en` still matches `en-US`.

#### How is this different from a per-video transcript scraper?

A per-video scraper takes a list of video URLs you have already gathered. This Actor takes a channel or
playlist and gathers the videos for you, then transcribes all of them in one run: the same per-video work,
without the URL-collecting step, at the same $5/1K price.

#### Is scraping YouTube transcripts legal?

The data is public: public channel metadata and publicly available captions. This Actor touches no private
user data and no members-only content. Transcripts may carry copyright (creator-uploaded captions
especially), so **respect copyright, use the data responsibly, and you are responsible for your downstream
use.** YouTube actively resists automated access, so expect the occasional blocked run: those are free.

#### What happens when YouTube changes its internal API?

Every response is shape-checked before it is normalized. A change that breaks the shape fails the affected
source loudly instead of emitting silently wrong records, and a scheduled smoke test runs against a
known-stable channel so we usually find out before you do. This is a deliberately honest, lower-stability
source than a documented API: priced and monitored accordingly.

# Actor input Schema

## `sources` (type: `array`):

The channels, playlists, or explicit video lists to transcribe: one run handles the whole batch, one source at a time. Each entry needs exactly one of `channel`, `playlist`, or `videos`.

## `maxVideos` (type: `integer`):

Caps the videos processed per source. 0 means no cap: the whole source. For a channel it takes the newest videos first; for a playlist it takes the first videos in the playlist’s own order (a playlist has no single "newest" order). Enumeration reaches roughly the newest ~10,000 videos per source; for larger channels, narrow with a date range or a playlist. The primary cost and scope control.

## `publishedAfter` (type: `string`):

Keep only videos published on or after this ISO date (YYYY-MM-DD). Applied during enumeration.

## `publishedBefore` (type: `string`):

Keep only videos published on or before this ISO date (YYYY-MM-DD). Applied during enumeration.

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

Priority list of caption language codes (e.g. `en`, `de`, `pt-BR`). The first language available on a video wins; a manual (creator-uploaded) caption is preferred to an auto-generated one.

## `allowAutoGenerated` (type: `boolean`):

When no manual caption exists in the requested languages, fall back to YouTube auto-generated captions (flagged `isAutoGenerated: true`). When off, such videos are reported as having no transcript.

## `outputFormat` (type: `string`):

How each transcript is shaped: `segments` (timed `[{start,dur,text}]`), `plaintext` (joined prose), or `srt` (a single SRT document).

## `titleFilter` (type: `string`):

Keep only videos whose title contains this text, case-insensitively. Plain substring matching: not a regular expression.

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

Include title, channel name/handle, publish date, duration, and view count in each record. When off, emit the transcript and ids only (smaller records).

## Actor input object example

```json
{
  "sources": [
    {
      "channel": "@mkbhd"
    }
  ],
  "maxVideos": 0,
  "languages": [
    "en"
  ],
  "allowAutoGenerated": true,
  "outputFormat": "segments",
  "includeMetadata": true
}
```

# Actor output Schema

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

Every record this run produced, as JSON. Also available as CSV, Excel and XML.

# 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 = {
    "sources": [
        {
            "channel": "@mkbhd"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("workware/youtube-channel-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 = { "sources": [{ "channel": "@mkbhd" }] }

# Run the Actor and wait for it to finish
run = client.actor("workware/youtube-channel-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 '{
  "sources": [
    {
      "channel": "@mkbhd"
    }
  ]
}' |
apify call workware/youtube-channel-transcript-scraper --silent --output-dataset

```

## MCP server setup

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