# YouTube Channel: All Transcripts (`steadyfetch/youtube-channel-transcripts`) Actor

Paste a channel, get every video's transcript in one run — no per-channel fee, and never charged for a video we can't transcribe. Videos, Shorts and live VODs, de-duplicated. Captions first, speech-to-text when a video has none. JSON, text, SRT or VTT.

- **URL**: https://apify.com/steadyfetch/youtube-channel-transcripts.md
- **Developed by:** [Steadyfetch Team](https://apify.com/steadyfetch) (community)
- **Categories:** Videos, AI, Agents
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 transcripts

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?

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: All Transcripts

**Paste a channel, get every video's transcript in one run.** Videos, Shorts and live VODs,
de-duplicated. Captions first, built-in speech-to-text when a video has none — so caption-less
videos still return real text. No chaining, no per-channel fee, never charged for a video we
can't transcribe.

Issues answered in about 3 hours.
Unofficial: steadyfetch is **not affiliated with, endorsed by, or sponsored by YouTube or Google**. "YouTube" is a trademark of Google LLC, used here only to say what this actor reads.

There is no per-channel fee — you only pay for transcripts that are delivered. Every row
carries `charged` and `statusReason`, so you can reconcile the invoice from the dataset itself.
**Only rows with `charged: true` were billed.**

***

### What a row looks like

Real output, unedited apart from trimming the transcript and segment list:

```json
{
  "status": "ok",
  "charged": true,
  "statusReason": null,
  "inputChannel": "@mkbhd",
  "channelHandle": "@mkbhd",
  "positionInChannel": 0,
  "videosFound": 25,
  "videoId": "ngPkbaZliaU",
  "url": "https://www.youtube.com/watch?v=ngPkbaZliaU",
  "title": "The Truth About the Bezelless Concept Phones",
  "channelName": "Marques Brownlee",
  "channelId": "UCBJycsmduvYEL83R_U4JriQ",
  "durationSeconds": 389,
  "viewCount": 2522420,
  "isLiveContent": false,
  "language": "en",
  "source": "captions",
  "captionKind": "manual",
  "text": "Do you remember back in the day when… screen-to-body ratio was one of those headlining numbers…",
  "segments": [
    { "start": 2.593, "end": 4.781, "text": "Do you remember back in the day when..." },
    { "start": 4.863, "end": 7.295, "text": "screen-to-body ratio was one of those, like," }
  ],
  "srt": null,
  "vtt": null,
  "chargeEvents": { "transcript": 1, "speechMinutes": 0 }
}
```

| field | notes |
|---|---|
| `inputChannel` · `channelHandle` · `positionInChannel` · `videosFound` | which channel this came from, and where in it — join rows back to the channel you pasted |
| `text` · `segments[{start,end,text}]` | the transcript, and the same text with timestamps in seconds |
| `srt` · `vtt` | filled only when you ask for that `format`; otherwise `null` |
| `source` | `captions` or `speech_ai` — how this transcript was produced |
| `captionKind` | `manual` (uploaded by the channel) or `auto` (YouTube's own auto-captions); `null` on the speech route |
| `title` · `channelName` · `channelId` · `durationSeconds` · `viewCount` · `isLiveContent` | as YouTube reports them |
| `charged` · `statusReason` · `chargeEvents` | the reconciliation trio |
| `retryable` | on a row that did not deliver: `true` means YouTube refused us this time and the same input is worth running again, `false` means the answer will not change. It always agrees with the note in `statusReason`, so code can branch on the column instead of parsing the sentence |

The run summary (in the key-value store) carries a per-channel breakdown: **videos found vs
transcribed**, and the reason enumeration stopped (`completed`, `maxVideos`, …).

**[Browse the full live example dataset →](https://api.apify.com/v2/datasets/enopchEDqaahAgJ3r/items?clean=true\&format=json)**
— one real `@mkbhd` run, four transcripts as delivered: a 31-minute video (30,883 characters), a
Pixel impressions video, a phone review and a foldable review, each with its own `charged` and
`chargeEvents` line.

### Agent / API paste-block

```
Actor:      steadyfetch/youtube-channel-transcripts
Required:   channels   (array of channel URLs, @handles or channel IDs — a single video link works too)
Optional:   maxVideos             (integer, default 50 — newest N videos per channel)
            maxItems              (integer, default 500 — cap for the WHOLE run, across channels)
            language              (string, e.g. "en", "es", "pt-BR" — preferred caption track)
            format                (json | text | srt | vtt, default json)
            enableSpeechFallback  (boolean, default true — off = captions only, no speech minutes)
            maxSpeechMinutes      (integer, default 60 — hard cap for the whole run)
Charges:    transcript      once per delivered transcript, captions or speech-to-text
            speech_minute   per started minute, only when speech-to-text actually ran
Build spec: https://apify.com/steadyfetch/youtube-channel-transcripts/api
Token:      https://console.apify.com/settings/integrations
```

```bash
curl -X POST "https://api.apify.com/v2/acts/steadyfetch~youtube-channel-transcripts/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"channels":["https://www.youtube.com/@mkbhd"],"maxVideos":25,"format":"srt"}'
```

Calling from an agent or MCP client: **omit** an option you do not want rather than sending
`null` — the platform rejects an explicit `null` before the run is even created.

***

### What you can paste

A channel URL (`youtube.com/@handle`, `/channel/UC…`, `/c/…`, `/user/…`), a bare `@handle`, a
bare channel ID, or a full channel URL with any trailing tab. Casing in the link does not
matter. A single **video** link works too — it transcribes just that video. Videos, Shorts and
live recordings are all enumerated and de-duplicated into one list.

`maxVideos` caps how many of a channel's newest videos are transcribed (default 50). Raise it —
and your **Maximum cost per run** — to transcribe a whole channel.

`maxItems` is the second cap, and it applies to the **whole run** rather than to each channel
(default 500). Per-channel caps multiply — ten channels at 50 is 500 transcripts — so this is the
one number that bounds what a multi-channel run can bill you. Channel reports, skipped videos and
uncharged misses are free and never count against it; when the run stops on this limit the status
line says so and names how many videos were left.

### What you are charged for

**Pricing: from $1.20/1,000 transcripts.** Two events, and nothing else — **listing the channel
adds no separate fee**:

- **Transcript** — once per video that returns real text, from captions or from speech-to-text.
- **Speech-to-text minute** — per started minute, and **only** when a video had no usable
  captions so speech-to-text had to run. A captioned video never triggers it, and a
  caption-less video too long for the speech route (see `audio_too_long_for_speech` below)
  is reported uncharged instead of being charged for a partial transcript.

**What can fail, and what it costs you: nothing.** The two most common non-deliveries are
`no_speech` (the audio is music or silence, so there is no transcript to sell) and
`blocked_retry` (YouTube challenged or throttled the fetch, or was still processing the video).
Both come back as a row with `charged: false` naming the reason — as do `no_captions`,
`private_or_members_only`, `removed_or_unavailable`, `age_restricted`, `region_blocked` and
`live_no_transcript_yet`. You pay for delivered transcripts and nothing else.

Switch **"Use speech-to-text when a video has no captions"** off and you will never be charged a
speech-to-text minute; caption-less videos come back as uncharged rows instead. `Max videos` and
`Max speech-to-text minutes` are hard stops: the run finishes successfully and each skipped row
names the limit that stopped it.

### When a video (or channel) is not charged

| `status` | what happened | temporary? |
|---|---|---|
| `channel_not_found` | the channel handle / ID / URL did not resolve | no |
| `no_videos_found` | the channel has no public videos, Shorts or live VODs | no |
| `resolve_failed` · `enumerate_failed` | the channel could not be looked up or listed right now | **yes — re-run** |
| `channel_region_blocked` | YouTube does not serve this channel to the location the run fetched from — a limit on where we went out, not a verdict on the channel. Runs can leave from different locations, so another one may be allowed to list it | **yes — re-run** |
| `no_speech` | the audio is music or silence — there is no transcript to sell | no |
| `no_captions` | no captions, and you switched speech-to-text off for this run | no |
| `asr_unavailable` | our speech-to-text service refused this actor's access mid-run — that is on us, not you. Captioned videos still delivered; caption-less ones came back uncharged | **yes — try again later** |
| `audio_too_long_for_speech` | no captions, and the video is too long for the speech-to-text route — YouTube does not release enough of its audio for a complete transcript, and we never sell a partial one as whole. Captioned videos are unaffected at any length | no |
| `private_or_members_only` | private or members-only | no |
| `removed_or_unavailable` | YouTube says the video is gone; its own words are in the row | no |
| `age_restricted` | YouTube requires a signed-in, age-verified account | no |
| `region_blocked` | the uploader has not published it in the country we fetched from | no |
| `live_no_transcript_yet` | a live stream — no transcript exists until it ends | re-run after it ends |
| `blocked_retry` | YouTube challenged, throttled or was still processing | **yes — re-run** |
| `skipped_budget` · `skipped_speech_cap` | a limit stopped it; the row names which | yes |
| `input_error` | the paste was not a channel or video | fix and re-run |

A temporary problem is never reported as a permanent one. This actor may fail when the platform
changes things — failed items are never charged.

### FAQ

**How do I download every transcript from a YouTube channel?**
Paste the channel URL or `@handle` and run it. No YouTube API key, no cookies, no sign-in.

**Does it include Shorts and live streams?**
Yes — the Videos, Shorts and Live tabs are all enumerated and merged, de-duplicated by video ID.

**Can I transcribe just the newest few videos?**
Set `maxVideos`. Videos are taken newest-first.

**What if a video has no captions?**
Speech-to-text runs on the audio and you get real text, marked `source: "speech_ai"`. If the
audio has no speech at all, the row comes back `no_speech` and uncharged. The speech route
works on short videos only: past a few minutes YouTube stops releasing the audio to anything
but its own player, so a long caption-less video comes back `audio_too_long_for_speech` and
uncharged rather than half-transcribed. Shorts are unaffected, and so is every video that has
captions — the large majority of a channel, including nearly every spoken upload — at any length.

**Can I get SRT or VTT subtitles?**
Set `format` to `srt` or `vtt` and each row carries a ready-to-save subtitle string.

**Can I use this through an MCP server?**
Yes. It is a standard Apify actor, so any MCP client that can call Apify actors can call it.

**Why does a run cost more than the transcripts?**
Apify bills platform usage (compute and proxy) for what a run actually consumes, separately from
these events. `Maximum cost per run` is the ceiling that covers both.

***

### Steadyfetch YouTube suite

Same transcript engine, different way in. All-inclusive pay per event, no start fee, charged only on delivery.

| What you paste | Actor |
|---|---|
| Video URLs or IDs | [YouTube Transcripts + Speech AI](https://apify.com/steadyfetch/youtube-transcript-scraper) |
| A channel URL, `@handle` or channel ID | **this actor** |

**The rest of the steadyfetch shelf** — same contract everywhere: all-inclusive pay per event,
no start fee, charged only on delivery.

| Family | Actors |
|---|---|
| Ad creative intelligence | [Facebook](https://apify.com/steadyfetch/facebook-ads-transcript-scraper) · [Google Ads video](https://apify.com/steadyfetch/google-ads-video-transcript-scraper) · [TikTok](https://apify.com/steadyfetch/tiktok-ads-transcript-scraper) · [LinkedIn](https://apify.com/steadyfetch/linkedin-ads-transcript-scraper) · [Google Ads text & OCR](https://apify.com/steadyfetch/google-ads-creative-text-scraper) |
| Trends & keywords | [Google Trends](https://apify.com/steadyfetch/google-trends-scraper) · [Trends Now](https://apify.com/steadyfetch/google-trends-now-scraper) · [Breakout keywords](https://apify.com/steadyfetch/breakout-keywords-scraper) · [Autocomplete keywords](https://apify.com/steadyfetch/google-keyword-suggest-scraper) · [Keyword volume & CPC](https://apify.com/steadyfetch/keyword-search-volume-scraper) · [Social trends](https://apify.com/steadyfetch/social-trends-scraper) |
| **YouTube transcripts** | **[YouTube videos](https://apify.com/steadyfetch/youtube-transcript-scraper) · YouTube channels** |
| Instagram | [Reel transcripts](https://apify.com/steadyfetch/instagram-reel-transcript-scraper) · [Profile posts](https://apify.com/steadyfetch/instagram-profile-posts) |
| Jobs | [Indeed](https://apify.com/steadyfetch/indeed-jobs-scraper) · [Career sites by domain](https://apify.com/steadyfetch/company-jobs-by-domain) · [Glassdoor](https://apify.com/steadyfetch/glassdoor-jobs-scraper) · [Multi-board](https://apify.com/steadyfetch/multi-job-board-scraper) |
| Amazon | [Products](https://apify.com/steadyfetch/amazon-product-scraper) · [Search](https://apify.com/steadyfetch/amazon-search-scraper) · Bestsellers · Sellers |
| Any media file | [Speech to Text](https://apify.com/steadyfetch/media-transcriber) · any link or file |

Unlinked names are publishing shortly on the same account — search **steadyfetch** on Apify Store.

Free n8n templates for the suite: **[github.com/steadyfetch/n8n-templates](https://github.com/steadyfetch/n8n-templates)** — no community nodes needed.

### Feedback & support

Found an issue? Open it on the **Issues tab** — issues are answered in about 3 hours, and always within one business day.

# Actor input Schema

## `channels` (type: `array`):

YouTube channels: https://www.youtube.com/@handle, /channel/UC…, /c/…, /user/…, a bare channel ID, or a single video link (transcribed directly as a convenience — steadyfetch/youtube-transcript-scraper is the dedicated actor for one-off videos). Leave the default in place to get one uncharged sample row that shows the exact output shape. Omit the field rather than sending null.

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

Hard cap on how many videos to transcribe from each channel (newest first). Raise it — and your maximum cost per run — to transcribe a whole channel.

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

Hard cap on how many videos this run transcribes in total, across every channel you pasted. "Max videos per channel" limits each channel on its own; this one limits the bill for the run. Channel reports and skipped-video rows are always free and never count against it.

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

Two-letter language code (en, es, ja) or a regional code (pt-BR). If a video has no captions in that language the default track is used instead, and the row tells you which language you got.

## `format` (type: `string`):

json keeps timestamped segments, text returns the transcript only, srt and vtt add a ready-to-download subtitle string on the row.

## `enableSpeechFallback` (type: `boolean`):

On: caption-less videos are transcribed with speech-to-text, which charges speech-to-text minutes on top of the transcript. Off: caption-less videos come back as uncharged rows and you never pay a speech-to-text minute.

## `maxSpeechMinutes` (type: `integer`):

Hard cap on speech-to-text minutes for the whole run. Videos that would go past it are skipped as uncharged rows naming this limit.

## Actor input object example

```json
{
  "channels": [
    "https://www.youtube.com/@mkbhd"
  ],
  "maxVideos": 50,
  "maxItems": 500,
  "format": "json",
  "enableSpeechFallback": true,
  "maxSpeechMinutes": 60
}
```

# Actor output Schema

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

One row per video across the channel's Videos, Shorts and live tabs: transcript text, timestamped segments, optional SRT/VTT, the channel it came from, duration, language, and a status (ok / no\_speech / removed\_or\_unavailable / channel\_not\_found / no\_videos\_found / blocked\_retry / …). Only rows with charged: true were billed.

## `summary` (type: `string`):

Delivered count, channels processed, videos enumerated, speech-to-text minutes charged, and a per-channel breakdown (videos found vs transcribed).

## `errors` (type: `string`):

Present only when a fetch or transcription failed after all retries: video ID and reason.

# 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 = {
    "channels": [
        "https://www.youtube.com/@mkbhd"
    ]
};

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

# Run the Actor and wait for it to finish
run = client.actor("steadyfetch/youtube-channel-transcripts").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 '{
  "channels": [
    "https://www.youtube.com/@mkbhd"
  ]
}' |
apify call steadyfetch/youtube-channel-transcripts --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,steadyfetch/youtube-channel-transcripts"
        }
    }
}

```

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/JV2LH9ZFIVU6lFqNl/builds/Nl7Ky2J0fOG7BFafo/openapi.json
