# YouTube Transcript Scraper - Subtitles & Captions, $0.001/Video (`apidojo/youtube-transcript-scraper`) Actor

Extract YouTube transcripts, subtitles, and captions (uploaded or auto-generated) as JSON with per-line timestamps from youtube.com/watch and youtu.be links. $0.001 per transcript. Pick a caption language, run in bulk, reshape output. No API key or login. For AI, RAG, SEO, and research.

- **URL**: https://apify.com/apidojo/youtube-transcript-scraper.md
- **Developed by:** [API Dojo](https://apify.com/apidojo) (community)
- **Categories:** Videos, AI, Social media
- **Features:** Limited permissions
- **Stats:** 100 total users, 42 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $0.97 / 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.
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?

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 & API – Transcripts, Subtitles and Captions with Timestamps

YouTube Transcript Scraper returns the captions of public YouTube videos as structured JSON: every line's text with its start time and duration, plus the caption track that was returned. Give it `youtube.com/watch` or `youtu.be` links and get results through the Apify Console or API, with no YouTube API key or login, at $0.001 per transcript ($1 per 1,000). It reads the caption tracks YouTube already has, uploaded or [auto-generated](https://support.google.com/youtube/answer/6373554); it doesn't transcribe audio, so videos with captions turned off return no transcript.

### At a glance

- **Input:** `youtube.com/watch?v=…` or `youtu.be/…` links, one per entry
- **Output:** one JSON row per video: `transcript` lines with `text`, `startTime` (like `"0:03"`) and `dur` (seconds), plus the `selected` caption track
- **Price:** $0.001 per transcript ($1 per 1,000)
- **Language:** optional `lang` code (`en`, `es`, `de`, `tr`, `pt-BR`, …); auto-generated captions count
- **Free Apify plan:** 5 runs a month, up to 10 transcripts per run
- **Not supported:** Shorts, live, embed, playlist and channel links; videos without captions; speech-to-text

### Quick start

Open the Actor in Apify Console and click **Start**; the input is prefilled. This input fetches one English transcript:

```json
{
    "startUrls": ["https://www.youtube.com/watch?v=_AbFXuGDRTs"],
    "lang": "en"
}
```

To call it from code, send the same input to the [Apify API](https://docs.apify.com/api/v2) and get the rows back in one request:

```bash
curl -X POST "https://api.apify.com/v2/acts/apidojo~youtube-transcript-scraper/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"startUrls":["https://www.youtube.com/watch?v=_AbFXuGDRTs"],"lang":"en"}'
```

Real output for that video, trimmed to the first 2 of its 551 lines:

```json
{
    "inputSource": "https://www.youtube.com/watch?v=_AbFXuGDRTs",
    "type": "transcript",
    "id": "_AbFXuGDRTs",
    "transcript": [
        { "text": "Hidden in this mountain is a $1 billion", "dur": "5.679", "startTime": "0:00" },
        { "text": "nuclear bunker. We are 2,000 ft", "dur": "4.8", "startTime": "0:03" }
    ],
    "selected": { "title": "English (auto-generated)", "languageCode": "en" },
    "availableLanguages": [{ "title": "Vietnamese", "languageCode": "vi" }]
}
```

### Supported YouTube link formats

- `https://www.youtube.com/watch?v=VIDEO_ID`, including `m.youtube.com` and `music.youtube.com`
- `https://youtu.be/VIDEO_ID`

Links can be plain strings or objects like `{ "url": "…" }`. Each link is processed separately, so a duplicate link returns a duplicate row.

Not supported: Shorts (`/shorts/…`), live (`/live/…`), embed (`/embed/…`), playlist and channel links. Links from other sites are rejected.

Starting from a playlist or a channel? Get its video links first with [YouTube Playlist Scraper](https://apify.com/apidojo/youtube-playlist-scraper?fpr=yhdrb) or [YouTube Channel Scraper](https://apify.com/apidojo/youtube-channel-scraper?fpr=yhdrb), then pass them to `startUrls`.

Only videos with captions return a transcript. Videos with captions turned off are skipped (error code C003) and the rest of the batch continues.

### Input parameters

| Field | Type | Description |
| --- | --- | --- |
| `startUrls` | Array, required | YouTube video links, one per entry. See [Supported YouTube link formats](#supported-youtube-link-formats). A run without `startUrls` fails at start. |
| `lang` | String | Optional caption language code, such as `en`, `es`, `de`, `tr` or `pt-BR`. Leave empty for the video's default caption track. If the video has no track in that language, a different track is returned, so check `selected.languageCode`. |
| `maxItems` | Integer | Stops saving once this many rows are in the dataset. Leave empty to process every URL. Free-plan runs are capped at 10 rows. |
| `customMapFunction` | String | Optional JavaScript function that reshapes each row. See [Custom map function](#custom-map-function). |
| `outputConnector` | String | Optional MCP connector to deliver the results to. See [Send results to Notion, Todoist and more](#send-results-to-notion-todoist-and-more-mcp-connectors). |
| `deliverTarget` | String | Where the connector writes: a page or database URL or ID (Notion example: `https://www.notion.so/My-Posts-24f8a1b2c3d44e5f8a9b0c1d2e3f4a5b`). Some services need none. |
| `deliverMode` | String | `perPost` writes one record per item, `summary` one roll-up per run. Defaults to `perPost`. |
| `deliverMaxItems` | Integer | Optional cap on how many records `perPost` delivers. Ignored in `summary`. |

### Output

Each video becomes one JSON row in the dataset. Export it as JSON, CSV, Excel or XML from the Console, or read it through the API.

| Field | Description |
| --- | --- |
| `inputSource` | The link exactly as you submitted it. Always present, even with a custom map function. |
| `type` | `"transcript"` on transcript rows. Notice rows don't have it. |
| `id` | The 11-character YouTube video ID. |
| `transcript` | Lines in playback order. Each has `text`, `startTime` (clock-style string such as `"0:03"` or `"17:06"`, not seconds) and `dur` (seconds as a decimal string, such as `"5.679"`). |
| `selected` | The caption track that was returned: `title`, written in the requested language (e.g. `"English (auto-generated)"`, `"Español"`), and `languageCode`. |
| `availableLanguages` | A single `{ title, languageCode }` entry from the transcript backend. It doesn't list the video's caption tracks, so don't use it to find languages; request one with `lang` and check `selected.languageCode`. |

Line text can include line breaks, zero-width spaces, speaker-change markers (`>>`) and tags such as `[Music]`. Auto-generated captions may show `[ __ ]` where YouTube hides a word. Strip these if you need clean prose.

To keep only transcripts, filter on `type === "transcript"`.

### More input examples

Request French captions for a batch:

```json
{
    "startUrls": [
        "https://www.youtube.com/watch?v=_AbFXuGDRTs",
        "https://youtu.be/iG9CE55wbtY"
    ],
    "lang": "fr",
    "maxItems": 50
}
```

Get one plain-text string per video instead of timed lines:

```json
{
    "startUrls": ["https://www.youtube.com/watch?v=_AbFXuGDRTs"],
    "customMapFunction": "(object) => ({ videoId: object.id, language: object.selected.languageCode, fullText: object.transcript.map((line) => line.text).join(' ') })"
}
```

Tips:

- Set `lang` when you need one language across a batch, then drop rows whose `selected.languageCode` differs.
- Test a new batch with a small `maxItems` before scaling up.
- Keep the full output until your schema is settled; reshape with `customMapFunction` later.

### Pricing

Pricing follows a transparent, pay-per-event model. You're only charged for transcripts that are actually extracted. That's $1 per 1,000 transcripts.

| Pricing item | Price | Description |
| --- | --- | --- |
| Video Transcript | $0.001 | Charged per successful transcript extraction (`transcript-query`) |
| Dataset Item | $0.00 | Charged for each item pushed to the dataset, but this actor doesn't charge this event, so it costs nothing regardless of plan |

#### Understanding your costs

- **Single video:** One `startUrls` entry with a valid transcript costs $0.001 for the `transcript-query` charge.
- **Batch run (10 videos):** If all 10 videos return a transcript, you're charged 10 × $0.001 = $0.01. Videos that are skipped (no transcript, invalid URL, or a failed request) are not charged.
- **No extra storage fee:** This actor doesn't charge the `dataset-item` event at all, so there's no additional cost on top of the $0.001 transcript price.

#### Why it works so well

- Pay per event: only successfully extracted transcripts are charged.
- Scale from a single video lookup to large batch or bulk extraction jobs without unpredictable costs.
- No `dataset-item` charge on top of the transcript price, since this actor doesn't bill for that event.

#### Free Apify plan

If you run on Apify's Free plan, you can still use this YouTube Transcript Scraper in Demo Mode. Free users can run the actor up to 5 times per month, with each run capped at a maximum of 10 items — just enough to test it out. To use this actor without limitations, subscribe to a paid plan on Apify. [Subscribe here](https://apify.com/pricing?fpr=yhdrb).

### Features

- **Timed transcript lines:** every line has `text`, `startTime` and `dur`, so you can rebuild exact timing, chapter markers or subtitle files.
- **Uploaded and auto-generated captions:** reads whichever caption tracks the video already has.
- **Language selection:** request a language with `lang`, including regional codes such as `pt-BR`; `selected` shows which track came back.
- **Bulk runs:** pass hundreds of links in one run; `maxItems` caps how many rows are saved.
- **Fault-tolerant:** each video is processed on its own. Videos without captions and failed requests are skipped while the rest of the batch continues, and rate limits are retried automatically. 4,015 of 4,018 runs succeeded in the last 30 days (Apify public run stats, September 2026).
- **Custom output shape:** `customMapFunction` renames or reshapes the fields of every row.
- **No API key or login:** works from the video link alone. Run it from the Console, the Apify API, integrations or on a schedule.

### Custom map function

Use this function to reshape every row. It receives one transcript object and returns the object to save, so you can rename fields, change formatting or keep only the attributes you need.

- The return value has to be an object.
- It reshapes rows; it can't drop them.
- `inputSource` is always added back to the result.

Example:

```javascript
(object) => {
    return {
        videoId: object.id,
        language: object.selected.languageCode,
        fullText: object.transcript.map((line) => line.text).join(' '),
    };
}
```

Result:

```json
{
    "inputSource": "https://www.youtube.com/watch?v=_AbFXuGDRTs",
    "videoId": "_AbFXuGDRTs",
    "language": "en",
    "fullText": "Hidden in this mountain is a $1 billion nuclear bunker. We are 2,000 ft …"
}
```

#### Requirements and error handling

The value must be a **single JavaScript function** that takes one transcript and returns the transformed transcript. Arrow functions and regular functions both work; `async` and generator functions are not supported.

**Limits:** the function runs in an isolated sandbox. It must be synchronous and finish within about 1 second per transcript, and only plain JavaScript is available (no network requests, `require` or `console`). Each transcript is passed in as plain JSON data.

**An unusable function never stops your run.** The actor logs a warning, ignores the function, and saves the original, unmodified transcripts instead-so you still get your data:

| Situation | What happens |
| --------- | ------------ |
| Not valid JavaScript (for example, plain text pasted into the field) | Rejected before the first request; original transcripts are saved |
| Valid JavaScript but not a function (for example, an object) | Rejected before the first request; original transcripts are saved |
| `async` or generator function | Rejected before the first request; original transcripts are saved |
| Returns a Promise (for example, `(object) => Promise.resolve(object)`) | Rejected at that point; the remaining transcripts are saved unmodified |
| Returns something other than an object (for example, a number or `null`) | Rejected at that point; the remaining transcripts are saved unmodified |
| Throws while transforming a transcript | Rejected at that point; the remaining transcripts are saved unmodified |
| Takes longer than about 1 second on one transcript (for example, an endless loop) | Rejected at that point; the remaining transcripts are saved unmodified |

If your output does not look transformed, check the run log for a warning that starts with `Your "customMapFunction" input was rejected:`-it explains exactly what was wrong.

If you do not need to transform the output, simply leave this field empty.

### Send results to Notion, Todoist and more (MCP connectors)

The YouTube Transcript Scraper can deliver its results straight into the tools you already use, through [Apify MCP Connectors](https://apify.com/store/collections/mcp-connectors).

1. Authorize a connector (Notion, Todoist, or any service whose MCP server offers non-destructive write tools) once in [Console → Settings → API & Integrations → MCP connectors](https://console.apify.com/settings/integrations).
2. Pick it in the **Send results to Notion, Todoist, …** field.
3. Fill in where and how to write:
   - **Destination target:** the page or database URL or ID (Notion example: `https://www.notion.so/My-Posts-24f8a1b2c3d44e5f8a9b0c1d2e3f4a5b`). Share it with your connector first through Notion's **••• → Connections** menu. Some services need no target.
   - **Write mode:** `perPost` writes one record per video; `summary` writes a single roll-up for the run.
   - **Max records to deliver** (optional): a cap on how many records `perPost` delivers.

**Notion:** point the target at a Notion **page** and the Actor creates a database with one typed column per field (URL, number, date, text). An **existing database** gets its matching columns filled by name. The connector can't add columns to it, so an empty database receives only titles and the rest of each record goes to the row's page body. The `selected` object becomes `selected.title` and `selected.languageCode` columns, and the `transcript` lines become one JSON text column. Notion caps a text cell at 2,000 characters, so a long transcript is cut off in Notion; the dataset always has the full transcript. Fields without a column go to the row's page body, so nothing is lost.

Your credentials stay with you: the Actor writes only through your own connector and only uses non-destructive write tools, so it can't delete or overwrite anything. Delivery runs after the scrape and is best-effort. If it fails, the run still finishes normally with all results in the dataset, and the run log says what went wrong. If the run is close to its timeout, delivery stops early so the run still succeeds, and the log says how many records were delivered.

### Use cases

#### Content repurposing and marketing

Turn video content into blog posts, show notes and social copy without re-watching or transcribing by hand.

- Draft blog posts or newsletters from the full transcript of an existing video.
- Use `startTime` to create accurate chapter markers or show notes.
- Batch-process a channel's back catalog into a searchable content library.

**Example:** turn a podcast's YouTube upload into a written recap and pull quotes for social media. If the recap also needs view counts or channel details, [YouTube Scraper API](https://apify.com/apidojo/youtube-scraper-api?fpr=yhdrb) can add that metadata for the same video.

#### SEO and content marketing

Spoken content becomes text you can analyze and reuse for search.

- Pull keyword-rich transcript text to inform on-page copy.
- Find the subtopics that come up most across the top videos in a niche.
- Build FAQ or article content from spoken explanations in tutorial videos.
- Request the same video in several languages with `lang` to plan multilingual content.

**Example:** scrape transcripts from the top-ranking videos on a topic to find subtopics and phrasing worth covering in a companion article.

#### Research and academic analysis

Structured, timed transcript text supports discourse and content analysis at scale.

- Collect transcripts across many videos for text-based discourse analysis.
- Use `startTime` and `dur` to tie quotes to exact moments in the source video.
- Build longitudinal datasets that track how a topic is discussed over time.

**Example:** scrape 100 educational videos on the same topic to compare how creators explain one concept. For a course-length playlist, [YouTube Playlist Scraper](https://apify.com/apidojo/youtube-playlist-scraper?fpr=yhdrb) can list every video URL in order, ready for `startUrls`.

#### AI, RAG and NLP pipelines

Clean, timed transcript text is ready-made input for summarization, retrieval, classification or fine-tuning.

- Collect transcripts at scale to build training or evaluation corpora.
- Chunk lines by `startTime` for retrieval-augmented generation (RAG) with timestamp citations.
- Set `lang` for language-specific datasets, and drop rows whose `selected.languageCode` fell back to another language.
- Use `customMapFunction` to shape rows into the schema your pipeline expects.

**Example:** scrape transcripts from a curated list of domain videos to build a fine-tuning set for a domain-specific summarization model.

#### Accessibility and localization

Existing captions are a starting point for accessibility review and translation work.

- Pull the existing transcript as the base for an accessibility audit.
- Request a language with `lang` and check `selected.languageCode` to see whether the video already has captions in it.
- Use the timed lines to resync or reuse captions on other platforms.

**Example:** check a set of training videos for existing captions in each target language to plan which still need translation. If the videos span a whole channel, [YouTube Channel Scraper](https://apify.com/apidojo/youtube-channel-scraper?fpr=yhdrb) can list every video so nothing gets missed.

### Frequently asked questions

#### Can I scrape YouTube transcripts without an API key?

Yes. The scraper works from a video's link and needs no YouTube Data API key or login.

#### Can I use this as a YouTube Transcript API?

Yes. Every run can be started through the Apify API, including the `run-sync-get-dataset-items` endpoint shown in [Quick start](#quick-start), so you can call it from your own app, script or automation.

#### How do I download YouTube subtitles or captions as text?

Pass the video links in `startUrls` and run the Actor. Each video's captions come back as timed lines you can export as JSON, CSV or Excel, or join into one string with `customMapFunction`.

#### Does it work with auto-generated captions?

Yes. Auto-generated tracks are returned like uploaded ones; `selected.title` shows which kind you got, for example `"English (auto-generated)"`.

#### Can it transcribe videos that have no captions?

No. It reads caption tracks that YouTube already has and doesn't run speech-to-text. Videos with captions turned off are skipped (C003).

#### Does it support YouTube Shorts?

No. Only `youtube.com/watch?v=…` and `youtu.be/…` links are supported.

#### Can I get SRT files?

Not directly. The output is JSON with `text`, `startTime` and `dur` per line, which you can export as JSON, CSV or Excel and convert to SRT in your own code.

#### Does this scraper support bulk or batch transcript extraction?

Yes. Pass multiple links in `startUrls` to run it as a bulk YouTube transcript scraper, and use `maxItems` to cap how many rows are saved.

#### Is this different from open-source "youtube-transcript" libraries?

It covers similar ground to open-source youtube-transcript packages, but runs as a managed Apify actor with built-in retries, rate-limit handling, batch support, and a hosted API, so there's no need to install or maintain a library yourself.

#### How much does it cost to scrape a YouTube transcript?

You pay $0.001 per successfully extracted transcript (`transcript-query`). There's no separate `dataset-item` charge, since this actor doesn't bill for that event, on any plan.

#### What happens if I submit a non-YouTube URL?

It's rejected. Only hostnames containing `youtube` or `youtu` are accepted.

#### Can I request a transcript in a specific language?

Yes. Set `lang` to a language code such as `en`, `tr` or `pt-BR`. If the video has no track in that language, a different track is returned, so check `selected.languageCode`.

#### Can I see which caption languages a video has?

Not reliably. The `availableLanguages` field holds a single entry and doesn't list the video's tracks. Request the language you need with `lang` and check `selected.languageCode`.

#### What does the transcript timing data look like?

Each line has `startTime`, a clock-style string such as `"0:03"` or `"17:06"`, and `dur`, the line's duration in seconds as a string such as `"5.679"`.

#### How many videos can I process per run?

As many as you pass in `startUrls`. Set `maxItems` to cap the rows saved. Free-plan runs are capped at 10 rows.

#### What happens if the backend rate limit is hit?

A `C019` notice row is pushed to the dataset and the request is retried automatically after a short delay.

#### Why did my run fail immediately with no videos processed?

Usually `startUrls` is missing, or the run failed to initialize with the subscription and policy service (C100). Check the Log tab for the code.

#### Can I automate transcript scraping on a schedule?

Yes. Use Apify's built-in scheduler, for example to pull transcripts as new videos appear on a channel you monitor.

### Troubleshooting

#### A video is missing from the results

The actor logs the reason and continues with the rest of the batch.

| Error code | Cause | Behavior |
| --- | --- | --- |
| C001 | The transcript request to the backend failed (network or unexpected error). | The video is skipped; no row is pushed for it. |
| C002 | The backend returned an empty or unexpected response. | The video is skipped; no row is pushed for it. |
| C003 | The video has no transcript (captions turned off or unavailable). | The video is skipped; no row is pushed for it. |

#### The dataset contains `C019` rows

The backend rate limit was hit. The request is retried automatically after a short delay, so no action is needed; filter the notice rows out with `type === "transcript"`.

#### The run stopped or failed

C098 (unhandled promise rejection) and C099 (uncaught exception) stop the run; C100 means it couldn't initialize with the subscription and policy service. Retry the run, and if it happens again, contact support with the run's Log tab output.

#### The run returned no results

- Each link in `startUrls` must be a `youtube.com/watch?v=…` or `youtu.be/…` link.
- The link's host must be YouTube; other domains are rejected.
- `startUrls` must not be empty.
- The videos must have captions; check on YouTube first.

### Related YouTube Actors

A transcript captures what's said in a video. For view counts, channel details or the rest of a playlist, pair this actor with these tools from the same developer:

| [YouTube Scraper API](https://apify.com/apidojo/youtube-scraper-api?fpr=yhdrb) | [YouTube Playlist Scraper](https://apify.com/apidojo/youtube-playlist-scraper?fpr=yhdrb) | [YouTube Channel Scraper](https://apify.com/apidojo/youtube-channel-scraper?fpr=yhdrb) |
| --- | --- | --- |
| Videos, channels, playlists, search and trending metadata from one actor | Every video in a playlist, with position and metadata, ready for this scraper's `startUrls` | Every video published by a given channel |

### Contact

If you need support, check the run's Log tab in Apify Console for the error code first, then reach out through the actor's Issues tab on Apify with the run link attached.

# Actor input Schema

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

One YouTube video link per entry. Supported: youtube.com/watch?v=VIDEO_ID (also m. and music. subdomains) and youtu.be/VIDEO_ID. Not supported: Shorts, live, embed, playlist and channel links. To start from a playlist or channel, get its video links with apidojo/youtube-playlist-scraper or apidojo/youtube-channel-scraper first. Plain URL strings; objects like {"url": "..."} also work. Duplicate links return duplicate rows.

## `lang` (type: `string`):

Optional caption language code, such as en, es, de, tr or pt-BR. Auto-generated captions count: en can return "English (auto-generated)". Leave empty to get the video's default caption track. If the video has no track in that language, a different track is returned instead, so check the output's selected.languageCode to see which one you got.

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

Optional. The run stops saving once this many rows are in the dataset. Leave empty to process every URL. Runs on the free Apify plan are capped at 10 rows.

## `customMapFunction` (type: `string`):

JavaScript function to transform each output transcript. Use to add computed fields, modify values, or remove unnecessary data. NOT intended for filtering - filtering may result in automatic bans. Must be a single synchronous function, e.g. (object) => { return {...object} }. It runs in a sandbox with plain JavaScript only (no network or require) and about 1 second per item. inputSource is always kept. Leave empty if you do not need to transform the output - an unusable function is ignored and the original items are saved instead.

## `outputConnector` (type: `string`):

Pick an MCP connector you authorized in Console → Settings → API & Integrations → MCP connectors (e.g. Notion, Todoist — any service whose MCP server offers non-destructive write tools works best-effort). The scraped results are written through your own connector — the Actor never sees your third-party credentials. Only non-destructive write tools are used; it can never delete or overwrite your existing data.

## `deliverTarget` (type: `string`):

Where to write into the connector — paste the URL or ID. Notion example: https://www.notion.so/My-Posts-24f8a1b2c3d44e5f8a9b0c1d2e3f4a5b. Leave empty for services that need no target (e.g. Todoist Inbox, Slack). For Notion, share the page or database with your connector first (Notion's ••• → Connections menu), and point at a PAGE for structured columns (the Actor builds the database for you) — an empty database receives only the title (the rest of each record goes to the row's body).

## `deliverMode` (type: `string`):

perPost writes one record per scraped item; summary writes a single roll-up for the run.

## `deliverMaxItems` (type: `integer`):

Cap on how many records perPost mode writes to the connector; ignored in summary mode. Leave empty to deliver everything scraped.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.youtube.com/watch?v=_AbFXuGDRTs",
    "https://youtu.be/_AbFXuGDRTs"
  ],
  "lang": "en",
  "maxItems": 100,
  "customMapFunction": "(object) => { return { ...object } }",
  "deliverMode": "perPost"
}
```

# Actor output Schema

## `overview` (type: `string`):

One row per video: timed caption lines (text, startTime, dur) and the caption track that was returned. Notice rows (rate limit, demo) have no type field.

# 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=_AbFXuGDRTs",
        "https://youtu.be/_AbFXuGDRTs"
    ],
    "lang": "en",
    "maxItems": 100,
    "customMapFunction": (object) => { return { ...object } }
};

// Run the Actor and wait for it to finish
const run = await client.actor("apidojo/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=_AbFXuGDRTs",
        "https://youtu.be/_AbFXuGDRTs",
    ],
    "lang": "en",
    "maxItems": 100,
    "customMapFunction": "(object) => { return { ...object } }",
}

# Run the Actor and wait for it to finish
run = client.actor("apidojo/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=_AbFXuGDRTs",
    "https://youtu.be/_AbFXuGDRTs"
  ],
  "lang": "en",
  "maxItems": 100,
  "customMapFunction": "(object) => { return { ...object } }"
}' |
apify call apidojo/youtube-transcript-scraper --silent --output-dataset

```

## MCP server setup

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