# YouTube Transcript API: Text, Timestamps, SRT & VTT (`himanshu-data/youtube-transcript-api`) Actor

Turn YouTube video links into clean transcript text, timestamps, SRT and VTT subtitles. Supports available manual and automatic captions, batch URLs and language selection. Pay only for successful transcripts.

- **URL**: https://apify.com/himanshu-data/youtube-transcript-api.md
- **Developed by:** [Himanshu Agrawal](https://apify.com/himanshu-data) (community)
- **Categories:** Social media, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$4.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 API: Text, Timestamps, SRT & VTT

Get the captions that already exist on YouTube videos as plain text, timestamped segments, SRT or WebVTT. Paste video URLs or IDs and get one dataset item per video.

This Actor reads existing captions only. Both manual captions (uploaded by the creator) and automatic captions (generated by YouTube) are supported. If a video has no captions, this Actor cannot transcribe it. There is no AI speech recognition in this version.

### Quick start

1. Add one or more video URLs or IDs to **Video URLs or IDs**.
2. Optionally set **Preferred languages**, for example `en` or `es`.
3. Click **Start**. Each video with captions becomes one dataset item.
4. Open the **SUMMARY** record in the key-value store to see what happened to every input, including videos that had no captions.

### Supported inputs

| Input type | Example |
| --- | --- |
| Video ID | `jNQXAC9IVRw` |
| Watch URL | `https://www.youtube.com/watch?v=jNQXAC9IVRw` |
| Short link | `https://youtu.be/jNQXAC9IVRw` |
| Shorts | `https://www.youtube.com/shorts/VIDEO_ID` |
| Live | `https://www.youtube.com/live/VIDEO_ID` |
| Embed | `https://www.youtube.com/embed/VIDEO_ID` |

Accepted hosts are `youtube.com`, `www.youtube.com`, `m.youtube.com`, `music.youtube.com`, `youtu.be` and `www.youtube-nocookie.com` (embed). Playlists, channels, search pages and other websites are rejected and recorded in SUMMARY. A watch URL that also has a playlist parameter is read as that single video.

### Input

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `videoUrls` | array of strings | required | Video URLs or 11 character IDs. Up to 1000 entries. Duplicates are removed before any request. |
| `languages` | array of strings | `["en"]` | Caption languages in order of preference. `en` also matches regional tracks such as `en-US`, and `pt-BR` also matches `pt`. Within a language, manual captions are preferred over automatic ones. |
| `fallbackToOriginal` | boolean | `true` | If none of your languages exist, return the video's original caption language. The item then has `matchedRequestedLanguage: false`. If off, such videos are skipped with code `LANGUAGE_NOT_AVAILABLE`. Captions are never translated. |
| `includeSegments` | boolean | `true` | Add the `segments` array with timestamps. |
| `includeSrt` | boolean | `false` | Add an `srt` string. |
| `includeVtt` | boolean | `false` | Add a `vtt` string. |
| `maxVideos` | integer | `100` | Maximum unique videos to process, from 1 to 1000. Extra inputs are skipped before any request. |
| `proxyConfiguration` | object | residential | Proxy settings. Residential proxies are recommended for reliability. |

### Output

Only videos with a non-empty transcript are saved to the dataset.

| Field | Description |
| --- | --- |
| `videoId` | 11 character video ID. |
| `url` | Canonical watch URL. |
| `title` | Video title as reported by YouTube, or `null` if not provided. |
| `channelName` | Channel name as reported by YouTube, or `null`. |
| `language` | Language code of the returned caption track. |
| `availableLanguages` | Caption tracks the video offers: `languageCode`, `name`, `isGenerated`. |
| `isGenerated` | `true` for automatic captions, `false` for manual, `null` if unknown. |
| `matchedRequestedLanguage` | `true` if the track matches one of your languages, `false` if the original language was used as a fallback. |
| `text` | Full transcript as one string. |
| `segmentCount` | Number of caption segments. |
| `durationSeconds` | Video length in seconds as reported by YouTube, or `null`. |
| `segments` | Optional. `[{ text, start, duration, end }]`, all times in seconds. |
| `srt` | Optional. SubRip subtitle text. |
| `vtt` | Optional. WebVTT subtitle text. |
| `scrapedAt` | ISO timestamp. |

#### Example item

This example is illustrative and shortened. Real values come from the video.

```json
{
  "videoId": "jNQXAC9IVRw",
  "url": "https://www.youtube.com/watch?v=jNQXAC9IVRw",
  "title": "Example video title",
  "channelName": "Example channel",
  "language": "en",
  "availableLanguages": [
    { "languageCode": "en", "name": "English", "isGenerated": false }
  ],
  "isGenerated": false,
  "matchedRequestedLanguage": true,
  "text": "First caption line. Second caption line.",
  "segmentCount": 2,
  "durationSeconds": 19,
  "segments": [
    { "text": "First caption line.", "start": 1.2, "duration": 2.16, "end": 3.36 },
    { "text": "Second caption line.", "start": 4.12, "duration": 3.5, "end": 7.62 }
  ],
  "scrapedAt": "2026-10-27T12:00:00.000Z"
}
```

### SUMMARY record

The default key-value store gets a `SUMMARY` record with:

- `status`: `SUCCEEDED`, `NO_RESULTS` or `FAILED`, plus a `message`.
- Counts: `requested`, `unique`, `invalid`, `duplicates`, `processed`, `succeeded`, `failed`, `skippedDueToLimit`, `skippedDueToSpendingLimit`.
- `spendingLimitReached`, `startedAt`, `finishedAt`.
- `outcomes`: one entry per input with `inputIndex`, `videoId`, `status` and, for failures, a stable `code` and `message`.

| Code | Meaning | Retried |
| --- | --- | --- |
| `INVALID_INPUT` | Not a valid video URL or ID. | No |
| `UNSUPPORTED_URL` | Playlist, channel, other website or other URL type. | No |
| `VIDEO_UNAVAILABLE` | Video deleted, unavailable or not found. | No |
| `VIDEO_RESTRICTED` | Private, age restricted or sign in required. | No |
| `NO_CAPTIONS` | The video has no captions. | No |
| `LANGUAGE_NOT_AVAILABLE` | No requested language and fallback is off. | No |
| `BLOCKED` | The source refused or rate limited the request. | Yes |
| `SOURCE_ERROR` | The source returned a server error. | Yes |
| `EMPTY_CAPTIONS` | The caption track was empty or unreadable. | Yes |
| `TIMEOUT`, `NETWORK_ERROR` | Connection problems. | Yes |
| `RESPONSE_TOO_LARGE`, `INTERNAL_ERROR` | Unexpected response or error. | No |
| `OUTPUT_ERROR` | Saving the result could not be confirmed. Check the dataset before retrying the video. | No |

Retryable errors are tried up to 3 times in total, each time with a new proxy session.

If every processed video fails because of source or network errors, the run is marked as failed. If videos simply have no captions, the run finishes normally with zero items and status `NO_RESULTS`.

### Pricing

**$0.004 per successful transcript, or $4 per 1,000, on every Apify plan. No Actor start fee.** The charge covers one video, regardless of caption length and whether you include timestamps, SRT or VTT.

You pay per saved transcript (event `transcript`). Duplicates, invalid inputs, failed extractions, videos without captions and skipped inputs are not charged. Current prices are shown on the Actor's pricing tab. If your run reaches its spending limit, the Actor stops starting new videos and records the rest as `skippedDueToSpendingLimit`.

### Limitations

- Only existing captions are returned. No speech recognition and no translation.
- Private, members only, age restricted and deleted videos cannot be read.
- Playlists and channels are not expanded in this version. Pass video URLs.
- Automatic captions may contain recognition mistakes made by YouTube.
- YouTube can change its pages at any time, which may temporarily cause failures.

### API usage

Replace `APIFY_TOKEN` with your API token. Keep it out of source code.

#### Synchronous run (small batches)

Returns dataset items directly. Synchronous runs time out after 300 seconds, so keep batches small.

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

#### Python

```python
import os
import requests

resp = requests.post(
    "https://api.apify.com/v2/acts/himanshu-data~youtube-transcript-api/run-sync-get-dataset-items",
    headers={"Authorization": f"Bearer {os.environ['APIFY_TOKEN']}"},
    json={"videoUrls": ["https://www.youtube.com/watch?v=jNQXAC9IVRw"], "languages": ["en"]},
    timeout=320,
)
resp.raise_for_status()
for item in resp.json():
    print(item["videoId"], item["language"], item["segmentCount"])
```

#### JavaScript (Node.js 18+)

```javascript
const res = await fetch(
    'https://api.apify.com/v2/acts/himanshu-data~youtube-transcript-api/run-sync-get-dataset-items',
    {
        method: 'POST',
        headers: {
            Authorization: `Bearer ${process.env.APIFY_TOKEN}`,
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({ videoUrls: ['https://www.youtube.com/watch?v=jNQXAC9IVRw'], languages: ['en'] }),
    },
);
if (!res.ok) throw new Error(`Request failed with status ${res.status}`);
const items = await res.json();
console.log(items.map((i) => `${i.videoId} ${i.segmentCount}`));
```

#### Large batches (asynchronous)

For many videos, start a run and poll it instead of waiting on one request:

1. Start: `POST https://api.apify.com/v2/acts/himanshu-data~youtube-transcript-api/runs` with the input as JSON body. The response contains `data.id` (run ID) and `data.defaultDatasetId`.
2. Poll: `GET https://api.apify.com/v2/actor-runs/RUN_ID?waitForFinish=60` until `data.status` is `SUCCEEDED`, `FAILED`, `ABORTED` or `TIMED-OUT`.
3. Fetch items: `GET https://api.apify.com/v2/datasets/DATASET_ID/items?format=json`.
4. Fetch the summary: `GET https://api.apify.com/v2/actor-runs/RUN_ID/key-value-store/records/SUMMARY`.

Send the same `Authorization: Bearer APIFY_TOKEN` header on every request.

# Actor input Schema

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

YouTube video URLs (watch, youtu.be, shorts, live, embed) or 11 character video IDs. Playlists and channels are not supported.

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

Caption language codes in order of preference, for example en, es or pt-BR. Regional variants match too (en matches en-US). Manual captions are preferred over auto generated ones in the same language.

## `fallbackToOriginal` (type: `boolean`):

If no requested language exists, return the video's original caption language instead. The output field matchedRequestedLanguage shows when this happened. Turn off to skip such videos.

## `includeSegments` (type: `boolean`):

Add a segments array with text, start, duration and end in seconds.

## `includeSrt` (type: `boolean`):

Add the transcript as an SRT subtitle string.

## `includeVtt` (type: `boolean`):

Add the transcript as a WebVTT subtitle string.

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

Maximum number of unique videos to process. Extra inputs are skipped before any request.

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

Residential proxies are recommended. Disable proxies only for local testing.

## Actor input object example

```json
{
  "videoUrls": [
    "https://www.youtube.com/watch?v=jNQXAC9IVRw"
  ],
  "languages": [
    "en"
  ],
  "fallbackToOriginal": true,
  "includeSegments": true,
  "includeSrt": false,
  "includeVtt": false,
  "maxVideos": 100,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

One item per video with a saved transcript.

## `summary` (type: `string`):

Counts, per video outcomes and failure codes.

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

// Run the Actor and wait for it to finish
const run = await client.actor("himanshu-data/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=jNQXAC9IVRw"],
    "languages": ["en"],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("himanshu-data/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=jNQXAC9IVRw"
  ],
  "languages": [
    "en"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call himanshu-data/youtube-transcript-api --silent --output-dataset

```

## MCP server setup

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