# YouTube Transcript Scraper: Captions, Timestamps, Channels (`cozybit/youtube-transcript-scraper`) Actor

YouTube transcripts without an account: plain text and timestamped segments for videos, Shorts, playlists and channels. Pick caption languages or fall back to the original. Every video gets a record with a clear reason when it has no captions. Trial: 0.01 USD per 1,000 transcripts until 30 Nov 2026.

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

## Pricing

$0.01 / 1,000 video transcripts

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?

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 as text and timestamped segments

Get the transcript (captions) of YouTube videos as clean text and as timestamped segments, without a YouTube account and without downloading any video:

- **Any public video**: paste video links, Shorts links or video IDs.
- **Whole playlists and channels**: paste a playlist or channel link and get the transcripts of its newest videos.
- **Your language**: choose the caption languages you prefer, or fall back to the video's own language.
- **Subtitle files**: optionally get SRT or WebVTT subtitles for video editors and players, included in the transcript price.
- **Honest results**: every video gets a record. When a video has no captions or cannot be read, the record says why, and you do not pay for it.

Prices include all platform costs: you pay per transcript delivered and nothing for proxies or compute.

### Input

| Field | What it does | Default |
|---|---|---|
| YouTube URLs or video IDs | Video links (`watch?v=`, `youtu.be`, Shorts, live), 11-character video IDs, playlist links (`playlist?list=`) and channel links (`@handle`, `/channel/UC...`) | three example videos |
| Caption languages | Language codes in order of preference, for example `en` or `pt-BR`. Human-made captions win over automatic ones in the same language | `en` |
| Fall back to any language | When none of your languages exists, return the video's own captions instead of an error | on |
| Include timestamped segments | Return the `segments` list next to the plain `text` | on |
| Subtitle format | Choose SRT or WebVTT to add a `subtitles` field with the subtitle file's text, even when segments are off | none |
| Maximum videos per playlist or channel | How many of the newest videos to take from each playlist or channel link | 100 |
| Proxy configuration | Keep the default residential proxy (see [Limits](#limits)) | Apify residential |

Example: three videos, Spanish captions preferred, English otherwise:

```json
{
  "urls": ["https://www.youtube.com/watch?v=iG9CE55wbtY", "https://youtu.be/arj7oStGLkU", "jNQXAC9IVRw"],
  "languages": ["es", "en"]
}
```

Example: the 20 newest videos of a channel in German or English, text only:

```json
{
  "urls": ["https://www.youtube.com/@kurzgesagt"],
  "maxVideosPerSource": 20,
  "languages": ["de", "en"],
  "includeSegments": false
}
```

### Output (record format v1)

One record per video:

```json
{
  "video_id": "jNQXAC9IVRw",
  "url": "https://www.youtube.com/watch?v=jNQXAC9IVRw",
  "source_url": null,
  "status": "ok", "error_code": null, "error": null,
  "title": "Me at the zoo", "channel_name": "jawed", "channel_id": "UC4QobU6STFB0P71PMvOGN5A",
  "description": "Microplastics are accumulating in human brains at an alarming rate\nhttps://www.youtube.com/watch?v=0PT5c1z3LL8\n\n“Nanoplastics and Human Health” with Matthew J Campen, PhD, MSPH\nhttps://www.youtube.com/watch?v=RRBN_4L09Mg\n\n00:00 Intro\n00:05 The cool thing\n00:17 End",
  "view_count": 447000000,
  "thumbnail_url": "https://i.ytimg.com/vi/jNQXAC9IVRw/sddefault.jpg",
  "published_at": "2005-04-23T20:31:52-07:00",
  "duration_seconds": 19,
  "language": "en", "language_name": "English", "is_auto_generated": false,
  "available_languages": [
    { "code": "en", "name": "English", "is_auto_generated": false },
    { "code": "de", "name": "German", "is_auto_generated": false }
  ],
  "segment_count": 6,
  "segments": [
    { "start": 1.2, "duration": 2.16, "text": "All right, so here we are, in front of the elephants" },
    { "start": 5.318, "duration": 2.656, "text": "the cool thing about these guys is that they have really..." }
  ],
  "text": "All right, so here we are, in front of the elephants the cool thing about these guys is that they have really... really really long trunks and that's cool (baaaaaaaaaaahhh!!) and that's pretty much all there is to say",
  "subtitles": null,
  "scraped_at": "2026-10-30T17:23:32.782Z"
}
```

- `segments`: `start` and `duration` in seconds. `text` is all segments joined into one paragraph.

- `subtitles`: SRT or WebVTT text when requested and `status` is `ok`; otherwise `null`. Save the text as a `.srt` or `.vtt` file, or read it from the API. SRT example:

  ```srt
  1
  00:00:01,200 --> 00:00:03,360
  All right, so here we are, in front of the elephants

  2
  00:00:05,318 --> 00:00:07,974
  the cool thing about these guys is that they have really...
  ```

- `is_auto_generated: true` means YouTube's automatic speech recognition made the captions; expect missing punctuation and some wrong words. For some videos YouTube also offers automatic captions in another language (for example English for Japanese news); these are marked the same way.

- `source_url`: the playlist or channel link the video came from, or `null` for videos you entered directly.

- `description`, `view_count` and `thumbnail_url`: the video description, current view count and widest available thumbnail. The example view count is approximate and changes over time. Missing values are `null`.

- `published_at`: YouTube's ISO publication date or timestamp, keeping its timezone offset when supplied. It is optional; a failed date lookup leaves `null` without losing the transcript or changing its status.

- Field names in format v1 do not change; any future change will be announced here first.

`status` tells you what happened:

| status | error_code | Meaning | Charged |
|---|---|---|---|
| `ok` | `null` | Transcript delivered | yes |
| `no_captions` | `no_captions` | The video has no captions at all | no |
| `error` | `language_not_available` | Captions exist, but not in your languages, and fallback is off | no |
| `error` | `video_unavailable` | Private, deleted or non-existent video | no |
| `error` | `age_restricted` | YouTube shows it only to signed-in adults; this actor never signs in | no |
| `error` | `live_stream` | A live stream in progress or upcoming; captions appear after it ends | no |
| `error` | `unplayable` | YouTube refuses playback, for example in members-only videos | no |
| `error` | `source_unavailable` | A playlist or channel link could not be listed | no |
| `error` | `invalid_input` | The entry is not a YouTube video, playlist or channel link | no |
| `error` | `bot_check`, `transport`, `parse` | YouTube blocked or failed the request after all retries, or answered in an unexpected format | no |

A run summary (`RUN_SUMMARY`) is saved in the run's key-value store.

### Use it from code

Run the actor and read the transcripts with the official Apify API clients. Get your API token in Apify Console under **Settings > API & Integrations**.

Python (`pip install apify-client`):

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("cozybit/youtube-transcript-scraper").call(run_input={
    "urls": ["https://www.youtube.com/watch?v=iG9CE55wbtY"],
    "languages": ["en"],
})
for video in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(video["status"], video["title"])
    print(video["text"])  # the whole transcript as one plain-text paragraph
```

JavaScript (`npm install apify-client`):

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_API_TOKEN>' });
const run = await client.actor('cozybit/youtube-transcript-scraper').call({
    urls: ['https://www.youtube.com/watch?v=iG9CE55wbtY'],
    languages: ['en'],
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const video of items) console.log(video.status, video.title, video.text);
```

HTTP API (runs the actor and returns the records in one call; suited to small batches that finish within 5 minutes):

```bash
curl -X POST "https://api.apify.com/v2/acts/cozybit~youtube-transcript-scraper/run-sync-get-dataset-items" \
  -H "Authorization: Bearer <YOUR_API_TOKEN>" -H "Content-Type: application/json" \
  -d '{"urls": ["https://www.youtube.com/watch?v=iG9CE55wbtY"], "includeSegments": false}'
```

For an LLM prompt, use each record's `text` field (plain text, no timestamps) and skip records whose `status` is not `ok`.

### Pricing

**Trial price until 30 November 2026:** 0.01 USD per 1,000 transcripts (the lowest price Apify allows). From 1 December 2026 this scraper costs 2.00 USD per 1,000 transcripts. Apify notifies all users before a price change.

You pay per delivered transcript; platform usage (compute and proxies) is included.

| Event | Price |
|---|---|
| Transcript (`status: ok`) | 2.00 USD per 1,000 (0.002 USD each) |

Videos without captions, unavailable videos, errors and playlist or channel listing are free.

Worked examples:

- 10 videos: 10 x 0.002 = **0.02 USD**.
- The 100 newest videos of a channel, 92 of them with captions: 92 x 0.002 = **0.184 USD**.
- 5,000 videos: **10 USD**.

Long videos cost the same as short ones. To cap spending, set a maximum cost per run in the run options; the actor stops cleanly when it is reached.

### Free Apify plan

On the free Apify plan a run returns at most **20 videos**. When the limit is reached the run finishes successfully with the status message "Free Apify plan limit reached"; everything collected so far is in the dataset. Any paid Apify plan removes the limit.

### Reliability

In 44 test runs on the Apify platform with default settings (videos in six languages, manual and automatic captions, Shorts, podcasts of up to 5 hours, channel links), **all 407 videos were answered correctly**: 404 transcripts, 2 "no captions" and 1 private video reported as unavailable. None were lost to YouTube blocking. The median run of 10 videos took 15 seconds.

How it stays reliable: the actor reads the same caption data the YouTube app uses and asks only for the few fields it needs. When YouTube asks an IP to prove it is not a bot, the actor switches to a new IP, up to three times per video. Transcripts are fetched live on every run, never from a cache, and regular test runs notice changes on YouTube's side quickly.

### Limits

- Only captions that YouTube shows publicly. The actor never signs in, never downloads video or audio and does no speech-to-text of its own; videos without captions return `no_captions`.
- Age-restricted and members-only videos cannot be read without signing in, so they return an error (not charged).
- Use the default residential proxy. Datacenter proxies and runs without a proxy are blocked by YouTube most of the time.
- Playlists and channels: up to 5,000 videos per link, newest first; channel links list the channel's uploads, including Shorts and past live streams.
- Up to 1,000 entries per run; the same video is processed once even if it appears several times.
- No translation: YouTube refused every automated request for machine-translated captions in our tests, so the actor returns captions only in languages the video has.
- YouTube has no official public transcript API; when YouTube changes its apps the actor may fail until it is updated.
- Transcripts are the creators' content. You are responsible for using them in line with copyright law and YouTube's terms.

### Support

Report problems and feature requests on the actor's **Issues** tab, ideally with the run link.

# Actor input Schema

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

Enter 1-1000 video IDs, video URLs, playlist URLs or channel URLs (including @handles). Playlists and channels are expanded in the order entered; channels use their uploads playlist. A video URL with a playlist parameter still selects just that video. Duplicate videos are returned once. Free Apify plans process at most 20 videos per run; any paid Apify plan removes this cap.

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

Collect up to this many videos from each playlist or channel, in YouTube's listing order. Does not limit individual video URLs. Free Apify plans still process at most 20 videos in total.

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

ISO language codes in order of preference, such as en (English), de (German), fr (French) or pt-BR (Brazilian Portuguese). The first available language is selected. Manual captions are preferred over automatic captions in the same language. This chooses existing captions; it does not translate them.

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

If none of your preferred languages is available, use another caption language (manual first, otherwise automatic). When off, videos without a preferred language return a language_not_available error.

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

Include start time, duration and text for each caption segment. Full text is always returned.

## `subtitleFormat` (type: `string`):

Add a subtitles text field per video, ready to save as a .srt or .vtt file.

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

Residential proxies are recommended. No country is forced. Turn Apify Proxy off for direct access; blocked direct requests are not retried.

## Actor input object example

```json
{
  "urls": [
    "https://www.youtube.com/watch?v=iG9CE55wbtY",
    "https://youtu.be/arj7oStGLkU",
    "https://www.youtube.com/@kurzgesagt"
  ],
  "maxVideosPerSource": 100,
  "languages": [
    "en"
  ],
  "fallbackToAnyLanguage": true,
  "includeSegments": true,
  "subtitleFormat": "none",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

One record per video with status, language, text and timestamped segments.

## `runSummary` (type: `string`):

Counts of delivered, captionless and failed videos and request statistics.

# 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=iG9CE55wbtY",
        "https://www.youtube.com/watch?v=arj7oStGLkU",
        "https://www.youtube.com/watch?v=jNQXAC9IVRw"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("cozybit/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=iG9CE55wbtY",
        "https://www.youtube.com/watch?v=arj7oStGLkU",
        "https://www.youtube.com/watch?v=jNQXAC9IVRw",
    ] }

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

```

## MCP server setup

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