# YouTube Transcript & Subtitle Scraper (`abotapi/youtube-transcript-scraper`) Actor

Extract transcripts and subtitles from YouTube videos in bulk using video, playlist, channel URLs, or keyword search. Returns timed transcript segments, plain text, SRT, and WebVTT subtitle files, with optional auto-translation to other languages.

- **URL**: https://apify.com/abotapi/youtube-transcript-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** Videos, Developer tools, AI
- **Stats:** 55 total users, 9 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 transcript fetcheds

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 & Subtitles Scraper

Pull transcripts and subtitles for YouTube videos in bulk. Give it video URLs, playlist URLs, channel URLs, or just keywords to search YouTube, and it returns each video's transcript as timed segments, as plain text, and as ready-to-use SRT and WebVTT subtitle files. It can also auto-translate transcripts into another language.

### Why this scraper

- Four ways in: direct video URLs or IDs, playlist URLs, channel URLs (`@handle`, `/channel/UC...`, `/c/...`, `/user/...`), and keyword searches, all in one run.
- Every output record carries the transcript three ways: timed `segments`, a single plain-text `transcript` field, and `srt` plus `vtt` strings you can save straight to disk.
- Language control: list your preferred languages in priority order, or translate the result into any language YouTube supports.
- Handles both human-written captions and auto-generated ones, and falls back to whatever the video offers when your preferred language is not available.
- Concurrent fetching with automatic connection rotation, so large playlists and channels move quickly.
- Per-source time windows: trim each video's transcript to a specific second range (and only pay for the trimmed text).
- Pay only for what you get: failed, captions-disabled, or incremental-mode-suppressed videos are not billed.
- Resume an interrupted large pull, or schedule the actor to run daily/weekly and get only what's NEW, UPDATED, REAPPEARED, or EXPIRED since last time.

### Data you get

Per video (one dataset record). Values below are illustrative placeholders, not from a live video.

| Field | Example |
|---|---|
| `videoId` | `EXAMPLE_ID1` |
| `videoUrl` | `https://www.youtube.com/watch?v=EXAMPLE_ID1` |
| `videoTitle` | `Sample Video Title` |
| `channelName` | `Sample Channel` |
| `channelUrl` | `https://www.youtube.com/@SampleChannel` |
| `durationSeconds` | `213` |
| `source` | `python tutorial` |
| `sourceType` | `search` (one of `video`, `playlist`, `channel`, `search`) |
| `language` | `English` |
| `languageCode` | `en` |
| `isGenerated` | `false` |
| `isTranslated` | `false` |
| `translatedTo` | `null` |
| `charCount` | `5234` |
| `segmentCount` | `142` |
| `transcript` | `"Hello and welcome to this sample transcript ..."` |
| `segments` | `[{ "text": "Hello and welcome", "start": 0.0, "duration": 1.84 }, ...]` |
| `srt` | `"1\n00:00:00,000 --> 00:00:01,840\nHello and welcome\n\n2\n..."` |
| `vtt` | `"WEBVTT\n\n00:00:00.000 --> 00:00:01.840\nHello and welcome\n\n..."` |
| `trimmedStart` | `0` (start of the time window applied; `0` = no trim) |
| `trimmedDuration` | `0` (length of the time window applied; `0` = no trim) |
| `success` | `true` |
| `error` | `null` (a short message when a transcript could not be fetched) |
| `fetchedAt` | `2026-07-30T08:13:20.560058+00:00` (when this row was fetched, ISO 8601 UTC) |
| `changeType` | `NEW` (incremental mode only — `NEW` | `UPDATED` | `UNCHANGED` | `REAPPEARED` | `EXPIRED`) |
| `changedFields` | `[]` (incremental mode only — field names that changed since the last run, on `UPDATED` rows) |
| `firstSeenAt` | `2026-07-30T08:13:20Z` (incremental mode only) |
| `lastSeenAt` | `2026-07-30T08:13:20Z` (incremental mode only) |

`changeType`/`changedFields`/`firstSeenAt`/`lastSeenAt` are only added when `incrementalMode` is on; a normal run's rows keep the shape above without them.

### How to use

Pick a `mode`:

- `mode: "url"` reads `videoUrls` (paste video, playlist, or channel URLs).
- `mode: "search"` reads `searchQueries` (find videos by keyword).

The other field is ignored. Optionally set `startSec` and `durationSec` to trim every video's transcript to the same time window (`durationSec: 0` means "until end of video").

Fetch one video's transcript:

