# YouTube Transcript Scraper — SRT/VTT + Whisper AI Fallback (`memo23/youtube-transcript-scraper`) Actor

Extract YouTube transcripts as plain text, timestamped JSON, SRT or VTT from videos, Shorts, live replays and whole channels. Opt-in Whisper AI transcribes caption-less videos. Lists every caption track, any language, and bills only new uploads on a schedule. Pay only for delivered transcripts.

- **URL**: https://apify.com/memo23/youtube-transcript-scraper.md
- **Developed by:** [Muhamed Didovic](https://apify.com/memo23) (community)
- **Categories:** AI, Videos, Agents
- **Features:** Limited permissions
- **Stats:** 50 total users, 4 monthly users, 94.4% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $5.00 / 1,000 transcripts

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

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

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## YouTube Transcript Scraper — SRT, VTT, JSON & Plain Text with Whisper AI Fallback

Turn any YouTube video or Short into a clean transcript in one run. Every row carries the full text, timestamped segments, ready-made **SRT and VTT subtitle files**, and the video's core metadata (title, channel, views, duration, description). When a video has **no captions at all**, the optional **Whisper AI fallback** downloads the audio and transcribes it with speech-to-text — something caption-only scrapers simply return empty for.

#### How it works

![How the YouTube Transcript Scraper works](https://raw.githubusercontent.com/muhamed-didovic/muhamed-didovic.github.io/main/assets/how-it-works-youtube-transcript.png)

#### ✨ Why use this scraper?

- **Several ways to the captions** — when one way to a video's captions comes back empty, the next one is tried automatically, and opt-in Whisper AI speech-to-text comes last. A temporary error on one video never costs you the run.
- **Whisper AI for caption-less videos** — the differentiator: videos with no captions still come back transcribed (opt-in, billed as a separate premium event so you never pay it unknowingly).
- **Every format in one row** — plain text for LLM pipelines, timestamped JSON segments for analysis, SRT and VTT for subtitle workflows. No post-processing.
- **Language selection with auto-translate** — request `en`, `de`, `es`… and where the exact track is missing, YouTube's translated track is used when available.
- **Metadata included free** — title, channel and @handle, publish date, view count, duration, keywords, description and thumbnails ride along on every transcript row.
- **Every caption track listed** — `availableCaptionTracks` names each language YouTube has for the video and whether a person wrote it or it is auto-generated, so you can pick the track yourself.
- **Live stream replays work** — paste the `youtube.com/live/…` link as-is. A stream that is still live, or ended so recently that YouTube has not published its captions, is reported as `live_not_ready` and not charged.
- **Bulk-friendly** — paste hundreds of URLs, whole channels or playlists in one run; failed videos are isolated and never billed.
- **Paste the input you already use** — `videoUrl` + `targetLanguage` (pintostudio's input) and `urls: [{ "url": … }]` run unchanged, and every row also carries a `data` array of `start` / `dur` / `text`, the segment shape those pipelines read.
- **Says which track it read** — `transcriptLanguage`, `isAutoGenerated` and `isTranslated` on every row, so you know whether you got a person's captions, YouTube's speech recognition or a translation.
- **The hook on every row** — `hook3s` is the first 3 seconds of speech and `hookStartSeconds` says when the talking starts, so openers compare side by side without reading transcripts.
- **Chain any YouTube scraper** — pass a finished run's `datasetId` (or paste rows in `datasetItems`) and every video or channel link in those rows is transcribed; the row shape does not matter.
- **Scheduled runs bill only what is new** — name a `watchlistId` and switch on `newItemsOnly`; a video transcribed on an earlier run is left out, and a channel walk transcribes only its new uploads.
- **Every miss on the record** — asked = delivered + reported. A video that could not be transcribed is in the run's `ERRORS` record with a reason and what to change, not billed as a row, and the status line reconciles the count.

#### 🎯 Use cases

| Who | What they do with it |
|---|---|
| **AI & LLM builders** | Feed clean plain-text transcripts into RAG pipelines, summarizers, and agents (MCP-friendly output). |
| **Content & SEO teams** | Repurpose videos into articles, show notes, and quote pulls; mine competitor channels for topics. |
| **Researchers & analysts** | Build searchable corpora from talks, interviews and news coverage, with timestamps intact. |
| **Subtitle & localization teams** | Get SRT/VTT straight from the source, plus translated tracks where YouTube offers them. |
| **Media monitoring** | Track what's being said about brands and people across YouTube at scale. |

#### 📥 Supported inputs

| Input | Example |
|---|---|
| Standard video URLs | `https://www.youtube.com/watch?v=dQw4w9WgXcQ` |
| Short links | `https://youtu.be/dQw4w9WgXcQ` |
| Shorts | `https://www.youtube.com/shorts/{id}` |
| Live stream replays | `https://www.youtube.com/live/{id}` |
| Channel | `https://www.youtube.com/@handle` (or `/channel/…`, `/c/…`, `/user/…`, its `/videos` or `/streams` tab) — walked newest first up to `maxItems` |
| Playlist | `https://www.youtube.com/playlist?list=PL…` — every playable entry, in playlist order, up to `maxItems` |
| Bare video id | `dQw4w9WgXcQ` |
| Chained dataset | `datasetId: "nGkT…"` — the default dataset of a finished YouTube scraper run; rows are deep-scanned for video and channel links |
| Pasted rows | `datasetItems: [ { "url": "https://youtu.be/…" } ]` |

**Not supported:** private, members-only or age-gated videos. A stream still in progress has no captions yet; it is reported, not charged. A watch link that carries `&list=` is one video; use the `playlist?list=` link for the whole playlist.

#### 🔄 How a run works

1. Each URL is resolved to its video ID; channels and playlists are expanded into their videos.
2. The caption track in your requested language is located (translated when that language is missing).
3. If that comes back empty, the run retries automatically by other routes, and with `whisperFallback` on, transcribes the audio as a last step.
4. Segments are normalised to timestamped JSON and rendered to SRT and VTT.
5. One row per video is pushed — transcript, formats, and metadata together.

#### ⚙️ Input parameters

| Field | Type | Default | Notes |
|---|---|---|---|
| `startUrls` | array | — | Video, Shorts, live, channel or playlist URLs, or bare video ids |
| `videoUrl` / `urls` | string / array | — | Accepted as well, so an input pasted from another transcript actor runs as is (not shown in the form) |
| `datasetId` | string | — | Default dataset of a finished YouTube scraper run; every video or channel link in its rows is transcribed |
| `datasetItems` | array | — | Rows pasted from a scraper instead of chaining by id |
| `watchlistId` | string | — | Named memory for scheduled runs; videos transcribed under this id are remembered |
| `newItemsOnly` | boolean | `false` | Leave out videos already on the watchlist. Needs `watchlistId` |
| `language` | string | `default` | Caption language code (`en`, `de`, …); `default` = the video's original track. `targetLanguage` is read too |
| `whisperFallback` | boolean | `false` | Whisper AI speech-to-text for caption-less videos — billed per transcribed video as a premium event |
| `maxItems` | integer | `100` | Hard cap on billed transcript rows |
| `maxConcurrency` | integer | `10` | Parallel video fetches |
| `proxy` | object | Automatic | Works out of the box; nothing to configure. Your own proxy URLs are used when you supply them |

#### 📊 Output overview

One row per transcribed video. The transcript appears three ways — `transcript` (timestamped segments), `transcript_only_text` (plain text), and `transcript_srt` / `transcript_vtt` (ready-to-save subtitle files) — alongside the video's metadata and the spoken hook. A video that could not be transcribed produces no row and no charge; it is listed in the run's `ERRORS` record with the reason (see below).

#### 🧾 What was not delivered, and why

Every video the run set out to transcribe ends as a dataset row with a transcript or as an entry in the run's `ERRORS` record (key-value store), never neither, and never as a billed row without a transcript. The record is written on every run, with an empty `items` list when everything was delivered, so an integration can read it unconditionally. Each entry carries a `reason`, a plain-language `detail` with what to change, and a `retryable` flag:

| `reason` | What happened | Re-run as-is? |
|---|---|---|
| `no_captions` | No caption track and `whisperFallback` is off | Switch on `whisperFallback` |
| `no_speech` | No captions, and Whisper heard only music, silence or noise | No |
| `too_long` | No captions, and the video is longer than Whisper's 30-minute cap | No |
| `live_not_ready` | A live stream still in progress, not started yet, or ended in the last day before YouTube published captions | Later: captions usually appear a few hours after the stream ends |
| `unavailable` | Private, removed, members-only or age-gated | No |
| `login_required` | YouTube asked the fetch to sign in | Yes |
| `rate_limited` | YouTube throttled the fetch | Yes, in a few minutes |
| `fetch_failed` | Another transport failure | Yes |

The status line carries the same arithmetic, for example *"Delivered 48 of 50 videos. 2 not delivered and not charged: 2 no captions and no Whisper fallback — see the ERRORS record."* Videos the watchlist left out are counted separately and never charged. A run that delivers nothing because every fetch failed on YouTube's side is marked FAILED so it stands out in your run list.

#### 🔁 Coming from pintostudio?

Paste your input as it is. Nothing to rename:

| pintostudio/youtube-transcript-scraper | This actor |
|---|---|
| `videoUrl` (one video per run) | `videoUrl` works; `startUrls` takes a list of videos, channels and playlists in one run |
| `targetLanguage` (required, default `en`) | `targetLanguage` works; `language` is the same thing and defaults to the video's own track |
| Output `data: [{ start, dur, text }]` | The same `data` array on every row, plus plain text, SRT, VTT and the video's metadata |
| $0.01 per result | $0.005 per transcript, charged only when the row has one |
| Video without captions: error, empty `data` | Listed in `ERRORS` with a reason, not charged; opt-in Whisper transcribes it instead |
| Live replays fixed in October 2026 | `youtube.com/live/…` links work, a stream still live is reported as `live_not_ready` |
| No channel or playlist input | Channels and playlists walked up to `maxItems`; a schedule with `watchlistId` bills only new uploads |
| No word on the track used | `transcriptLanguage`, `isAutoGenerated`, `isTranslated`, and every track in `availableCaptionTracks` |

#### 🔁 Coming from another YouTube transcript scraper?

What users of other transcript actors report on their issue boards, and what this one does about it:

| Reported elsewhere | Here |
|---|---|
| Live video links fail with "impossible to retrieve video ID" | `youtube.com/live/…` links are read as-is; a stream still in progress is reported as `live_not_ready`, not billed |
| "No captions found" and an empty result | Opt-in Whisper transcribes caption-less videos up to 30 minutes; a video left untranscribed is named in `ERRORS` with a reason and never billed |
| Charged for empty results | A row is written, and billed, only when it carries a transcript |
| Want only a channel's new videos each day | `watchlistId` + `newItemsOnly`: a scheduled run transcribes and bills only uploads it has not seen |
| Want the publish date in the output | `publishDate` and `uploadDate` on every row |
| Want the whole transcript as one string, no timestamps | `transcript_only_text` |
| Want to see which caption tracks exist before choosing | `availableCaptionTracks` lists every language and whether it is auto-generated |
| Requested language ignored, always English | `language` returns the transcript in the language you ask for (checked with `de` and `fr` on an English video) |

#### 📦 Output sample

Real trimmed row from a live run:

```json
{
    "videoId": "dQw4w9WgXcQ",
    "title": "Rick Astley - Never Gonna Give You Up (Official Video) (4K Remaster)",
    "author": "Rick Astley",
    "channelId": "UCuAXFkgsw1L7xaCfnd5JJOw",
    "lengthSeconds": "213",
    "viewCount": "1699540216",
    "transcript": [
        { "text": "[♪♪♪]", "startMs": "1360", "endMs": "3040", "startTimeText": "0:01" },
        { "text": "♪ We're no strangers to love ♪", "startMs": "18800", "endMs": "22140", "startTimeText": "0:18" }
    ],
    "transcript_only_text": "[♪♪♪] ♪ We're no strangers to love ♪ ♪ You know the rules and so do I ♪ …",
    "transcript_srt": "1\n00:00:01,360 --> 00:00:03,040\n[♪♪♪]\n…",
    "transcript_vtt": "WEBVTT\n\n00:00:01.360 --> 00:00:03.040\n[♪♪♪]\n…",
    "data": [{ "start": "1.36", "dur": "1.68", "text": "[♪♪♪]" }, { "start": "18.64", "dur": "3.24", "text": "♪ We're no strangers to love ♪" }],
    "transcriptSource": "timedtext",
    "transcriptLanguage": "en",
    "isAutoGenerated": false,
    "isTranslated": false,
    "keywords": ["rick astley", "Never Gonna Give You Up", "nggyu"],
    "thumbnail": { "thumbnails": [{ "url": "https://i.ytimg.com/vi/dQw4w9WgXcQ/…", "width": 168, "height": 94 }] }
}
```

#### 🗂 Key output fields

| Field | Meaning |
|---|---|
| `transcript[]` | Timestamped segments: `text`, `startMs`, `endMs`, `startTimeText` |
| `transcript_only_text` | The whole transcript as one plain string — LLM-ready |
| `transcript_srt` / `transcript_vtt` | Complete subtitle files as strings — save and use directly |
| `transcriptSource` | Which step produced it: one of the caption routes, or `whisper` when the audio was transcribed |
| `transcriptLanguage` | Language code of the text you got; `null` when the source did not say |
| `isAutoGenerated` | `true` for YouTube's speech recognition, `false` for a person's captions, `null` when it cannot be told |
| `isTranslated` | `true` when the text was translated into the `language` you asked for |
| `data[]` | The same segments as `start`, `dur`, `text` in seconds (as strings), the shape pintostudio's actor returns |
| `availableCaptionTracks[]` | Every caption track on the video: `languageCode`, `name`, `kind` (`manual` or `auto-generated`), `isTranslatable` |
| `publishDate` / `uploadDate` | When the video was published |
| `channelHandle` | The channel's @handle, from its profile link |
| `liveBroadcastDetails` | Start and end time for a video that was a live stream |
| `hook3s` / `hookStartSeconds` | The first 3 seconds of speech, and when speech starts; a music-only opening reads as the caption marker (for example `[♪♪♪]`) |
| `videoId`, `title`, `author`, `channelId` | Video identity |
| `viewCount`, `lengthSeconds`, `keywords`, `shortDescription`, `thumbnail` | Metadata that rides along free |

#### ❓ FAQ

**What happens with videos that have no captions?**
Without `whisperFallback` they produce no row and no charge, and the `ERRORS` record lists them as `no_captions`. With `whisperFallback: true`, the audio is downloaded and transcribed by Whisper speech-to-text — billed as a separate premium event per video, only when it actually produces a transcript. Over music or silence Whisper tends to invent filler ("Thank you.", "♪"); a result made only of that counts as no transcript, so the video is listed as `no_speech` and neither the row nor Whisper is charged. Whisper handles videos up to 30 minutes long.

**Which languages are supported?**
Any language YouTube has a caption track for. Set `language` to a code like `de` or `es`; when that exact track is missing, YouTube's auto-translated track is used where available.

**Can I transcribe a whole channel or playlist?**
Yes. A channel URL (`youtube.com/@handle`) is walked newest first and a playlist URL (`youtube.com/playlist?list=…`) in playlist order, both up to `maxItems`. For search results, run a YouTube search scraper and pass its dataset id in `datasetId`; every video link in the rows is transcribed. On a schedule, add a `watchlistId` with `newItemsOnly` so only new uploads are transcribed and billed.

**My integration starts runs with a short timeout. What happens to a long run?**
About 45 seconds before the run's timeout the actor stops taking new videos, finishes the ones in progress and ends normally. Rows already delivered stay in the dataset, and the status line says the run stopped early. Unfinished videos are not charged.

**Do I need to configure proxies?**
No. The default works out of the box at any volume. If you want runs to go through your own proxies, switch off "Use Apify Proxy" in the `proxy` input and add your proxy URLs.

**Do failed videos cost me anything?**
No. A video without a transcript produces no row, so nothing is billed for it; it is named in the `ERRORS` record instead. Whisper is only charged when it delivers text.

#### 💵 Pricing

| Event | When | Price |
|---|---|---|
| Transcript | A row with a transcript is written | $0.005 |
| Whisper AI transcription | Opt-in, only when Whisper produces text for a caption-less video | $0.20 |
| Actor start | Once per run | $0.00005 |

A video that comes back without a transcript writes no row and costs nothing.

#### 💬 Support

Found a bug or missing a field? Open an issue on the actor's **Issues tab** in Apify Console — issues are answered within 1–2 business days.

#### 🛠 Additional services

Need scheduled transcript archives, a merged multi-platform transcript feed (YouTube + TikTok + Instagram + Loom), or delivery straight to your database? Custom builds and SLAs available — contact me through the actor page.

#### 🔎 Explore more scrapers

Same developer, same stack: [Video & Audio Transcriber (Whisper)](https://apify.com/memo23/video-audio-transcriber), [Instagram Transcript Scraper](https://apify.com/memo23/instagram-transcript-cheerio), [YouTube Comments](https://apify.com/memo23/youtube-comments-scraper), [YouTube Search](https://apify.com/memo23/youtube-search-results-scraper) — and the full portfolio at [memo23 on Apify Store](https://apify.com/memo23).

#### 🤖 For AI Agents & LLM Apps

Minimal input: `{ "startUrls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"] }` (or `{ "videoUrl": "…" }`). One row per video; read `transcript_only_text` for the text and the run's `ERRORS` record for anything not delivered. Built for machine consumption: `transcript_only_text` drops straight into a context window; timestamped segments support citation and chaptering; stable field names across every row. Pair with the [Video Transcripts MCP Server](https://apify.com/memo23/transcripts-mcp-server) to expose transcripts as a tool in agent frameworks. Keep `maxItems` low per call for cost control; every row is self-contained.

***

### ⚠️ Disclaimer

This Actor is an independent tool and is not affiliated with, endorsed by, or sponsored by YouTube or Google LLC. All trademarks mentioned are the property of their respective owners.

The scraper accesses only publicly available video pages and caption data — no login, no age-gated, members-only or private content. Users are responsible for ensuring their use complies with YouTube's Terms of Service, copyright law as it applies to transcript content, applicable data-protection law (GDPR, CCPA, etc.), and any contractual obligations of their own organisation.

***

### SEO Keywords

youtube transcript scraper, youtube transcript api, extract youtube transcript, youtube captions scraper, youtube subtitles downloader, srt from youtube, vtt from youtube, youtube video to text, youtube transcription tool, whisper youtube transcription, transcribe youtube videos without captions, youtube transcript for llm, youtube rag pipeline, bulk youtube transcripts, youtube caption extractor, video to text api, youtube shorts transcript, youtube transcript json, youtube playlist transcript, youtube channel transcripts, auto-generated captions, apify youtube transcript, pintostudio alternative, youtube-transcript-scraper alternative

# Actor input Schema

## `startUrls` (type: `array`):

Video URLs in any form — `youtube.com/watch?v=…`, `youtu.be/…`, Shorts or `youtube.com/live/…` replay links — bare 11-character video ids, a channel (`/@handle`, `/channel/…`, `/c/…`, its Videos or Live tab) walked newest first, or a `youtube.com/playlist?list=…` link. Channels and playlists stop at Maximum results. Paste as many as you like; each video becomes one transcript row. Coming from another transcript actor? A `videoUrl` string or `urls` list in the JSON input works too.

## `datasetId` (type: `string`):

Default dataset ID of a finished YouTube scraper run (channel videos, search results, playlists). Every row is deep-scanned for video and channel links, so the row shape does not matter.

## `datasetItems` (type: `array`):

Paste rows from a YouTube scraper instead of chaining by ID. Any field holding a youtube.com/watch, youtu.be, /shorts/ or channel URL is picked up.

## `watchlistId` (type: `string`):

Named key-value store that remembers which videos this run already transcribed. Use the same id on a schedule (for example <code>mkbhd-daily</code>) and later runs leave them out, so a video is never billed twice. Letters, numbers, hyphens, underscores.

## `newItemsOnly` (type: `boolean`):

When enabled, videos already stored on the Watchlist ID are left out of the run and the dataset — a channel is still walked, only its new uploads are transcribed. Requires <code>watchlistId</code>. Default: false.

## `language` (type: `string`):

Preferred caption language code (e.g. `en`, `de`, `es`). `default` takes the video's original track. When the exact track is missing, the transcript is translated. Each row says the language it came back in and whether the track was auto-generated. `targetLanguage` in the JSON input is read too.

## `whisperFallback` (type: `boolean`):

When a video has no captions at all, download its audio and transcribe it with Whisper speech-to-text. Billed as a separate premium event per transcribed video — see the pricing tab. Off by default so you never pay it unknowingly. Applies to videos up to 30 minutes long.

## `maxItems` (type: `integer`):

Hard cap on transcript rows for the run — your spend guard. Applies per run, across all input URLs (matters for channel expansion).

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

Parallel video fetches.

## `minConcurrency` (type: `integer`):

Lower bound for the autoscaled pool.

## `maxRequestRetries` (type: `integer`):

Retries per video before it is skipped (skipped videos are never billed).

## `proxy` (type: `object`):

Runs go out through the actor's own residential pools, so Apify proxy groups and countries chosen here are not used (the run logs a line saying so). Only your own proxy URLs, with 'Use Apify Proxy' switched off, are honoured.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
  ],
  "newItemsOnly": false,
  "language": "default",
  "whisperFallback": false,
  "maxItems": 100,
  "maxConcurrency": 10,
  "minConcurrency": 1,
  "maxRequestRetries": 3,
  "proxy": {
    "useApifyProxy": true
  }
}
```

# 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 = {
    "startUrls": [
        "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
    ],
    "proxy": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/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 = {
    "startUrls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"],
    "proxy": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("memo23/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 '{
  "startUrls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
  ],
  "proxy": {
    "useApifyProxy": true
  }
}' |
apify call memo23/youtube-transcript-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,memo23/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/0DZQSYBoLgufCC5Hg/builds/lcIkDa5WQMdmLpOem/openapi.json
