# YouTube Transcript Scraper — Never Silent, LLM-Ready (`catalini82/youtube-transcript-scraper`) Actor

Public YouTube transcript scraper with full batch accounting: every input returns a transcript or a typed, actionable failure — the run is never silent. Supports direct videos plus playlist/channel expansion, caption-availability auditing, and LLM-ready output as JSON, text, SRT, WebVTT, or chunks.

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

## Pricing

from $8.00 / 1,000 transcript delivereds

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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js/docs.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python/docs.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/integrations/mcp.md).

If your project is in a different language, use 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 Transcript Scraper — Never Silent, LLM-Ready

Extract public YouTube captions at scale with **honest batch accounting**. In a normally
completed run with functioning platform storage, every accepted input returns either a transcript
**or** a typed, actionable failure. Feed it public video URLs, playlists, or channels and get back
clean text, timestamped segments, SRT/WebVTT subtitles, or LLM-ready chunks.

Python-only runtime. No Node, YouTube account, retained cookies, API key, audio download, or
browser automation.

### Why this Actor

Most transcript scrapers quietly drop the videos they can't handle: you send 100 URLs, get 63
rows back, and have no idea what happened to the other 37. This one is built around a different
promise — **complete accounting**:

- **Never silent.** In a normally completed run with functioning platform storage, every input
  produces one accounted result. Successes carry the transcript; failures carry a typed status
  (`no_captions`, `video_unavailable`, `rate_limited`, `blocked`, `language_unavailable`, …)
  plus a plain-English remediation.
- **Typed, actionable failures.** Each failure says what went wrong and whether retrying will
  help, so you can react programmatically instead of guessing.
- **Playlist & channel expansion.** Give it a public playlist or channel and it lists the videos
  (a bounded `maxItems` prefix, newest-first for channels) and extracts each transcript, with a
  parent-level `sourceExpansionRecord` accounting for what was discovered, admitted, and omitted.
- **Caption‑availability audit mode.** A text‑free mode that reports which caption tracks and
  languages a video advertises — manual vs. automatic — without downloading any transcript.
- **LLM‑ready output.** Deterministic overlapping chunks with token estimates, ready to embed
  or feed to a model.
- **Run‑level accountability report.** A machine‑readable summary in the key‑value store
  reconciles every expected input against what was actually written.

### ⚠️ Proxy: use Residential

YouTube blocks datacenter IP ranges, and the Apify cloud runs on datacenter IPs. Because of
this, **`proxyMode` defaults to `residential`**, which is the only mode that reliably reaches
YouTube from the cloud. `direct`, `apify_auto`, and `datacenter` are frequently rate-limited
or blocked — and when that happens the Actor tells you so with a typed `blocked` / `rate_limited`
result rather than silently returning nothing.

**The residential proxy cost is included in the per-transcript price.** You are not billed
separately for proxy traffic, and you do not need to supply your own proxy. Measured on real runs,
residential transfer is 85–96% of what this Actor actually costs to operate — that cost is carried
in the price rather than passed through to you.

### What you are charged for

Charging is driven by what actually lands in your dataset, so you pay for delivered output and never
for work the Actor didn't do. See the pricing box on the Store page for exact figures.

Two rules decide every video row:

1. **No attempt, no charge.** If the Actor never made a network request for an input, that row is
   free.
2. **You pay for answers about the video, never for our routing problems.** A transcript, a caption
   inventory, or a definitive verdict about the video itself is chargeable. A blocked, rate-limited,
   or timed-out request is not.

| Outcome | Charged |
| --- | --- |
| Delivered transcript | Full transcript price |
| Caption-audit answer (`available` / `none`) | Audit price, below a transcript |
| Definitive verdict about the video — `no_captions`, `language_unavailable`, `video_unavailable`, `age_restricted`, `region_blocked` | Half the transcript price |
| Playlist/channel listing that returned videos | Listing price, never a transcript price |
| Duplicate video already extracted in this run | An eighth of the transcript price |
| `blocked`, `rate_limited`, `timeout`, upstream/proxy errors | **Free** — our problem, not yours |
| Inputs beyond `maxVideos`, invalid URLs | **Free** — no network work was performed |
| Inputs skipped after a charge limit or a dead route | **Free** — never attempted |
| Listing that returned no videos | **Free** |
| Rows the dataset never confirmed | **Free** |