```json
{
  "mode": "url",
  "videoUrls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"],
  "languages": ["en"],
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

Several videos at once (URLs or bare IDs both work):

```json
{
  "mode": "url",
  "videoUrls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "https://youtu.be/dQw4w9WgXcQ",
    "dQw4w9WgXcQ"
  ],
  "languages": ["en", "en-US"],
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

Search YouTube by keyword and transcribe the top results:

```json
{
  "mode": "search",
  "searchQueries": ["langgraph tutorial", "apify actor development"],
  "maxVideosPerSource": 5,
  "languages": ["en"],
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

Expand a playlist and a channel, capped per source, and translate everything to English:

```json
{
  "mode": "url",
  "videoUrls": [
    "https://www.youtube.com/playlist?list=PLxxxxxxxxxxxxxxxx",
    "https://www.youtube.com/@SomeChannel"
  ],
  "maxVideosPerSource": 25,
  "maxVideos": 100,
  "languages": ["en"],
  "translateToLanguage": "en",
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

Trim every video's transcript to seconds 30 to 60:

```json
{
  "mode": "url",
  "videoUrls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"],
  "startSec": 30,
  "durationSec": 30,
  "languages": ["en"],
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

The trimmed `transcript`, `segments`, `srt`, and `vtt` only contain snippets inside the window, and the `transcript` billing event counts only the trimmed characters.

### Resume & recurring updates

Two different, complementary things:

- **`resumeFromRunId`** continues **one** interrupted crawl. Paste a previous run ID or dataset ID; this run skips videos it already collected there instead of re-fetching (and re-billing) them.
- **`incrementalMode`** is for scheduling this actor on the **same** `videoUrls`/`searchQueries` setup again and again (daily, weekly) and getting only what changed. The actor remembers its own previous run of that setup, keyed automatically from mode + URLs/queries + language settings (or a `stateKey` you name yourself). Every video is classified `NEW`, `UPDATED`, `UNCHANGED`, `REAPPEARED`, or `EXPIRED`; by default only `NEW`/`UPDATED`/`REAPPEARED` are returned (and billed) — turn on `emitUnchanged` or `emitExpired` to also get those.

Schedule daily monitoring of a search, returning only what changed:

```json
{
  "mode": "search",
  "searchQueries": ["langgraph tutorial"],
  "maxVideosPerSource": 20,
  "incrementalMode": true,
  "languages": ["en"],
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

Continue a large playlist pull that got interrupted:

```json
{
  "mode": "url",
  "videoUrls": ["https://www.youtube.com/playlist?list=PLxxxxxxxxxxxxxxxx"],
  "resumeFromRunId": "<previous run ID>",
  "maxVideosPerSource": 500,
  "languages": ["en"],
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

Notes:

- `EXPIRED` is only ever produced in `mode: search`, and only once a run genuinely scanned every search query to its end (not capped by `maxVideos`, not a `resumeFromRunId` run, not a search that may have returned more results than `maxVideosPerSource` asked for). In `mode: url` the tracked set is whatever URLs you pasted, so a video missing this run simply wasn't pasted — `EXPIRED` can never fire there.
- An `EXPIRED` row is synthesized from the last known data, not re-fetched, so it is **never billed** the `transcript` event.
- A suppressed `UNCHANGED` video still has to be fetched (to compute its `charCount` fingerprint) but is never pushed or billed — only `NEW`/`UPDATED`/`REAPPEARED` rows (and `UNCHANGED`/`EXPIRED` if you opt in) cost anything.

### Input parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `mode` | string enum | `"url"` | One of `"url"` (read `videoUrls`) or `"search"` (read `searchQueries`). The other field is ignored. |
| `videoUrls` | array of strings | `[]` | Used when `mode = url`. Watch URLs, youtu.be URLs, shorts URLs, playlist URLs, channel URLs (@handle, /channel/, /c/, /user/), or 11-character video IDs. Playlists and channels are expanded to their videos. |
| `searchQueries` | array of strings | `[]` | Used when `mode = search`. Free-form keywords. Top results per query are fetched. |
| `startSec` | integer | `0` | Trim every transcript to start at this second. `0` keeps from the beginning. |
| `durationSec` | integer | `0` | Trim every transcript to this many seconds from `startSec`. `0` keeps until the end of each video. |
| `maxVideosPerSource` | integer | `10` | How many videos to take from each playlist, channel, or search query. |
| `maxVideos` | integer | `0` | Hard cap on the total number of videos across all sources. `0` means no overall cap. |
| `languages` | array of strings | `["en"]` | Preferred transcript language codes in priority order. The first available one is used; if none are available, any transcript the video offers is returned. |
| `translateToLanguage` | string | `""` | Optional language code to translate the transcript into using YouTube's auto-translation. Empty keeps the original language. |
| `preserveFormatting` | boolean | `false` | Keep inline formatting tags (such as italics) in the transcript text instead of stripping them. |
| `resumeFromRunId` | string | `""` | Continue one interrupted run: paste a previous run ID or dataset ID and this run skips videos already collected there. |
| `incrementalMode` | boolean | `false` | Turn on for recurring monitoring of the same setup. See [Resume & recurring updates](#resume--recurring-updates). |
| `emitUnchanged` | boolean | `false` | Incremental mode only. Also return (and bill) videos that haven't changed since the last run, marked `UNCHANGED`. |
| `emitExpired` | boolean | `false` | Incremental mode only, `mode: search` only. Also return videos no longer found, marked `EXPIRED`. Never billed. |
| `stateKey` | string | `""` | Incremental mode only. Optional name for this monitoring campaign's saved state. Leave empty to derive one automatically from the setup. |
| `proxyConfiguration` | object | Apify Proxy, `RESIDENTIAL` | Proxy settings. YouTube blocks most datacenter / cloud IPs from fetching transcripts, so the `RESIDENTIAL` group is strongly recommended. |

### Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape — the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human-readable **summary** of each record — not the full JSON. Each item becomes one entry with a **title** and its key fields flattened to plain text. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify → Settings → Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. (If the picker is empty, you haven't authorized a connector yet.)
3. For **Notion**, also set `notionParentPageUrl` to the page where items should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

### Output example

> Sample shape: values are illustrative placeholders, not from a live video.

```json
{
  "videoId": "EXAMPLE_ID1",
  "videoUrl": "https://www.youtube.com/watch?v=EXAMPLE_ID1",
  "videoTitle": "Sample Video Title",
  "channelName": "Sample Channel",
  "channelUrl": "https://www.youtube.com/@SampleChannel",
  "durationSeconds": 213,
  "source": "https://www.youtube.com/watch?v=EXAMPLE_ID1",
  "sourceType": "video",
  "language": "English",
  "languageCode": "en",
  "isGenerated": false,
  "isTranslated": false,
  "translatedTo": null,
  "charCount": 5234,
  "segmentCount": 142,
  "transcript": "Hello and welcome to this sample transcript.\nThis is the second line of the transcript.\n...",
  "segments": [
    { "text": "Hello and welcome to this sample transcript.", "start": 0.0, "duration": 2.32 },
    { "text": "This is the second line of the transcript.", "start": 2.32, "duration": 2.08 }
  ],
  "srt": "1\n00:00:00,000 --> 00:00:02,320\nHello and welcome to this sample transcript.\n\n2\n00:00:02,320 --> 00:00:04,400\nThis is the second line of the transcript.\n",
  "vtt": "WEBVTT\n\n00:00:00.000 --> 00:00:02.320\nHello and welcome to this sample transcript.\n\n00:00:02.320 --> 00:00:04.400\nThis is the second line of the transcript.\n",
  "success": true,
  "error": null,
  "fetchedAt": "2026-07-30T08:13:20.560058+00:00"
}
```

With `incrementalMode` on, rows also carry `changeType`, `changedFields`, `firstSeenAt`, and `lastSeenAt` — see [Resume & recurring updates](#resume--recurring-updates).

### Billing

You are charged once per run, plus a small amount per keyword search that was successfully resolved (URL inputs are not charged for resolution — and a resolved search is billed even if every video it finds turns out unchanged, since the search itself still ran), plus a length-based unit per fetched transcript. One transcript unit covers roughly 4,000 characters of transcript text, about 1,000 LLM tokens. A short clip is about one unit, a 1-hour podcast around twelve, and a 3-hour video around thirty-five. Videos whose captions are disabled, unavailable, or could not be fetched are not billed. If you trim the transcript with `startSec` and `durationSec`, you only pay for the trimmed text. In `incrementalMode`, a video suppressed as `UNCHANGED` is not billed either, even though it still had to be fetched to confirm nothing changed — and an `EXPIRED` row is never billed, since nothing was re-fetched for it. Exact amounts are shown on this actor's Store page.

### Plan requirement

Works on any Apify plan, but YouTube blocks most datacenter and cloud IP ranges from fetching transcripts. For reliable results use Apify Proxy with the `RESIDENTIAL` group, which is available on the Starter plan and higher. On the free plan, expect many videos to return an error; the run will log a notice and add a record explaining the upgrade path.

# Actor input Schema

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

Pick how to specify videos.

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

One per line. Watch URLs, youtu.be URLs, shorts URLs, playlist URLs, channel URLs (@handle, /channel/UC..., /c/..., /user/...), or 11-character video IDs. Playlists and channels are expanded to their videos.

## `searchQueries` (type: `array`):

One per line. Top results per query (up to 'Max videos per source') are fetched.

## `startSec` (type: `integer`):

Trim each transcript to start at this second. 0 keeps from the beginning of each video.

## `durationSec` (type: `integer`):

Trim each transcript to this many seconds from Start above. 0 keeps until the end of each video.

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

How many videos to take from each playlist, channel, or search query.

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

Hard cap on the total number of videos processed across all sources. 0 means no overall cap.

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

Language codes in priority order, for example \["en", "en-US", "de"]. The first available one is used; if none are available, any transcript YouTube offers is returned.

## `translateToLanguage` (type: `string`):

Optional. A language code (e.g. "en") to translate the transcript into using YouTube's auto-translation. Leave empty to keep the original.

## `preserveFormatting` (type: `boolean`):

Keep inline formatting tags (e.g. italics) in the transcript text instead of stripping them.

## `resumeFromRunId` (type: `string`):

Paste a previous run ID or dataset ID to continue a large pull without re-fetching (or re-billing) videos already collected there. Use this after an interrupted run, or when continuing a large playlist/channel/search in another run. For recurring monitoring of the same videos, use Incremental mode below instead.

## `incrementalMode` (type: `boolean`):

Turn this on for daily or recurring monitoring of the same videoUrls/searchQueries. The first run returns every video as NEW. Later runs normally return only NEW, UPDATED, and REAPPEARED videos. Turn on "Emit unchanged" or "Emit expired" only when you also want those videos returned (and billed). State is kept separately for each mode/URL/query/language setup; use State key when you want to name or deliberately share a monitoring campaign. To continue one specific interrupted run instead, use Resume from a previous run above.

## `emitUnchanged` (type: `boolean`):

Off by default. Turn on to also return videos whose transcript has not changed since the last run, marked UNCHANGED. This returns — and bills — extra rows you already have, so leave it off unless you specifically want the full snapshot every run.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to also return videos that were present in a previous run but no longer appear, marked EXPIRED. Only produced in Keyword search mode, and only once a run has fully scanned every tracked search query — not when Max videos capped it, when Resume was used, or when a search may have been truncated. In URL mode this can never fire: the tracked set is whatever URLs you pasted, so a missing one just wasn't pasted this run, not confirmed gone. EXPIRED rows are synthesized from the last known data, so they are never re-billed. Leave off unless you need expiry tracking.

## `stateKey` (type: `string`):

Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently-configured runs. Leave empty to let the actor derive a key automatically from the mode/URLs/queries/language settings — different setups then never mix state with each other.

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

YouTube blocks most datacenter / cloud IPs from fetching transcripts. The RESIDENTIAL group is strongly recommended; without it many videos return errors. Free Apify plans do not include residential proxy.

## `mcpConnectors` (type: `array`):

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify → Settings → Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON — the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "url",
  "videoUrls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
  ],
  "searchQueries": [
    "python tutorial"
  ],
  "startSec": 0,
  "durationSec": 0,
  "maxVideosPerSource": 10,
  "maxVideos": 0,
  "languages": [
    "en"
  ],
  "translateToLanguage": "",
  "preserveFormatting": false,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "maxNotifyListings": 50
}
```

# Actor output Schema

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

Per-video transcripts: timed segments, plain text, SRT and WebVTT.

# 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 = {
    "mode": "url",
    "videoUrls": [
        "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
    ],
    "searchQueries": [
        "python tutorial"
    ],
    "languages": [
        "en"
    ],
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/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 = {
    "mode": "url",
    "videoUrls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"],
    "searchQueries": ["python tutorial"],
    "languages": ["en"],
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

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

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "mode": "url",
  "videoUrls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
  ],
  "searchQueries": [
    "python tutorial"
  ],
  "languages": [
    "en"
  ],
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call abotapi/youtube-transcript-scraper --silent --output-dataset

```

## MCP server setup

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

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/HKBgqYs8YdfTPMj9p/builds/ReTLZUmgEAU4aNYrX/openapi.json
