# YouTube Transcripts for RAG (`omargnagy/youtube-transcripts-for-rag`) Actor

Turn YouTube videos into retrieval-ready text. Per video you get a full plain transcript plus token-bounded chunks (real cl100k tokens, your size and overlap) each carrying start and end timestamps, a deep link to the moment, and a deterministic chunk id for idempotent upserts. No API key, no proxy.

- **URL**: https://apify.com/omargnagy/youtube-transcripts-for-rag.md
- **Developed by:** [Omar Nagy](https://apify.com/omargnagy) (community)
- **Categories:** AI, Developer tools, Videos
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 transcripts

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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 Transcripts for RAG

Turn a list of YouTube videos into **retrieval-ready text**: one full plain-text transcript per video, plus **token-bounded chunks** that each carry a start and end timestamp, the video id, a deep link to that exact moment, and a **deterministic chunk id** you can use as an idempotent upsert key in a vector store.

No API key. No Google credentials. No Google account.

> **It uses residential egress, on purpose, for one request per video.** YouTube refuses caption metadata to datacenter IP addresses including Apify's, so the single request that reads a video's caption list goes out through Apify's RESIDENTIAL proxy group. The caption text itself, which is the bulk of the bytes, is downloaded on the ordinary datacenter connection. Measured on the platform, that costs **44 KB of residential bandwidth per video**, and every run reports what it spent. See "Residential egress and bandwidth".

### Who it is for

The concrete use case is an **AI agent or a RAG pipeline that has to answer questions about video content and cite the moment it came from.** You point it at a conference talk, a lecture series, a podcast back catalogue or a competitor's channel, and you get rows that go straight into an embedding call. Because every chunk carries `startSeconds` and a `timestampUrl`, the answer your agent produces can link back to the second in the video where the claim was made, which is the difference between a citation a human trusts and a paraphrase they do not.

It also suits:

- Building a searchable knowledge base out of a team's recorded talks or trainings.
- Feeding an LLM the actual words of a long video instead of its description.
- Language and content research where you need to know which caption tracks exist before you commit to a language.

### Why the chunking is the point

Most transcript tools hand you a wall of text and leave the hard part to you. This one does the part that decides whether retrieval works:

- **Real tokens, not characters.** Chunk size is counted with the `cl100k_base` BPE encoding, the one used by OpenAI's `text-embedding-3` models and GPT-4 class models. Every finished chunk is re-encoded and shrunk until it genuinely fits, so a 400-token chunk is never 438 tokens.
- **Timestamps survive chunking.** Chunks are packed out of caption segments, so `startSeconds` and `endSeconds` are the real boundaries of the speech inside that chunk.
- **Overlap so sentences are not cut in half.** The default 40-token overlap repeats the tail of one chunk at the head of the next, so a claim split across a boundary still retrieves.
- **Deterministic chunk ids.** The id is a hash of the video, language, chunk settings, position and text. Re-running the Actor on the same video produces the same ids, so a re-run is an upsert, not a pile of duplicates.
- **An honest language story.** You give a fallback chain, and the output tells you which code was actually used, which codes were tried, whether the track was auto-generated, and every caption language the video has.

### Modes

| Mode | What it does | Charged |
|---|---|---|
| `chunks` (default) | One video record with the full transcript, plus one record per chunk | `transcript` and `chunk` |
| `transcript` | One video record with the full transcript, no chunks | `transcript` |
| `languages` | Lists the caption tracks a video has, downloads none of them | free |

Any other value for `mode` is rejected as a bad request. It is never treated as a default and never silently ignored. Each mode is also a Standby HTTP route of the same name, see below.

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `videos` | array of strings | | **Required in every mode.** Video URLs or 11-character ids. Watch, `youtu.be`, Shorts, embed and live links all work. Deduplicated, order preserved. |
| `mode` | `chunks`, `transcript`, `languages` | `chunks` | anything else is a 400 |
| `languages` | array of strings | `["en"]` | fallback chain, tried in order, exact match then prefix (`en` accepts `en-GB`). Ignored when `mode` is `languages`. |
| `allowAutoGenerated` | boolean | `true` | off means only human-written tracks count. Ignored when `mode` is `languages`. |
| `allowAnyLanguage` | boolean | `false` | on means take the first available track when the chain misses. Ignored when `mode` is `languages`. |
| `chunkTokens` | integer 50 to 4000 | `400` | **only used when `mode` is `chunks`** |
| `chunkOverlapTokens` | integer 0 to 2000 | `40` | **only used when `mode` is `chunks`**, and **must be smaller than `chunkTokens`** or the run fails with a bad-request message |
| `includeFullTranscript` | boolean | `true` | off drops `fullText` from the video record. Always on when `mode` is `transcript`. |
| `maxVideos` | integer 1 to 500 | `50` | safety cap applied before any request |
| `proxyConfiguration` | object | Apify Proxy, `RESIDENTIAL` group | the lane used for caption discovery. Leave it on residential unless you know your egress is not gated by YouTube. |
| `residentialBudgetMb` | integer 1 to 10000 | `200` | hard ceiling on residential traffic for the run. On reaching it the run stops cleanly, every remaining video gets a `budget_exceeded` record, and nothing more is charged. |

```json
{
  "mode": "chunks",
  "videos": [
    "https://www.youtube.com/watch?v=aircAruvnKk",
    "https://www.youtube.com/watch?v=jNQXAC9IVRw"
  ],
  "languages": ["en"],
  "chunkTokens": 400,
  "chunkOverlapTokens": 40,
  "allowAutoGenerated": true,
  "includeFullTranscript": true
}
```

### Output

Two record types in one dataset, told apart by `type`. Two dataset views ship with the Actor, **Chunks** and **Videos**, so you can export either shape on its own.

Real output, from a run on 5 September 2026. One `video` record per video:

```json
{
  "type": "video",
  "videoId": "aircAruvnKk",
  "videoUrl": "https://www.youtube.com/watch?v=aircAruvnKk",
  "mode": "chunks",
  "languagesRequested": ["en"],
  "status": "ok",
  "title": "But what is a neural network? | Deep learning chapter 1",
  "channel": "3Blue1Brown",
  "channelId": "UCYO_jab_esuFRV4b17AJtAw",
  "videoDurationSeconds": 1120,
  "viewCount": 24124707,
  "language": "en",
  "isAutoGenerated": false,
  "languagesTried": ["en"],
  "languageFallbackUsed": false,
  "availableLanguages": ["ar", "bn", "zh", "zh-CN", "zh-TW", "cs", "en", "fil", "fr", "de", "el", "iw", "hi", "hu", "it", "ja", "ko", "mr", "fa", "fa-IR", "pl", "pt", "pt-BR", "ro", "ru", "es", "th", "tr", "uk", "ur"],
  "autoGeneratedLanguages": ["en"],
  "retrievalSource": "residential:player:IOS",
  "captionLane": "datacenter",
  "captionFormat": "xml",
  "segmentCount": 286,
  "chunkCount": 11,
  "transcriptChars": 18430,
  "transcriptTokens": 3817,
  "chunkTokens": 400,
  "chunkOverlapTokens": 40,
  "residentialBytes": 59671,
  "datacenterBytes": 14800,
  "fullText": "This is a 3. It's sloppily written and rendered at an extremely low resolution ..."
}
```

`retrievalSource` names the lane and client that found the caption list, `captionLane` the lane that downloaded the text, and the two byte counts are what this one video cost.

And one `chunk` record per chunk:

```json
{
  "type": "chunk",
  "chunkId": "aircAruvnKk-c9529d79aed76bde",
  "videoId": "aircAruvnKk",
  "videoUrl": "https://www.youtube.com/watch?v=aircAruvnKk",
  "title": "But what is a neural network? | Deep learning chapter 1",
  "channel": "3Blue1Brown",
  "language": "en",
  "isAutoGenerated": false,
  "chunkIndex": 1,
  "text": "What we're going to do is put together a neural network that can learn to recognize handwritten digits. This is a somewhat classic example for introducing the topic, and I'm happy to stick with the status quo here, because at the end of the two videos I want to point you to a couple good resources ...",
  "tokens": 396,
  "startSeconds": 100.96,
  "endSeconds": 214.16,
  "segmentCount": 30,
  "timestampUrl": "https://www.youtube.com/watch?v=aircAruvnKk&t=100s"
}
```

A video whose captions could not be retrieved gets a record too, so nothing disappears silently:

```json
{
  "type": "video",
  "videoId": "aircAruvnKk",
  "status": "error",
  "errorType": "captions_unreachable",
  "error": "Caption metadata could not be reached from this network. Every Innertube client was refused (playability: LOGIN_REQUIRED, ERROR; watch page HTTP 200). This usually means the request came from a datacenter IP that YouTube gates."
}
```

`errorType` values: `captions_unreachable` (the network was refused, the video may well have captions), `no_captions` (YouTube says the video is playable and publishes no caption track), `captions_disabled`, `language_not_available`, `empty_transcript`, `video_unavailable`, `rate_limited`, `fetch_failed`. The first two are deliberately separate, because reporting a blocked network as "this video has no captions" would teach an agent something false.

Larger samples are in the `examples/` folder of the source.

### Use it from an agent (Standby)

Standby keeps the Actor warm and answers over plain HTTP, so an agent calling mid-task pays no container start:

```
GET {standbyUrl}/chunks?videos=aircAruvnKk&chunkTokens=400&chunkOverlapTokens=40
GET {standbyUrl}/transcript?videos=aircAruvnKk&languages=en
GET {standbyUrl}/languages?videos=aircAruvnKk
GET {standbyUrl}/
```

`videos` and `languages` accept a comma-separated list. Every other input field works as a query parameter with the same name and the same defaults. `GET /` returns service info and the mode list.

A successful response is:

```json
{ "ok": true, "mode": "chunks", "videos": 1, "transcribed": 1, "failed": 0, "chunks": 1, "count": 2, "items": [ ... ], "tookMs": 1731 }
```

`items` holds the same records the dataset would receive, video record first. Errors are explicit and correctly typed:

- an unknown path is **404** with `{"ok": false, "error": "Unknown path \"/reviews\". Available: /chunks, /transcript, /languages."}`
- a bad or missing parameter is **400**, for example `{"ok": false, "error": "\"videos\" is required: give at least one YouTube video URL or 11-character video id."}`
- only a genuine fault returns 500

### Residential egress and bandwidth

YouTube decides whether to serve caption metadata based on the IP address asking. From Apify's datacenter egress every Innertube client answers HTTP 200 with `playabilityStatus: LOGIN_REQUIRED`, reason "Sign in to confirm you're not a bot", and zero caption tracks. The same request from a residential exit answers `OK` with the full tracklist. That is a property of YouTube, not of this code.

So the Actor splits its work across two lanes and spends the expensive one as sparingly as it can.

| Request | Lane | Typical size | Why |
|---|---|---|---|
| Innertube player call (the caption tracklist) | residential, after a free datacenter attempt | **28 to 60 KB** | the only request YouTube gates |
| Caption text download (`timedtext`) | datacenter, residential only if refused | 1.4 to 62 KB | a timedtext URL minted by a residential call is honoured from another IP, so the biggest payload stays on the cheap lane |
| Title and channel fallback (`oembed`) | datacenter | under 1 KB | never gated |

Three design choices come out of that, all measured rather than assumed:

- **The client ladder is ordered by what works.** Measured across both lanes on 5 September 2026, only the `IOS` and `ANDROID` clients ever return caption tracks. `ANDROID_VR`, `TVHTML5_SIMPLY_EMBEDDED_PLAYER`, `WEB_EMBEDDED_PLAYER`, `MWEB` and `WEB` never do. Asking those first, as an earlier version did, burned about 30 KB of residential bandwidth per video to learn nothing. They remain at the tail of the ladder only so a future YouTube change has somewhere to land.
- **The datacenter lane is tried first, every time.** On a host YouTube does not gate, the whole job runs without touching residential at all and costs nothing in proxy bandwidth. Verified: from an ordinary residential ISP connection all three sample videos are transcribed with `residentialBytes: 0`.
- **Requests are compressed and counted at the socket.** Every request asks for gzip, and the byte figures reported are TCP-level `bytesRead` plus `bytesWritten` on a fresh socket, after TLS and after compression. They are what the proxy meters, not an optimistic count of decoded characters.

Every video record carries `residentialBytes` and `datacenterBytes`, and the run log and status message carry the run totals and the residential bytes per transcript.

**If your plan has no residential proxy** the Actor does not crash. It runs the datacenter lane, and any video YouTube gates comes back with `errorType: "residential_proxy_required"` and a message saying so. Turn on the operator flag `debugConnectivity` to log every attempt with its lane, HTTP status and playability status.

### Pay-per-event

Two events, both charged per unit, no start fee. The live rates are always the ones on the Pricing tab.

| Event | Meaning | Rate |
|---|---|---|
| `transcript` | one video whose captions were fetched, decoded and normalized | **$0.005** |
| `chunk` | one chunk emitted | **$0.00005** |

What that works out to, from the three sample videos:

| Video | Length | Chunks | Cost |
|---|---|---|---|
| Me at the zoo | 19 seconds | 1 | $0.00505 |
| But what is a neural network? | 18 minutes | 11 | $0.00555 |
| Let's build GPT: from scratch | 1 hour 56 minutes | 63 | $0.00815 |

Rules that keep the bill honest:

- **A failed video costs nothing.** Only a video that actually produced a transcript is charged, and every error record is free.
- **`mode: "languages"` charges nothing at all**, because listing a video's caption tracks is free.
- **A run stopped by `residentialBudgetMb` charges nothing for the videos it did not reach.**
- **No start fee**, so trying the Actor on one video costs half a cent rather than a minimum.

### Limits

- **Captions must already exist.** This Actor reads YouTube's caption tracks. It does not transcribe audio, so a video with captions turned off returns an error record for that video, not silence.
- **Auto-generated captions are machine text.** No punctuation, no speaker labels, and real recognition errors on accents and jargon. Set `allowAutoGenerated: false` to refuse them.
- **One bad video does not fail the run.** A video that could not be retrieved is written to the dataset with `status: "error"` and an `errorType`, and the run continues. Only a run where *every* video failed is marked failed, and a run stopped by its own bandwidth budget is not: a limit doing its job is a successful run with explicit records, not a failure.
- **The error types are** `no_captions` (playable, but the uploader published no track), `language_not_available`, `empty_transcript`, `residential_proxy_required` (this run's network was gated and no residential lane was available), `captions_unreachable` (both lanes refused) and `budget_exceeded`.
- **Rate limiting.** Videos are paced with a short pause, and each residential request uses a fresh exit IP, so a gated address is retried from a different one rather than sticking.
- **A single caption segment longer than `chunkTokens`** is emitted as its own oversized chunk rather than being split at an invented boundary. It is rare, and the chunk's real `tokens` value tells you when it happened.
- **Timestamps come from the caption track**, so they are as precise as the captions are, which for auto-generated tracks means roughly phrase-level.

### What it does not do

It does not transcribe audio, download video or audio files, read private, unlisted or age-restricted videos, translate captions, diarize speakers, bypass any bot check, or call any paid API. If a video has no caption track, this Actor cannot produce a transcript for it.

### FAQ

**Which token encoding is used?**
`cl100k_base`. That is the encoding behind `text-embedding-3-small`, `text-embedding-3-large` and GPT-4 class models. Counts for other tokenizers are close but not identical.

**Can I get a transcript in a language the video was not spoken in?**
Only if the uploader published a caption track in that language. Put the codes you want in `languages` and the output reports what it found. This Actor does not translate.

**What is `timestampUrl` for?**
It is a watch link with `&t=` set to the chunk's start second, so a citation in your agent's answer opens the video at the right moment.

**Is the chunk id stable?**
Yes, for the same video, language, `chunkTokens`, `chunkOverlapTokens`, position and text. Change the chunk settings and you get different chunks, so you get different ids, which is the correct behaviour. The id is also identical whichever retrieval layer produced the transcript.

**Why did I get `captions_unreachable` or `residential_proxy_required` for a video I can see captions on?**
Your network was refused, not the video. `residential_proxy_required` means the run had no residential lane to retry through, so add Apify Proxy with the `RESIDENTIAL` group. Read "Residential egress and bandwidth" above.

**How much residential bandwidth will a big run use?**
About 44 KB per video, so roughly 4.3 MB per 100 videos. `residentialBudgetMb` caps it per run and defaults to 200 MB, which covers around 4,600 videos.

***

Built by Omar Nagy. Part of an agent-native data-tool series on Apify.

# Actor input Schema

## `videos` (type: `array`):

Video URLs or 11-character video ids, one per line. Watch links, youtu.be links, Shorts, embed and live links all work. Required in every mode. Duplicates are removed and the original order is kept. An entry that is not a YouTube video reference fails the run with a readable message instead of being silently skipped.

## `mode` (type: `string`):

chunks = full transcript plus token-bounded chunks. transcript = full transcript only, no chunks. languages = list the caption languages a video has, without downloading any transcript (this mode is not charged). Any other value is rejected as a bad request.

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

Caption languages to try, in order. Each code is matched exactly first, then by prefix, so "en" also accepts "en-GB". The first code that the video actually has wins, and the output records which codes were tried. Ignored when mode = languages.

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

On: YouTube's speech-recognition captions count as a valid track. Off: only human-written caption tracks are accepted, and a video that has nothing else is reported as an error record rather than returning machine text you did not ask for. Ignored when mode = languages.

## `allowAnyLanguage` (type: `boolean`):

On: when none of the codes in the fallback chain is available, take the video's first caption track anyway. Off (default): report the video as an error listing the languages it does have. Ignored when mode = languages.

## `chunkTokens` (type: `integer`):

Maximum tokens per chunk, counted with the cl100k\_base encoding used by OpenAI's text-embedding-3 and GPT-4 class models. Only used when mode = chunks. A chunk never exceeds this number.

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

Tokens repeated from the end of one chunk at the start of the next, so a sentence split across a boundary still retrieves. Must be smaller than the chunk size. Set 0 for no overlap. Only used when mode = chunks.

## `includeFullTranscript` (type: `boolean`):

On: the one video record per video also carries the whole transcript as plain text. Turn it off when you only want the chunks and do not want the transcript duplicated in the dataset. Always on when mode = transcript.

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

Safety cap. Videos beyond this count are dropped from the list before any request is made.

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

YouTube refuses caption metadata to datacenter IPs, including Apify's, so the one request that reads a video's caption list goes through a residential exit. Leave this on the RESIDENTIAL group unless you know your egress is not gated. The caption text itself is downloaded without the proxy, so a run spends about 44 KB of residential bandwidth per video, not the whole transcript.

## `residentialBudgetMb` (type: `integer`):

Hard ceiling on residential proxy traffic for this run. When it is reached the run stops cleanly: videos already done keep their records, every remaining video gets an explicit budget\_exceeded record, and nothing further is charged. Measured at about 0.043 MB per video, so the default covers roughly 4,600 videos.

## `debugConnectivity` (type: `boolean`):

Operator flag. Logs each caption-discovery attempt with its HTTP status and the playability status YouTube reported, so a blocked network can be diagnosed from the run log. Does not change the output records.

## Actor input object example

```json
{
  "videos": [
    "https://www.youtube.com/watch?v=aircAruvnKk"
  ],
  "mode": "chunks",
  "languages": [
    "en",
    "de",
    "ar"
  ],
  "allowAutoGenerated": true,
  "allowAnyLanguage": false,
  "chunkTokens": 400,
  "chunkOverlapTokens": 40,
  "includeFullTranscript": true,
  "maxVideos": 50,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "residentialBudgetMb": 200,
  "debugConnectivity": false
}
```

# Actor output Schema

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

No description

# 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 = {
    "videos": [
        "https://www.youtube.com/watch?v=aircAruvnKk",
        "https://www.youtube.com/watch?v=kCc8FmEb1nY",
        "https://www.youtube.com/watch?v=jNQXAC9IVRw"
    ],
    "mode": "chunks",
    "languages": [
        "en"
    ],
    "allowAutoGenerated": true,
    "allowAnyLanguage": false,
    "chunkTokens": 400,
    "chunkOverlapTokens": 40,
    "includeFullTranscript": true,
    "maxVideos": 50,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    },
    "residentialBudgetMb": 200
};

