# YouTube Transcript API (`danthedataman/youtube-transcript-language-api`) Actor

Get YouTube transcripts in supplied languages from videos, Shorts, channels and playlists. Export segments, plain text and video metadata.

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

## Pricing

from $1.76 / 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?

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

Extract supplied YouTube captions from video IDs, video URLs, Shorts, channels and playlists. Get one transcript per video with language, manual/auto kind, timestamped segments, plain text and video metadata.

Choose caption languages in order, prefer manual captions within each requested language, or exclude auto-generated captions. Failures are saved separately in the run's `ERRORS` record. Attempts that fail availability or access checks do not produce transcript rows or transcript-row charges.

### Quick start

```json
{
  "sources": [
    "https://www.youtube.com/watch?v=iG9CE55wbtY",
    "https://www.youtube.com/watch?v=qp0HIF3SfI4"
  ],
  "preferredLanguages": ["en", "fr"],
  "includeAutoGenerated": true,
  "includeSegments": true,
  "includePlainText": true
}
```

For a channel, use `https://www.youtube.com/@TED` or a `/channel/UC…` URL. For a playlist, use `https://www.youtube.com/playlist?list=PL8dPuuaLjXtNlUrzyH5r6jN9ulIgZBpdo`. Mix video, channel and playlist sources in one run. Repeated video IDs produce at most one transcript per run.

### Input

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `sources` | array | none (form prefill only) | Video IDs or HTTPS YouTube sources. Omitted, null or empty sources mean no source work. Watch, youtu.be, Shorts, live and embed video URLs are accepted. Channels accept handles or channel IDs; playlists accept a list ID in their URL. |
| `maxVideosPerSource` | integer | `10` | Maximum distinct video IDs inspected per channel/playlist, 0–200. Zero means no source work. Failed, date-filtered and already-seen-in-run videos consume the per-source bound. Single-video sources inspect one. |
| `preferredLanguages` | array | `[]` | Exact supplied language codes in fallback order, matched without case sensitivity. Manual before auto within the matching language. If no preference matches, use the unique source default when eligible. If that default exists but is ineligible, use the first eligible track. If no unique source default exists, report `CAPTION_SELECTION`. Empty follows the same default selection. |
| `includeAutoGenerated` | boolean | `true` | Allow auto-generated captions. False excludes them. |
| `includeSegments` | boolean | `true` | Include segments with times in seconds. |
| `includePlainText` | boolean | `true` | Include paragraph text joined with newlines. |
| `startDate` | string | `""` | Inclusive UTC publication date YYYY-MM-DD or timezone-bearing ISO timestamp. Optional. |
| `endDate` | string | `""` | Inclusive UTC publication date (whole day) or timezone-bearing ISO timestamp. Optional; cannot precede startDate. |

When no preference matches and YouTube supplies no single default track, the
video goes to `ERRORS` as `CAPTION_SELECTION`; name an available language in
`preferredLanguages` to select it. Date-only publication values retain that
precision and compare as midnight UTC against timestamp filters. A supplied
timestamp without a timezone is refused as `SOURCE_SCHEMA`.

Omitted or null optional settings use the defaults above; explicit false, zero and empty arrays are preserved. Date filters apply within the per-source bound, so a source may yield fewer transcripts than the bound. A missing publication date becomes an error when a date filter needs it. An input of `{}` exits successfully with guidance and an empty dataset. Both text output toggles may be false; the Actor still fetches and validates a nonempty transcript before emitting its metadata row.

### Output

One dataset item is one video with a nonempty supplied caption transcript. The following is an illustrative field-shape example, with sample values rather than a claimed live result:

```json
{
  "videoId": "iG9CE55wbtY",
  "url": "https://www.youtube.com/watch?v=iG9CE55wbtY",
  "title": "Example video title",
  "channelName": "Example channel",
  "channelId": "UCAuUUnT6oDeKwE6v1NGQxug",
  "languageCode": "en",
  "kind": "manual",
  "availableCaptionLanguages": ["en", "fr"],
  "segments": [{"start": 0.4, "duration": 1.2, "end": 1.6, "text": "Example caption."}],
  "plainText": "Example caption.",
  "description": "Example video description",
  "durationSeconds": 123,
  "viewCount": 876,
  "likeCount": 92,
  "publishedAt": "2007-01-07T05:00:34Z",
  "publishedTimestamp": 1168146034,
  "thumbnail": "https://i.ytimg.com/vi/iG9CE55wbtY/hqdefault.jpg",
  "channelUsername": "TED",
  "channelThumbnail": null,
  "subscriberCount": 27900000,
  "subscriberCountPrecision": "rounded",
  "subscriberCountText": "27.9M subscribers",
  "commentCount": 13034,
  "commentCountText": "13K",
  "availableCountries": ["US", "CA"],
  "geoRestriction": {"allowedCountries": ["US", "CA"]},
  "scrapedAt": "2026-10-06T17:00:00Z"
}
```