### When a run stops early

Two conditions stop the Actor from spending on work it cannot deliver or bill:

- **Charge limit reached.** If you set a maximum charge for the run, extraction stops rather than
  producing output past your limit. Remaining inputs return `extraction_error` /
  `CHARGE_LIMIT_REACHED`.
- **Route definitively blocked.** After three consecutive `blocked`/`rate_limited` results with no
  success in between, the Actor stops rather than repeating a failing request for every remaining
  video. Remaining inputs return `blocked` / `REQUEST_BLOCKED` with `attempts: []`.

In both cases every remaining input still gets its own typed, **unbilled** row explaining why it was
not attempted, and the `ACCOUNTABILITY_REPORT` carries a `runStop` section (`stopped`, `stopReason`,
`skippedInputCount`). The `OUTPUT` summary surfaces `runStopped` and `runStopReason`.

### Input

#### Transcript mode (default)

```json
{
  "videoUrls": [
    "https://www.youtube.com/watch?v=VIDEO_ID",
    "https://youtu.be/VIDEO_ID"
  ],
  "sources": [
    { "type": "playlist", "value": "PUBLIC_PLAYLIST_ID", "maxItems": 25 },
    { "type": "channel",  "value": "@public_handle",     "maxItems": 25 }
  ],
  "languages": ["en"],
  "languageFallback": true,
  "outputFormat": "llm",
  "includeMetadata": true,
  "includeSegments": true,
  "includeTranscriptText": true,
  "includeChunks": true,
  "maxVideos": 25,
  "maxConcurrency": 3,
  "perVideoTimeoutSecs": 30,
  "proxyMode": "residential"
}
````

`videoUrls` accepts watch, `youtu.be`, Shorts, embed, and live URL shapes. `sources` is an
optional ordered list of `video`, `playlist`, or `channel` entries; `videoUrls` and `sources`
can be combined.

> **Expansion behavior.** Playlist and channel expansion reads YouTube's current public listing
> format and returns each source's videos as a bounded `maxItems` prefix (channels newest‑first),
> with a parent `sourceExpansionRecord` accounting for discovery. Sources larger than `maxItems`
> are reported `limited`. Because it relies on an unofficial listing surface, a future YouTube
> format change fails safe with a typed `source_protocol_error` rather than dropping videos
> silently; direct `videoUrls` are always unaffected.

#### Caption‑audit mode

```json
{
  "operationMode": "caption_audit",
  "videoUrls": ["https://youtu.be/VIDEO_ID"],
  "languages": ["en", "de"],
  "includeMetadata": true,
  "proxyMode": "residential"
}
```

Audit mode never downloads a transcript. Each row reports the advertised caption inventory —
track count, available languages, and whether captions are manual, automatic, or of unknown
generation — or a fail‑closed `unknown` when a complete inventory can’t be safely established.
`languages` expresses optional interests and is reported as available/unavailable without
filtering the full inventory.

#### Key options

| Option | Default | Notes |
| --- | --- | --- |
| `outputFormat` | `llm` | `json`, `text`, `srt`, `vtt`, or `llm` (text + chunks). |
| `languages` | `["en"]` | Priority‑ordered caption language codes. |
| `languageFallback` | `true` | Use another advertised track if the preferred language is missing. |
| `includeSegments` / `includeTranscriptText` | `true` / `true` | At least one must stay enabled; current successful rows always retain both core payloads. |
| `includeChunks` / `chunkMaxTokens` / `chunkOverlapTokens` | `true` / `1000` / `100` | LLM chunking; overlap ≤ half of max. |
| `maxVideos` | `25` | Fetch budget. Inputs beyond it still get a typed `MAX_VIDEOS_LIMIT` row. |
| `maxConcurrency` | `3` | Videos processed at once. |
| `perVideoTimeoutSecs` | `30` | One absolute deadline per video (max 120). |
| `proxyMode` | `residential` | See the proxy note above. |
| `proxyCountry` | `""` | Optional two‑letter country code. |
| `debugMode` | `false` | Adds safe per‑attempt caption/HTTP diagnostics (never secrets). |

### Output

Each result is pushed to the dataset as soon as it finishes, so **dataset order is completion
order** — sort by `inputIndex` if you need input order. A successful transcript row looks like:

```jsonc
{
  "inputIndex": 0,
  "videoId": "M7lc1UVf-VE",
  "canonicalUrl": "https://www.youtube.com/watch?v=M7lc1UVf-VE",
  "status": "ok",
  "statusDetail": "Extracted 466 caption segments in 'en' through youtube-transcript-api.",
  "errorCode": "",
  "remediation": { "actionCode": "OK_NO_ACTION", "retryClass": "none", "retryRecommended": false },
  "languageUsed": "en",
  "languageAvailable": ["en"],
  "isAutoGenerated": false,
  "title": "YouTube Developers Live: Embedded Web Player Customization",
  "channelName": "Google for Developers",
  "durationSeconds": 1344,
  "viewCount": 1642048,
  "publishedAt": "2013-04-10T10:25:04-07:00",
  "transcriptText": "JEFF POSNICK: Hey, everybody. Welcome to this week's show ...",
  "segments": [ { "text": "JEFF POSNICK: Hey, everybody.", "startSeconds": 10.349, "durationSeconds": 1.0, "endSeconds": 11.349 } ],
  "chunks": [ /* deterministic overlapping LLM chunks with token estimates */ ],
  "transcriptCharCount": 20676,
  "tokenCountEstimate": 5169,
  "extractionStrategy": "youtube_transcript_api",
  "proxyModeUsed": "residential",
  "fetchedAt": "2026-07-14T18:06:05Z"
}
```

A failed row carries empty transcript payloads plus the typed diagnosis:

```jsonc
{
  "inputIndex": 2,
  "videoId": "…",
  "status": "video_unavailable",
  "errorCode": "VIDEO_UNAVAILABLE",
  "statusDetail": "The library reports that the public video is unavailable.",
  "remediation": { "actionCode": "CHECK_PUBLIC_AVAILABILITY", "retryClass": "manual_review", "retryRecommended": false },
  "transcriptText": "", "segments": [], "chunks": []
}
```

Playlist/channel sources emit a `sourceExpansionRecord` describing what was discovered
(`expansionStatus`, `admittedChildren`, `discoveryCompleteness`, ordering, and a typed
`primaryError` + remediation on failure). Expanded child videos are ordinary transcript rows
tagged with their `sourceType` and `sourceInputIndex`.

#### Statuses

`ok`, `no_captions`, `language_unavailable`, `video_unavailable`, `age_restricted`,
`region_blocked`, `blocked`, `rate_limited`, `timeout`, `invalid_url`, `extraction_error`,
`unknown_error`. Read `status`, `errorCode`, `statusDetail`, and `remediation` together —
`null` metadata alone never means success.

#### Accountability report

After processing, the Actor writes an `ACCOUNTABILITY_REPORT` record to the default key‑value
store: expected vs. accounted vs. missing `inputIndex` values, status/error/strategy tallies,
and (for expansion runs) source‑ and child‑level counts. `accountingComplete` is `true` only
when every expected input has a confirmed dataset row.

It also carries a `charging` section (what the run billed, and what it would have billed) and a
`runStop` section describing any early stop.

### Extraction architecture

One proven primary engine, with corroborating fallbacks that share code with the audit feature:

1. **`youtube-transcript-api`** — the primary, battle‑tested engine. It fetches captions through
   YouTube’s anonymous InnerTube player surface and wins the vast majority of transcripts.
2. **Anonymous InnerTube fallbacks** (`android_innertube`, `direct_timedtext`, `innertube_player`)
   — Actor‑owned public player/timed‑text paths tried only if the primary can’t produce a usable
   transcript. They also produce the positive caption‑availability evidence that powers
   caption‑audit mode, so they’re not dead weight.

All paths use anonymous public surfaces only, run through the selected proxy, discard cookies,
and never accept or emit keys, tokens, caption URLs, or proxy credentials. The chain stops at the
first usable transcript and records every attempt (visible with `debugMode`).

### Limitations

- **Public captions only.** Age‑restricted, private, deleted, members‑only, and login‑required
  videos are intentionally unsupported. Translations are not generated — `languageFallback`
  selects an existing alternate track, it does not machine‑translate.
- **YouTube can change.** These are anonymous web/player surfaces with no official public
  transcript API; upstream changes can require maintenance.
- **Playlist/channel expansion is bounded.** Each source returns up to a `maxItems` prefix
  (1–50, newest‑first for channels); larger sources are reported as `limited`. It reads YouTube's
  current public listing surface, so upstream format changes fail safe with a typed error rather
  than wrong data; direct video URLs are unaffected.
- **Proxy‑dependent.** Reliable cloud extraction requires residential egress (see above).
- **Token counts are approximate** and deterministic, not tied to a specific tokenizer.
- **Live/premiere/just‑uploaded** videos and very recently changed captions are less predictable.

### Use from the Apify Console, API, or integrations

Run the Actor from its Input tab, save reusable tasks, call it through the Apify API, or connect it to your automation and AI workflows.

Results are available in the default dataset, while the bounded run summary and full accountability report are available from the run output and key-value store.

Only use this Actor for content you are entitled to process. Public availability does not grant
ownership or remove copyright, privacy, or platform‑policy obligations.

# Actor input Schema

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

Public watch, youtu.be, Shorts, embed, or live URLs. Normally completed processing pushes a success or typed failure for every entry. Platform termination or total dataset outage can prevent a write.

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

Optional ordered source occurrences. Playlist and channel expansion uses a bounded public prefix; repeated values remain separate occurrences.

## `operationMode` (type: `string`):

Extract transcript output or perform a text-free public caption inventory audit.

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

Caption language codes in priority order, such as en, en-US, de, or es.

## `languageFallback` (type: `boolean`):

When no preferred language exists, use another advertised caption track instead of returning language\_unavailable.

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

Select the primary transcript representation. Unified status and metadata fields are always present.

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

Include public title, channel, duration, view count, publication date, description, and thumbnail when available.

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

Include the normalized timestamped caption segments in each record. At least one of segments or full transcript text must stay enabled.

## `includeTranscriptText` (type: `boolean`):

Include the joined full transcript text in each record. At least one of segments or full transcript text must stay enabled.

## `includeChunks` (type: `boolean`):

Include deterministic overlapping text chunks. LLM format always includes them.

## `chunkMaxTokens` (type: `integer`):

Approximate token limit for each LLM chunk. The overlap must be at most floor(chunkMaxTokens / 2).

## `chunkOverlapTokens` (type: `integer`):

Approximate context repeated between adjacent chunks. Runtime validation requires this value to be at most floor(chunkMaxTokens / 2), which guarantees a stride of at least half a chunk.

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

Only this many entries are fetched. Later entries still receive typed MAX\_VIDEOS\_LIMIT records so none disappear silently.

## `maxConcurrency` (type: `integer`):

Number of videos processed at once. Conservative defaults reduce blocking risk.

## `perVideoTimeoutSecs` (type: `integer`):

One total monotonic deadline shared by all extraction attempts for one video. Finalizing a timed-out isolated library worker may use up to 0.35 seconds of bounded terminate/kill/reap cleanup beyond it.

## `proxyMode` (type: `string`):

Which network egress to use. YouTube blocks datacenter IP ranges, so Residential is the default and the only mode that reliably works on the Apify cloud. Direct, automatic, and datacenter routes are frequently rate-limited or blocked; when that happens the Actor reports a typed 'blocked'/'rate\_limited' failure instead of silently returning nothing. Residential proxy traffic counts against your Apify residential proxy quota.

## `proxyCountry` (type: `string`):

Optional two-letter country code for an explicitly selected Apify proxy mode, for example US or DE.

## `debugMode` (type: `boolean`):

Include safe parser/request diagnostics. Proxy URLs, credentials, cookies, and secrets are never included.

## Actor input object example

```json
{
  "videoUrls": [
    "https://www.youtube.com/watch?v=jNQXAC9IVRw"
  ],
  "operationMode": "transcript",
  "languageFallback": true,
  "outputFormat": "llm",
  "includeMetadata": true,
  "includeSegments": true,
  "includeTranscriptText": true,
  "includeChunks": true,
  "chunkMaxTokens": 1000,
  "chunkOverlapTokens": 100,
  "maxVideos": 25,
  "maxConcurrency": 3,
  "perVideoTimeoutSecs": 30,
  "proxyMode": "residential",
  "proxyCountry": "",
  "debugMode": false
}
```

# Actor output Schema

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

Complete transcript, caption-audit, failure, and source-accounting rows from the default dataset.

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

Bounded run summary with status counts, charging information, and any early-stop reason.

## `accountabilityReport` (type: `string`):

Full reconciliation of expected, accounted, missing, written, free, and charged outcomes for the run.

# 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=jNQXAC9IVRw"
    ]
};

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

# Run the Actor and wait for it to finish
run = client.actor("catalini82/youtube-transcript-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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=jNQXAC9IVRw"
  ]
}' |
apify call catalini82/youtube-transcript-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=catalini82/youtube-transcript-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "YouTube Transcript Scraper — Never Silent, LLM-Ready",
        "description": "Public YouTube transcript scraper with full batch accounting: every input returns a transcript or a typed, actionable failure — the run is never silent. Supports direct videos plus playlist/channel expansion, caption-availability auditing, and LLM-ready output as JSON, text, SRT, WebVTT, or chunks.",
        "version": "0.6",
        "x-build-id": "KC7ioNrRBFWhxpjKp"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/catalini82~youtube-transcript-scraper/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-catalini82-youtube-transcript-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/catalini82~youtube-transcript-scraper/runs": {
            "post": {
                "operationId": "runs-sync-catalini82-youtube-transcript-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/catalini82~youtube-transcript-scraper/run-sync": {
            "post": {
                "operationId": "run-sync-catalini82-youtube-transcript-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "properties": {
                    "videoUrls": {
                        "title": "YouTube video URLs",
                        "minItems": 1,
                        "maxItems": 100,
                        "type": "array",
                        "description": "Public watch, youtu.be, Shorts, embed, or live URLs. Normally completed processing pushes a success or typed failure for every entry. Platform termination or total dataset outage can prevent a write.",
                        "items": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 500
                        }
                    },
                    "sources": {
                        "title": "Video, playlist, or channel sources",
                        "minItems": 1,
                        "maxItems": 100,
                        "type": "array",
                        "description": "Optional ordered source occurrences. Playlist and channel expansion uses a bounded public prefix; repeated values remain separate occurrences.",
                        "items": {
                            "type": "object",
                            "additionalProperties": false,
                            "required": [
                                "type",
                                "value"
                            ],
                            "properties": {
                                "type": {
                                    "title": "Source type",
                                    "description": "Select video, playlist, or channel. Search is unsupported.",
                                    "type": "string",
                                    "enum": [
                                        "video",
                                        "playlist",
                                        "channel"
                                    ]
                                },
                                "value": {
                                    "title": "Public source identity or URL",
                                    "description": "Use one supported public identity or canonical HTTPS YouTube URL.",
                                    "type": "string",
                                    "minLength": 1,
                                    "maxLength": 2048
                                },
                                "maxItems": {
                                    "title": "Maximum expanded items",
                                    "description": "Playlist/channel prefix size, from 1 through 50. Video sources must omit this field.",
                                    "type": "integer",
                                    "default": 25,
                                    "minimum": 1,
                                    "maximum": 50
                                }
                            }
                        }
                    },
                    "operationMode": {
                        "title": "Operation mode",
                        "enum": [
                            "transcript",
                            "caption_audit"
                        ],
                        "type": "string",
                        "description": "Extract transcript output or perform a text-free public caption inventory audit.",
                        "default": "transcript"
                    },
                    "languages": {
                        "title": "Preferred languages",
                        "maxItems": 10,
                        "type": "array",
                        "description": "Caption language codes in priority order, such as en, en-US, de, or es.",
                        "items": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 35
                        }
                    },
                    "languageFallback": {
                        "title": "Allow another caption language",
                        "type": "boolean",
                        "description": "When no preferred language exists, use another advertised caption track instead of returning language_unavailable.",
                        "default": true
                    },
                    "outputFormat": {
                        "title": "Primary output format",
                        "enum": [
                            "json",
                            "text",
                            "srt",
                            "vtt",
                            "llm"
                        ],
                        "type": "string",
                        "description": "Select the primary transcript representation. Unified status and metadata fields are always present.",
                        "default": "llm"
                    },
                    "includeMetadata": {
                        "title": "Include video metadata",
                        "type": "boolean",
                        "description": "Include public title, channel, duration, view count, publication date, description, and thumbnail when available.",
                        "default": true
                    },
                    "includeSegments": {
                        "title": "Include timestamped segments",
                        "type": "boolean",
                        "description": "Include the normalized timestamped caption segments in each record. At least one of segments or full transcript text must stay enabled.",
                        "default": true
                    },
                    "includeTranscriptText": {
                        "title": "Include full transcript text",
                        "type": "boolean",
                        "description": "Include the joined full transcript text in each record. At least one of segments or full transcript text must stay enabled.",
                        "default": true
                    },
                    "includeChunks": {
                        "title": "Include LLM-ready chunks",
                        "type": "boolean",
                        "description": "Include deterministic overlapping text chunks. LLM format always includes them.",
                        "default": true
                    },
                    "chunkMaxTokens": {
                        "title": "Maximum estimated tokens per chunk",
                        "minimum": 50,
                        "maximum": 4000,
                        "type": "integer",
                        "description": "Approximate token limit for each LLM chunk. The overlap must be at most floor(chunkMaxTokens / 2).",
                        "default": 1000
                    },
                    "chunkOverlapTokens": {
                        "title": "Estimated overlap tokens",
                        "minimum": 0,
                        "maximum": 1000,
                        "type": "integer",
                        "description": "Approximate context repeated between adjacent chunks. Runtime validation requires this value to be at most floor(chunkMaxTokens / 2), which guarantees a stride of at least half a chunk.",
                        "default": 100
                    },
                    "maxVideos": {
                        "title": "Maximum videos to fetch",
                        "minimum": 1,
                        "maximum": 100,
                        "type": "integer",
                        "description": "Only this many entries are fetched. Later entries still receive typed MAX_VIDEOS_LIMIT records so none disappear silently.",
                        "default": 25
                    },
                    "maxConcurrency": {
                        "title": "Maximum concurrency",
                        "minimum": 1,
                        "maximum": 10,
                        "type": "integer",
                        "description": "Number of videos processed at once. Conservative defaults reduce blocking risk.",
                        "default": 3
                    },
                    "perVideoTimeoutSecs": {
                        "title": "Per-video timeout (seconds)",
                        "minimum": 5,
                        "maximum": 120,
                        "type": "integer",
                        "description": "One total monotonic deadline shared by all extraction attempts for one video. Finalizing a timed-out isolated library worker may use up to 0.35 seconds of bounded terminate/kill/reap cleanup beyond it.",
                        "default": 30
                    },
                    "proxyMode": {
                        "title": "Proxy mode",
                        "enum": [
                            "residential",
                            "apify_auto",
                            "datacenter",
                            "direct"
                        ],
                        "type": "string",
                        "description": "Which network egress to use. YouTube blocks datacenter IP ranges, so Residential is the default and the only mode that reliably works on the Apify cloud. Direct, automatic, and datacenter routes are frequently rate-limited or blocked; when that happens the Actor reports a typed 'blocked'/'rate_limited' failure instead of silently returning nothing. Residential proxy traffic counts against your Apify residential proxy quota.",
                        "default": "residential"
                    },
                    "proxyCountry": {
                        "title": "Proxy country",
                        "maxLength": 2,
                        "type": "string",
                        "description": "Optional two-letter country code for an explicitly selected Apify proxy mode, for example US or DE.",
                        "default": ""
                    },
                    "debugMode": {
                        "title": "Debug mode",
                        "type": "boolean",
                        "description": "Include safe parser/request diagnostics. Proxy URLs, credentials, cookies, and secrets are never included.",
                        "default": false
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