// Run the Actor and wait for it to finish
const run = await client.actor("omargnagy/youtube-transcripts-for-rag").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 = {
    "videos": [
        "https://www.youtube.com/watch?v=aircAruvnKk",
        "https://www.youtube.com/watch?v=kCc8FmEb1nY",
        "https://www.youtube.com/watch?v=jNQXAC9IVRw",
    ],
    "mode": "chunks",
    "languages": ["en"],
    "allowAutoGenerated": True,
    "allowAnyLanguage": False,
    "chunkTokens": 400,
    "chunkOverlapTokens": 40,
    "includeFullTranscript": True,
    "maxVideos": 50,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
    "residentialBudgetMb": 200,
}

# Run the Actor and wait for it to finish
run = client.actor("omargnagy/youtube-transcripts-for-rag").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 '{
  "videos": [
    "https://www.youtube.com/watch?v=aircAruvnKk",
    "https://www.youtube.com/watch?v=kCc8FmEb1nY",
    "https://www.youtube.com/watch?v=jNQXAC9IVRw"
  ],
  "mode": "chunks",
  "languages": [
    "en"
  ],
  "allowAutoGenerated": true,
  "allowAnyLanguage": false,
  "chunkTokens": 400,
  "chunkOverlapTokens": 40,
  "includeFullTranscript": true,
  "maxVideos": 50,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "residentialBudgetMb": 200
}' |
apify call omargnagy/youtube-transcripts-for-rag --silent --output-dataset

```

## MCP server setup

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

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/89PLgH8A7PxSxh5gV/builds/F0UydJ8zhOnbVNWes/openapi.json