`start` and `duration` are seconds converted from supplied caption milliseconds. `end` is start plus duration; absent duration makes both duration and end `null`. Source words, text-node order and line breaks are preserved. Nonempty paragraphs are joined with newlines for `plainText`; segments may overlap as they do in YouTube's captions. `kind` is `manual` or `auto-generated`. The language inventory lists supplied caption codes, including auto tracks even when they are excluded from selection. No machine translation is requested.

`scrapedAt` is the UTC read time. `publishedAt` is the supplied publication time normalized to UTC; a date-only source remains YYYY-MM-DD. Duration is seconds; views and likes are counts. Metadata absent from the public responses is `null`. The availability player response decides video availability. The metadata player can report that its own playback client is unavailable while still supplying metadata; if it omits that metadata, `likeCount`, `publishedAt`, `publishedTimestamp`, `availableCountries` and `geoRestriction` are `null`. Date filters then report `PUBLICATION_UNKNOWN`.

`subscriberCount` expands a displayed number such as 27.9M to 27900000. `subscriberCountPrecision` is `rounded` for compact displays, `exact` for an unabridged integer, and `null` when absent. This expansion does not recover the exact subscriber total. `commentCount` uses the unabridged public comment-header count when supplied, otherwise an unabridged display; compact displays remain text and do not become exact counts.

`publishedTimestamp` is Unix seconds for a timezone-bearing publication time, and `null` for a date-only or absent value. `geoRestriction.allowedCountries` and `availableCountries` carry the supplied country-code allowlist. Missing lists are `null`; this is not a test of playback in every country. `segments` and `plainText` are omitted when their toggles are false.

### Errors and reliability

Open the run's key-value store record `ERRORS` for reasons, source URLs and video IDs when known. Codes distinguish `INVALID_INPUT`, `INVALID_SOURCE`, `NO_CAPTIONS`, `AUTO_CAPTIONS_EXCLUDED`, `EMPTY_CAPTIONS`, `CAPTION_SCHEMA`, `CAPTION_SELECTION`, `PRIVATE`, `REMOVED`, `AGE_RESTRICTED`, `LOGIN_REQUIRED`, `UNAVAILABLE`, `BOT_WALL`, `RATE_LIMITED`, `ACCESS_DENIED`, `SOURCE_5XX`, `HTTP_ERROR`, `TRANSPORT`, `SOURCE_SCHEMA`, `IDENTITY_MISMATCH`, `DISCOVERY_SCHEMA`, `SOURCE_REJECTED`, `SOURCE_UNAVAILABLE`, `REPEATED_CONTINUATION`, `DISCOVERY_STALLED`, `BROWSER_BLOCK`, `BROWSER_FAILED`, `PUBLICATION_UNKNOWN`, `PROXY_SETUP` and `DATASET_WRITE`. Invalid source entries are identified by their zero-based input index.

The primary path uses public JSON responses without downloading each video's watch page. The five video request classes (bootstrap, availability player, metadata player, comment header and captions) use US Apify residential sessions. Field selection keeps these JSON responses small. Channel/playlist discovery starts without a proxy; a measured bot wall, HTTP 429/403/5xx or transport failure moves only the affected request class to residential.

There are up to three browserless attempts with fresh sessions between attempts, then an isolated headless Playwright browser fallback by default. No account login or account cookies are used. Two videos can be fetched at once on the browserless path; each worker spaces request starts by at least 250 milliseconds. After date filtering, browserless caption and comment-header requests can overlap.

Browser fallback work runs one video at a time, closes each browser session and allows one fresh-session retry for classified transient failures. Its 45-second item budget includes queue time. Use at least 512 MB memory: forced browser work stalled at 256 MB in the retained platform checks. Discovery remains sequential. Permanent availability, identity and caption outcomes are reported directly. Valid empty sources and date-filtered results exit successfully; a run in which every attempt fails ends as failed. Partial success retains successful rows and the remaining error reasons. Once the run reaches the customer's maximum charge, the Actor starts no further source or video requests.

Channels expand their public `/videos` tab in source order. Separate channel Shorts and live-stream tabs are not expanded; provide their video URLs or a playlist explicitly. Playlists expand their supplied video items and continuations in source order. Private channels/playlists, legacy `/user/` and `/c/` channel URLs, search results, comments, audiovisual downloads, translation and captions requiring sign-in are outside coverage. Caption text comes from YouTube; accuracy and audiovisual completeness are not independently established.

