# YouTube Transcript API — Captions to Text, SRT & VTT (`insight.solutions/youtube-transcript-api`) Actor

Get the transcript of any YouTube video as timed segments, plain text, SRT or VTT. Picks your preferred language, falls back to auto-generated captions and can machine-translate. Returns title, channel, duration and every available language. Residential proxy included, no API key, no login.

- **URL**: https://apify.com/insight.solutions/youtube-transcript-api.md
- **Developed by:** [Insight Solutions](https://apify.com/insight.solutions) (community)
- **Categories:** Videos, AI, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.40 / 1,000 transcript returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## YouTube Transcript API — Captions to Text, SRT & VTT

**Get the transcript of any YouTube video, as data.** Give this Actor a list of YouTube links — or bare video IDs — and get back the spoken words as timed segments, plain text, SubRip (`.srt`) or WebVTT (`.vtt`), alongside the video's title, channel, duration, publish date and every caption language it offers.

No API key. No login. No cookies to paste. **$4 per 1,000 transcripts, residential proxy included**, videos without captions are free, and a run that returns no transcripts costs nothing at all.

### Try it in 30 seconds

```json
{
  "videoUrls": [
    "https://www.youtube.com/watch?v=iG9CE55wbtY",
    "EJXTZ5jpSmk"
  ],
  "languages": ["en"],
  "format": "segments"
}
```

One watch URL, one bare video ID — both work, and so do `youtu.be` share links, Shorts, embeds and live URLs.

### What comes back

```jsonc
{
  "ok": true,
  "videoId": "iG9CE55wbtY",
  "url": "https://www.youtube.com/watch?v=iG9CE55wbtY",
  "title": "Do schools kill creativity? | Sir Ken Robinson | TED",
  "channelName": "TED",
  "channelId": "UCAuUUnT6oDeKwE6v1NGQxug",
  "durationSec": 1164,
  "publishedAt": "2007-01-06T00:00:00.000Z",

  "language": "en",                  // what you actually got
  "languageName": "English",
  "isAutoGenerated": false,          // human-written caption file, not speech recognition
  "isTranslated": false,             // not machine-translated from another language
  "languageFallback": false,         // true only when none of your languages could be delivered
  "availableLanguages": [            // every language this video offers, translations included
    { "languageCode": "en", "languageName": "English", "isAutoGenerated": false, "isTranslatable": true, "isTranslation": false },
    { "languageCode": "es", "languageName": "Spanish", "isAutoGenerated": false, "isTranslatable": true, "isTranslation": false }
  ],

  "segments": [                      // timings in seconds
    { "start": 12.32, "duration": 4.08, "text": "Good morning. How are you?" },
    { "start": 16.4,  "duration": 3.52, "text": "It's been great, hasn't it?" }
  ],
  "text": "Good morning. How are you? It's been great, hasn't it? …",
  "wordCount": 2543,
  "charCount": 14208,

  "retrievedVia": "innertube-android",
  "scrapedAt": "2026-09-09T11:04:22.311Z",
  "source": "youtube.com",
  "sourceUrl": "https://www.youtube.com/api/timedtext?v=iG9CE55wbtY&lang=en&fmt=json3"
}
```

Ask for `"format": "srt"` or `"format": "vtt"` and the row gains an `srt` or `vtt` field holding a complete, valid subtitle file — numbered cues and `HH:MM:SS,mmm` for SubRip, a `WEBVTT` header and `HH:MM:SS.mmm` for WebVTT, with overlapping cues clamped so a player never doubles a line. Ask for `"format": "text"` and you get the prose without the segment array.

### Use cases

- **Summarise or search long-form video** — pull `text` straight into an LLM prompt, with `wordCount` there so you can size the prompt before you send it.
- **Build a searchable video archive** — one row per video, timings intact, so a search hit can link to `youtube.com/watch?v=ID&t=42s`.
- **Repurpose a channel's back catalogue** into articles, show notes or newsletters.
- **Ship subtitles** — `srt` and `vtt` come out ready to attach to a re-upload or a player.
- **Feed a research or RAG pipeline** — segments carry timestamps, so a citation can point at the second the claim was made.
- **Translate a talk** — ask for a language the video does not have and YouTube's own machine translation fills it in, flagged as `isTranslated`.

### How it works, and why it keeps working

YouTube has no public captions API. Everything that reads transcripts reads the same private endpoint the YouTube apps use — and over 2025 and 2026 that endpoint has been progressively fenced off. Single-path scrapers break every few months. This Actor runs a **chain of five paths** and takes the first that answers:

| Rung | What it is | Why it is on the list |
|---|---|---|
| 1 | InnerTube `player`, **Android** client | Loosest validation of the four clients, and the fastest path |
| 2 | InnerTube `player`, **Android VR** client | The client that is not subject to the proof-of-origin token requirement — the answer when rung 1 returns a token-gated caption URL |
| 3 | InnerTube `player`, **iOS** client | A third policy bucket; occasionally the only one serving a given video |
| 4 | InnerTube `player`, **Web** client | The browser context. Most likely to be token-gated, so it goes last |
| 5 | **Watch page** `ytInitialPlayerResponse` | The same document scraped from HTML. Slow, but it is an ordinary page load and survives some API-level refusals |

Under all five: **Apify residential proxy**, one pinned session per parallel worker. When YouTube refuses an exit IP — HTTP 429, HTTP 403, or a player response saying "sign in to confirm you're not a bot" — that session is retired and the rung is retried once from a different residential address. Retrying on an address that was just refused only deepens the block, so it is never done.

The `retrievedVia` field on every row says which rung produced it. If you are monitoring this Actor, that column is the early warning: a shift from `innertube-android` to the later rungs means the front of the chain is degrading, well before anything actually fails.

### How it compares

- **Five fallback paths, not one.** Most transcript scrapers implement a single client context and break the week YouTube changes it.
- **Residential proxy is in the price.** YouTube blocks datacenter IPs wholesale, so a transcript scraper without residential egress does not work from a cloud host at all. The proxy is configured by default and the cost is inside the $4/1,000 — you are not billed for proxy traffic on top.
- **Failures are free and legible.** A video with no captions, a private video or a blocked fetch produces a diagnostic row with an `errorType` you can branch on — and no charge. A run that returns nothing at all finishes **FAILED** with the reason in its status message, never a green run containing an apology.
- **Language handling that says what it did.** You get `language`, `isAutoGenerated`, `isTranslated` and the full `availableLanguages` list, so a fallback is never silent.

### Input reference

| Field | Type | Default | What it does |
|---|---|---|---|
| `videoUrls` **(required)** | array of strings | prefilled with two captioned videos | Watch URLs, `youtu.be` links, Shorts, embed or live URLs, or bare 11-character video IDs. Duplicates are fetched, and billed, once |
| `languages` | array of strings | `["en"]` | Language codes in order of preference. See the fallback order below |
| `format` | `segments` | `text` | `srt` | `vtt` | `segments` | The primary representation. `srt` and `vtt` are only added to the row when you ask for them |
| `includeTimestamps` | boolean | `true` | Keep the `segments` array. Turn off for a much smaller row |
| `includeMetadata` | boolean | `true` | Fill in title, channel, duration and publish date. Costs no extra request |
| `maxConcurrency` | integer | `4` | Videos in parallel. Each worker keeps its own proxy session |
| `videoTimeoutSecs` | integer | `30` | The whole budget for one video, across every rung, retry and the caption download. Any single request is additionally capped at 15 s |
| `maxRunSecs` | integer | `240` | Whole-run wall-clock budget. When it runs out the Actor keeps what it has and files a free diagnostic row for each video it never reached |
| `proxyConfiguration` | object | Apify residential | Leave it alone. Clearing it sends requests from the run's datacenter address, which YouTube will block |

**How `languages` resolves**, in order — and the row always says which step won:

1. a **human-written** track in one of your languages (exact code first, then the base language: `en` accepts `en-GB`);
2. an **auto-generated** track in one of your languages;
3. a **machine translation** into one of your languages — the translation is always attempted when the track is translatable, even if YouTube's response did not list your language as a target;
4. any human-written track the video has;
5. any auto-generated track.

### Output reference

Every row carries the same keys. `ok: true` is a transcript; `ok: false` is a free diagnostic row.

| Field | What it is |
|---|---|
| `videoId`, `url`, `input` | The video, its canonical watch URL, and the entry you supplied |
| `title`, `channelName`, `channelId`, `durationSec`, `publishedAt` | Video metadata, when `includeMetadata` is on |
| `language`, `languageName` | The language you actually got |
| `isAutoGenerated` | `true` when the text came from speech recognition rather than a human-written caption file |
| `isTranslated` | `true` when YouTube machine-translated an existing track for you |
| `languageFallback` | `true` when none of your `languages` could be delivered, so `language` is something else |
| `availableLanguages[]` | Every language the video offers, machine-translation targets included |
| `segments[]` | `{ start, duration, text }` in seconds |
| `text` | Every segment joined with single spaces |
| `srt` / `vtt` | A complete subtitle file — present only when that `format` was requested |
| `wordCount`, `charCount` | Size of `text` |
| `ok`, `error`, `errorType` | Whether this row is a transcript, and if not, why not |
| `retrievedVia` | Which rung of the fallback chain produced it |
| `scrapedAt`, `source`, `sourceUrl` | When, from where. The caption URL's expiring signature is stripped before it is written |

`errorType` on a diagnostic row is one of:

| Value | Meaning | Charged? |
|---|---|---|
| `no-captions` | The video has no caption track at all | No |
| `private` | Private, members-only or age-restricted, so a signed-in account would be needed. This Actor never signs in | No |
| `unavailable` | Deleted, region-locked or otherwise unplayable | No |
| `blocked` | YouTube refused our requests, including when the caption file is gated behind a proof-of-origin token | No |
| `invalid-id` | The entry was not a YouTube video — a channel, a playlist, or something else | No |
| `timeout` | The run's `maxRunSecs` budget ran out before this video was reached | No |

### Pricing

**$4.00 per 1,000 transcripts.** Pay-per-event, with the residential proxy already inside that number — there is no separate proxy line on your bill for this Actor.

| Event | What triggers it | FREE | Starter | Scale | Business |
|---|---|---|---|---|---|
| **Transcript returned** *(primary)* | One transcript written to your dataset | $0.004 | $0.004 | $0.0032 | $0.0024 |
| Run started | Once per run, after the first transcript | $0.001 | $0.001 | $0.001 | $0.001 |

**Worked example.** 250 videos, of which 240 have captions:

- 240 transcripts × $0.004 = **$0.96**
- 1 run start = **$0.001**
- 10 videos without captions = **$0.00**
- **Total: $0.961**

What you are never charged for: a video with no captions, a private or deleted video, an entry that was not a video, a video the run never reached before `maxRunSecs`, or a fetch YouTube blocked. If a whole run comes back empty it finishes FAILED and bills **nothing at all**, start fee included.

Set `ACTOR_MAX_TOTAL_CHARGE_USD` on a run and the Actor stops fetching once the ceiling is in sight, rather than handing you rows it cannot bill or billing you for rows it cannot hand over.

### Limits, and the one that might bite

**Videos without captions.** Plenty of YouTube has no caption track at all — short clips, music videos, some live streams, and anything an uploader has explicitly opted out of. There is nothing to fetch; you get a free `no-captions` row saying so.

**Age-restricted, private and members-only videos.** These need a signed-in account. This Actor does not log in, does not accept cookies and does not take a session token, and it never will — that is a deliberate line, not a missing feature. They return a free `private` row.

**Proof-of-origin ("PO") tokens — the real risk.** Through 2025 and 2026 YouTube has been rolling out an experiment that marks a caption URL with `exp=xpe`, and the caption endpoint then answers **HTTP 200 with an empty body** unless the request carries a token minted by YouTube's own player JavaScript. There is no way to produce that token without running YouTube's JavaScript in a browser.

What this Actor does about it:

- it **detects** the marker on the caption URL before spending a request on it;
- it **moves to the next client** in the chain, because the clients are gated independently — an Android VR caption URL is routinely clean when the Android or Web one is not. This is the single reason rung 2 exists;
- if every rung is gated, it writes a free `blocked` diagnostic row whose `error` says exactly that, rather than an empty transcript or a silent success.

Today the chain gets past this for the overwhelming majority of videos. If YouTube ever extends the token requirement to *every* client context, transcripts would stop being retrievable by any means short of a full browser — for this Actor and for every other tool of its kind. We would say so on this page rather than quietly returning worse data, and the run would still cost you nothing.

**Rate and reliability.** Requests go out through residential addresses with per-worker sessions and one rotation per block. Four videos in parallel is the default because it is where throughput and block rate balance; raising `maxConcurrency` speeds a long list up and makes blocks more likely.

### Use it from an AI agent, or from code

One JSON object in, one flat array out — the shape agent runtimes want. The Actor runs with **limited permissions**, uses **pay-per-event** pricing and never enters Standby, so it works over the Apify MCP server and with x402 agentic payments. The **Integrations** tab pushes results to Slack, a webhook, Zapier, Make, Google Sheets, Snowflake or BigQuery.

```bash
curl -X POST "https://api.apify.com/v2/acts/insight.solutions~youtube-transcript-api/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"videoUrls":["iG9CE55wbtY"],"format":"text"}'
```

```python
## pip install apify-client
from apify_client import ApifyClient

client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("insight.solutions/youtube-transcript-api").call(run_input={
    "videoUrls": [
        "https://www.youtube.com/watch?v=iG9CE55wbtY",
        "https://youtu.be/EJXTZ5jpSmk",
    ],
    "languages": ["en"],
    "format": "segments",
})

for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    if row.get("ok"):
        print(row["title"], row["wordCount"], "words", sep=" | ")
    else:
        print("skipped:", row["input"], row["errorType"])
```

Set `"format": "text"` and `"includeTimestamps": false` when you only need the words — the payload shrinks by roughly half and costs the same.

### FAQ

**Does this work on any video?**
Any public video that has a caption track. Videos with captions turned off, and private, members-only or age-restricted videos, return a free diagnostic row instead.

**Are auto-generated captions any good?**
They are usable and they are clearly labelled — `isAutoGenerated: true`. Expect almost no punctuation, sentence boundaries you cannot trust, and mangled proper nouns. A human-written track is always preferred when the video has one.

**Can I get a language the video does not have?**
Often, yes. If any track is translatable, the Actor asks YouTube for your language and you get a machine translation, flagged `isTranslated: true`. If YouTube serves nothing for that language, you get the original track and the row says so (`languageFallback: true`). The quality is YouTube's machine translation, not ours.

**Why do I sometimes get a different language than I asked for?**
Because the video did not offer yours and could not translate into it. The row still tells you exactly what you got — `language`, plus the full `availableLanguages` list, so you can decide what to do next.

**Do I need my own proxy?**
No. Apify residential proxy is configured by default and its cost is inside the per-transcript price.

**Will it handle a playlist or a whole channel?**
Not yet — this Actor takes videos, one row each. Feed it a list of video URLs from wherever your list comes from.

**What happens if one video fails?**
The others still run. The failed one produces a free diagnostic row and the run finishes `SUCCEEDED`. If *every* video fails the run finishes `FAILED` and you are billed nothing at all.

**How long can a run take?**
`maxRunSecs`, 240 seconds by default, checked before each video. When it runs out the Actor stops fetching, keeps and bills for everything already collected, files a free `timeout` row for each video it never reached, and finishes.

**Is the data fresh?**
Live. Every run reads YouTube at that moment; nothing is cached.

### Legal and data-protection notes

- **Public videos only.** Every source is a public video page or the public caption endpoint behind it. The Actor never logs in, never accepts cookies or session tokens, never takes an API key belonging to anyone else, and never touches private, members-only or age-restricted content.
- **Captions are the uploader's copyright.** This Actor retrieves them; it does not license them. Republishing a transcript, or training on it, is your call and your responsibility, subject to YouTube's terms and to copyright law where you operate. Quoting, summarising, searching and indexing are the ordinary uses and are what this is built for.
- **Expiring credentials are stripped.** The caption URL YouTube signs is a short-lived bearer credential. `sourceUrl` records where the transcript came from with that signature removed.
- **No personal data by design.** A transcript is the spoken content of a public video. The Actor stores no viewer data, no account data and no cookies, and it writes only the video's own public metadata alongside the text.
- **Not affiliated with YouTube, Google LLC, or with any channel or creator whose video you retrieve.** All product names and trademarks belong to their respective owners and are used only to describe which public endpoints this Actor reads.

### Our other Actors

Every Insight Solutions Actor is pay-per-result with no browser, no login and no API key, and every one of them returns free diagnostic rows instead of billing for failures. Prices are per 1,000 results.

**Video, audio & social**

- [YouTube Comments API](https://apify.com/insight.solutions/youtube-comments-api) — comments and replies with likes, pinned and hearted flags, newest or top sort.
- [YouTube Channel API](https://apify.com/insight.solutions/youtube-channel-api) — a channel's videos, Shorts and live streams, plus YouTube search.
- [Podcast Search, Episodes & Charts API](https://apify.com/insight.solutions/podcast-api) — Apple Podcasts search, charts and full episode feeds.
- [Bluesky Scraper](https://apify.com/insight.solutions/bluesky-scraper) — profiles, posts, followers and follows from the public AT Protocol API.
- [Telegram Channel Scraper](https://apify.com/insight.solutions/telegram-channel-scraper) — posts, views and channel stats from public Telegram channels.
- [Substack Scraper](https://apify.com/insight.solutions/substack-scraper) — posts with full free text, comments and publication profiles.

**News, documents & the web**

- [Google News Search, Topics & Real Article URLs](https://apify.com/insight.solutions/google-news-api) — news search and topic feeds with the publisher's real URL decoded.
- [Website to Markdown — Content Extractor for LLMs & RAG](https://apify.com/insight.solutions/website-content-extractor) — any site as clean Markdown, text and heading-aware chunks.
- [Internet Archive API](https://apify.com/insight.solutions/internet-archive-api) — archive.org search, item metadata, files and reviews.
- [Wayback Machine Toolkit](https://apify.com/insight.solutions/wayback-toolkit) — archived URL inventories, snapshots and text diffs between dates.
- [Website Technology Detector](https://apify.com/insight.solutions/website-tech-detector) — the tech stack behind any site, with the evidence for each detection.
- [Domain Intelligence API](https://apify.com/insight.solutions/domain-intelligence-api) — DNS, RDAP registration, TLS certificate and HTTP facts in one row per domain.
- [SEO Page Audit](https://apify.com/insight.solutions/seo-page-audit) — sitemap crawl with on-page checks, structured data and broken-link reports.
- [Keyword Suggestions API](https://apify.com/insight.solutions/keyword-suggestions-api) — Google, YouTube, Bing, Amazon and eBay autocomplete with alphabet and question expansions.
- [Website Contact Extractor](https://apify.com/insight.solutions/website-contact-extractor) — emails, phone numbers and social profiles from any list of websites.

**Business, finance & jobs**

- [Congress & Insider Trades API](https://apify.com/insight.solutions/congress-insider-trades-api) — STOCK Act periodic transaction reports and SEC Form 4 insider trades in one schema.
- [SEC EDGAR API](https://apify.com/insight.solutions/sec-edgar-api) — filings, XBRL financials and full-text search by ticker or CIK.
- [Y Combinator Companies, Batches & Founders](https://apify.com/insight.solutions/yc-companies-directory) — the YC directory with founders and social links, filterable by batch, industry and hiring status.
- [Career Site Jobs API](https://apify.com/insight.solutions/ats-jobs-api) — jobs straight from Greenhouse, Lever, Ashby, Workable and 10+ other ATS career sites.
- [New Job Postings Monitor](https://apify.com/insight.solutions/job-postings-monitor) — new, closed and changed postings on the career sites you watch.
- [Shopify Products API](https://apify.com/insight.solutions/shopify-products-api) — any Shopify store's catalogue, variants, prices and stock signals.

**Apps & games**

- [App Store & Google Play Reviews API](https://apify.com/insight.solutions/app-reviews-api) — reviews from both stores with ratings, versions and developer replies.
- [App Store Top Charts & App Search API](https://apify.com/insight.solutions/app-charts-api) — Apple top charts by country and genre, plus app search and details.
- [Steam Reviews API](https://apify.com/insight.solutions/steam-reviews-api) — Steam reviews with playtime, helpfulness and game details.
- [Steam Game Data API](https://apify.com/insight.solutions/steam-store-stats-api) — prices, tags, review scores, live player counts and top charts.

# Actor input Schema

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

One entry per video. Any YouTube link works — a watch URL, a youtu.be share link, a Shorts, embed or live URL, with or without a playlist or timestamp glued on — and so does a bare 11-character video ID. Channel and playlist URLs are rejected, because they are not one video. Duplicates are fetched, and billed, once.

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

Language codes in order of preference, most wanted first, e.g. \["en"] or \["de","en"]. A human-written track in one of these languages wins; failing that an auto-generated one; failing that a machine translation into one of them, when YouTube offers it. If none of that is possible you still get a transcript — whatever language the video actually has — and the row says which. Every language the video offers is listed in `availableLanguages`, so a fallback is never a mystery.

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

How the transcript is delivered. segments = a `segments` array of {start, duration, text} plus the joined `text`. text = the joined text only, for feeding a model or a search index. srt = a SubRip subtitle file in an `srt` field. vtt = a WebVTT file in a `vtt` field. srt and vtt also carry `text`, and are only added to the row when you pick them — a long talk renders to about 100 KB in each.

## `includeTimestamps` (type: `boolean`):

Keep the `segments` array of {start, duration, text}. Turn it off to get only the joined `text` and a much smaller row. It has no effect on the SRT and VTT formats, which carry their timings by definition, and none on the `text` format, which has no segments to begin with.

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

Fill in `title`, `channelName`, `channelId`, `durationSec` and `publishedAt` from the same response the captions come from. It costs no extra request, so leave it on unless you want the narrowest possible rows.

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

How many videos are fetched at once. Each parallel worker keeps its own residential proxy session, so a video that gets blocked burns only its own exit IP. Raising this makes a long list finish sooner and makes a block more likely; four is the balance that holds up in practice.

## `videoTimeoutSecs` (type: `integer`):

The whole budget for one video, spent across every attempt it needs: up to four client contexts, a watch-page fallback, a proxy-session rotation and the caption download. When it runs out the video gets a free `timeout` diagnostic row and the run moves on. Any single request is additionally capped at 15 seconds, so one wedged connection cannot spend the lot.

## `maxRunSecs` (type: `integer`):

Wall-clock budget for the whole run. When it is reached the Actor stops fetching, keeps every transcript it already wrote, and writes a free diagnostic row for each video it never reached. Rows already written are still charged; nothing is charged for work that did not happen.

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

YouTube blocks datacenter IP ranges wholesale, which is why this Actor is built around Apify's residential proxy and why the proxy cost is already inside the per-transcript price. Leave this on the default. Sessions rotate automatically when an exit IP is refused. Clearing it sends requests from the run's own datacenter address, which YouTube will almost certainly block.

## Actor input object example

```json
{
  "videoUrls": [
    "https://youtu.be/iG9CE55wbtY",
    "EJXTZ5jpSmk"
  ],
  "languages": [
    "en"
  ],
  "format": "segments",
  "includeTimestamps": true,
  "includeMetadata": true,
  "maxConcurrency": 4,
  "videoTimeoutSecs": 30,
  "maxRunSecs": 240,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

One row per video: the transcript in the format you asked for, plus the video's title, channel, duration and every caption language it offers. Videos that could not be read get a free diagnostic row saying why. Delivered as JSON items in the default dataset.

# 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=iG9CE55wbtY",
        "EJXTZ5jpSmk"
    ],
    "languages": [
        "en"
    ],
    "format": "segments",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("insight.solutions/youtube-transcript-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 = {
    "videoUrls": [
        "https://www.youtube.com/watch?v=iG9CE55wbtY",
        "EJXTZ5jpSmk",
    ],
    "languages": ["en"],
    "format": "segments",
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("insight.solutions/youtube-transcript-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 '{
  "videoUrls": [
    "https://www.youtube.com/watch?v=iG9CE55wbtY",
    "EJXTZ5jpSmk"
  ],
  "languages": [
    "en"
  ],
  "format": "segments",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call insight.solutions/youtube-transcript-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,insight.solutions/youtube-transcript-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/53XUiaJm7tXoHh7nL/builds/2dPTM9qnmgfdalTJt/openapi.json