A page alert is treated as `SOURCE_UNAVAILABLE`, including an informational
alert if YouTube supplies one. The per-source bound counts distinct video IDs. Two consecutive pages that add no new IDs while still offering a continuation stop that source with `DISCOVERY_STALLED`, even if their tokens differ. A final page without a continuation ends the source normally. Use individual video URLs when a source has this behavior.

#### Speed

In the timing sample, single-video runs finished in about 5 seconds without retries or browser fallback. About 1 to 1.5 seconds was Python and SDK start-up. Much of the remaining time was spent on requests to YouTube through residential proxies. Tested direct video requests from Apify's servers were blocked. In testing, pulling the container image took about 15 seconds. A run with an image pull and slow responses over residential proxies lasted past a minute. Larger batches share the start-up time across videos.

### Pricing

Each delivered transcript is one billable dataset-item event. Errors and filtered videos do not incur transcript-row charges. An Actor Start charge may also apply, including to empty runs. Check the Actor page for the current event prices.

### Python and JavaScript examples

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("danthedataman/youtube-transcript-language-api").call(
    run_input={"sources": ["qp0HIF3SfI4"], "preferredLanguages": ["en", "fr"]}
)
for transcript in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(transcript["plainText"])
```

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('danthedataman/youtube-transcript-language-api').call({
  sources: ['qp0HIF3SfI4'], preferredLanguages: ['en', 'fr']
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map(item => item.plainText));
```

Apify datasets can also be exported as JSON, CSV or Excel. Keep language and kind with the text when building a multilingual search index.

### Frequently asked questions

#### Does this transcribe speech?

It reads captions supplied by YouTube. Videos without eligible captions produce an `ERRORS` entry; there is no speech-to-text or translation.

#### Can I select a language?

Set `preferredLanguages` in order. The row states the selected language and whether its captions are manual or auto-generated.

#### Why are some counts null?

Counts and geography depend on the public responses. Rounded subscriber values are labeled; exact comment headers are not supplied for every video.

### Support

For a reproducible issue, open the Actor's Issues tab with the public video URL, input settings, run ID and `ERRORS` code. Do not include API tokens or account cookies.

# Actor input Schema

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

YouTube video IDs or HTTPS watch, youtu.be, Shorts, live or embed URLs; @handle or /channel/ID channel URLs; or /playlist?list=ID URLs. Omitted, null or empty means no work.

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

Inspect at most this many distinct video IDs per channel or playlist in source order. Failed, filtered and duplicate-in-run videos consume the source bound. Zero means no work. Single-video sources inspect one video. Range 0–200.

## `preferredLanguages` (type: `array`):

Try supplied caption language codes in this order, manual before auto within each language. If no preference matches, use the unique source default when eligible. If that default exists but is ineligible, use the first eligible track. If no unique source default exists, report CAPTION_SELECTION. No translation is requested.

## `includeAutoGenerated` (type: `boolean`):

Allow auto-generated caption tracks. False excludes them; a video with only auto captions goes to ERRORS.

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

Include segments with start and duration in seconds. Missing source duration is null. Captions are still fetched and validated when false.

## `includePlainText` (type: `boolean`):

Include plainText by joining nonempty source paragraphs with newlines. Captions are still validated when false.

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

Optional inclusive UTC calendar date YYYY-MM-DD or ISO timestamp with timezone. Applies within the per-source bound. Missing source publication time goes to ERRORS when filtering.

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

Optional inclusive UTC calendar date YYYY-MM-DD (whole day) or ISO timestamp with timezone. Must not precede startDate.

## Actor input object example

```json
{
  "sources": [
    "https://www.youtube.com/watch?v=iG9CE55wbtY",
    "https://www.youtube.com/watch?v=qp0HIF3SfI4"
  ],
  "maxVideosPerSource": 10,
  "preferredLanguages": [],
  "includeAutoGenerated": true,
  "includeSegments": true,
  "includePlainText": true,
  "startDate": "",
  "endDate": ""
}
```

# Actor output Schema

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

No description

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

Classified failures when present; never dataset rows.

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

// Run the Actor and wait for it to finish
const run = await client.actor("danthedataman/youtube-transcript-language-api").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": [
        "https://www.youtube.com/watch?v=iG9CE55wbtY",
        "https://www.youtube.com/watch?v=qp0HIF3SfI4",
    ] }

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,danthedataman/youtube-transcript-language-api"
        }
    }
}
```

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/bRR7Sz2Wim25Ql7Mq/builds/ZHUIEmbVefyUi3qXI/openapi.json
