# YouTube Transcript Scraper (`apihq/youtube-transcript-scraper`) Actor

Scrape YouTube transcripts, captions, and subtitles as clean timestamped JSON. No YouTube Data API key or quota, no browser. One video or batch up to 50. Returns caption segments plus video metadata and languages. Pay only for successful transcripts at $3 per 1,000; failed videos never charge.

- **URL**: https://apify.com/apihq/youtube-transcript-scraper.md
- **Developed by:** [apihq dev](https://apify.com/apihq) (community)
- **Categories:** Videos, AI, Developer tools
- **Stats:** 47 total users, 20 monthly users, 97.8% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.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/platform/actors/running/actors-in-store#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 Scraper

Extract time-coded transcripts from any public YouTube video as clean JSON. No YouTube Data API key, no quota, no browser. Send one video ID or a batch of up to 50.

The contract is simple: a video with no captions returns `success:false` and costs nothing, and you only pay for transcripts actually delivered. $3 per 1,000 transcripts.

### See one run

Input:

```json
{
  "videoIds": ["jNQXAC9IVRw", "00000000000"],
  "language": "en",
  "metadata": true
}
```

What comes back:

- 1 `success: true` transcript for `jNQXAC9IVRw` ("Me at the zoo"), English track, with the video's title, author, duration, and thumbnail attached, billed
- 1 `success: false` row, `code: PLAYABILITY`, for the unplayable ID, not billed
- Run status: **SUCCEEDED**
- Charged: 1 transcript

`videoIds` alone works too. The knobs above are optional: `language` requests a specific caption track, and `metadata` attaches the video's context so you get more than caption text. Omit `language` to use the default track picker. Good IDs return billed transcripts, videos with no fetchable captions return free `success:false` rows in the same dataset, and the run still succeeds so one bad ID never costs you a batch.

### Why this Actor

- **You pay only for transcripts delivered.** Videos with no fetchable transcript come back as `success:false` and never trigger a charge.
- **Every failure is machine-readable.** Each carries a stable `code` (`NO_CAPTIONS`, `PLAYABILITY`, `EMPTY_TRANSCRIPT`, `VALIDATION_FAILED`, `DEADLINE_EXCEEDED`, `PO_TOKEN_REQUIRED`), so your pipeline branches on the error type without parsing English.
- **A 30-second deadline per video.** If the upstream stalls you get a clean `504` with `code: DEADLINE_EXCEEDED` and no charge, not a 60-minute hang that drains your budget.
- **No YouTube Data API key or quota.** The official Data API's captions endpoint is OAuth-gated and restricted for third-party download. This takes a plain video ID and returns JSON. (You still run it with your normal Apify account, like any Actor.)

### Best for

AI and data workflows that turn videos into text: RAG pipelines and LLM training data, search indexing over spoken words, sentiment/topic/brand-safety analysis, and subtitle and accessibility tooling. The stable error codes and never-fail-a-run behavior matter most when transcripts feed an automated pipeline, where one caption-less video should never take down the batch. It works fine for one-off manual pulls too.

### Input example

A single video with metadata included:

```json
{
  "videoId": "jNQXAC9IVRw",
  "metadata": true
}
```

A batch of three videos in a specific language:

```json
{
  "videoIds": [
    "jNQXAC9IVRw",
    "dQw4w9WgXcQ",
    "kJQP7kiw5Fk"
  ],
  "language": "en",
  "metadata": true
}
```

The Actor caps batch input at 50 video IDs per run. For larger workloads, launch parallel runs.

### Output example

A successful extraction (the actual response for "Me at the zoo", the first ever YouTube upload):

```json
{
  "success": true,
  "video_id": "jNQXAC9IVRw",
  "language": "en",
  "transcript": [
    { "text": "All right, so here we are, in front of the elephants", "start": 1.2, "duration": 2.16 },
    { "text": "the cool thing about these guys is that they have really really really long trunks", "start": 5.318, "duration": 7.298 },
    { "text": "and that's pretty much all there is to say", "start": 16.881, "duration": 2 }
  ],
  "is_auto_generated": false,
  "available_tracks": [
    { "language_code": "en", "language_name": "English", "is_auto_generated": false },
    { "language_code": "de", "language_name": "German", "is_auto_generated": false }
  ],
  "metadata": {
    "title": "Me at the zoo",
    "author_name": "jawed",
    "author_url": "https://www.youtube.com/channel/UC4QobU6STFB0P71PMvOGN5A",
    "thumbnail_url": "https://i.ytimg.com/vi/jNQXAC9IVRw/hqdefault.jpg",
    "channel_id": "UC4QobU6STFB0P71PMvOGN5A",
    "duration_seconds": 19
  }
}
```

A video with no fetchable transcript lands in the same dataset and is not charged:

```json
{
  "success": false,
  "video_id": "00000000000",
  "code": "PLAYABILITY",
  "error": "Video 00000000000 is not playable: This video is unavailable",
  "request_id": "req_911f37d2e55644ff9d9e4a3f",
  "status_code": 403
}
```

Branch on the `success` field to split hits from misses. Quote the `request_id` in any support issue and we can trace the exact request.

### What you get

Every processed video produces one record. Successful extractions carry `success: true` with this shape:

| Field | Type | What it is |
|---|---|---|
| `success` | boolean | `true` for a fetched transcript, `false` for a video with no fetchable transcript. |
| `video_id` | string | The 11-character YouTube video ID. |
| `language` | string | Language code of the selected caption track. |
| `transcript` | array | Time-coded caption segments. Each has `text`, `start`, and `duration`. |
| `is_auto_generated` | boolean | True if the selected track is YouTube's ASR. |
| `available_tracks` | array | Every caption track the video exposes, with language code and name. |
| `metadata` | object | Video title, channel name, channel URL, thumbnail URL, channel ID, duration in seconds. Present when `metadata: true` (default). |

Videos with no fetchable transcript come back with `success: false`, plus `video_id`, the machine-readable `code`, a human-readable `error`, the service `request_id`, and the HTTP `status_code`. They are not charged.

### Pricing

Pay-per-result. Each successful transcript costs `$0.003`, which is `$3.00 per 1,000 transcripts`. The charge fires only when a transcript is delivered.

- 1,000 delivered transcripts cost $3.
- A batch of only caption-less or unplayable videos costs $0.

Apify subscriber discounts apply automatically: roughly `$2.40 per 1,000` on the Scale plan and `$1.80 per 1,000` on Business. Platform compute is included in the per-transcript price. Cap the maximum spend of a single run from Apify's Run Limits panel; the Actor honors the cap and reports how many videos completed.

### What this Actor does not do

Honest scope, so you know before you run it:

- **One video at a time, or batches of up to 50 IDs.** It does not crawl a whole channel or playlist. To collect the IDs first, use the [YouTube Channel Scraper](https://apify.com/apihq/youtube-channel-scraper) for long-form uploads or the [YouTube Shorts Scraper](https://apify.com/apihq/youtube-shorts-scraper) for Shorts, then feed the IDs here.
- **It reads captions that already exist** (manual or auto-generated). It does not transcribe audio, so a video with no caption track returns `success:false` with `code: NO_CAPTIONS`.
- **Public videos only.** Private, members-only, and age-gated videos return `success:false`.

### How to use this Actor

1. Open the Actor in the Apify Console.
2. Set `videoId` (single), `videoIds` (a list), or both. Values are merged and de-duplicated, and up to 50 unique IDs are processed per run. Optionally pass `language` (e.g. `en`, `de`, `en-US`) and `metadata` (defaults to true).
3. Click Start. Each processed video produces one record: `success: true` with the transcript, or `success: false` with the reason. Only successes are charged.

The Actor is also callable from the Apify API and every official integration (Make, Zapier, n8n, Slack, webhooks). The API tab in the Console has ready-to-paste JavaScript, Python, and curl snippets.

### Reliability

**Hard 30-second deadline per video.** If YouTube or the proxy network slows down, you get a `504` with `code: DEADLINE_EXCEEDED` for that video. You never wait minutes for a single result.

**Empty transcripts are treated as failures.** If YouTube returns a caption track that parses to zero segments, the record comes back as `success: false` with `code: EMPTY_TRANSCRIPT` and is not billed. You never pay for a blank transcript.

**Stable error taxonomy.** Every failure has a `code` your code can branch on, stable across releases: `NO_CAPTIONS`, `PLAYABILITY`, `EMPTY_TRANSCRIPT`, `VALIDATION_FAILED`, `DEADLINE_EXCEEDED`, `PO_TOKEN_REQUIRED`, `CAPTIONS_TIMEOUT`, `NO_INPUT`. Every error also carries a `request_id` for support correlation.

### FAQ

**Do I need a YouTube Data API key?**

No. The Actor reads YouTube's public data directly and does not consume Google API quota.

**Which videos work?**

Public videos with caption tracks. Manual and auto-generated (ASR) captions both work. Private, age-gated, or members-only videos return `success:false`.

**What if a video has no captions?**

It comes back as a `success: false` record with `code: NO_CAPTIONS`. You are not charged.

**Which languages are supported?**

Every language the creator uploaded. Pass a `language` code (such as `en`, `de`, or `en-US`) to pick a track; omit it and the Actor prefers manual English, then auto-generated English, then the first available track.

**Can I get transcripts for a whole channel?**

Not directly. This Actor takes single video IDs or batches of up to 50. Collect a channel's video IDs first with the [YouTube Channel Scraper](https://apify.com/apihq/youtube-channel-scraper) for long-form uploads or the [YouTube Shorts Scraper](https://apify.com/apihq/youtube-shorts-scraper) for Shorts, then feed them here.

**Can I use this Actor from my own code?**

Yes. Use the Apify API or one of the official SDKs (Node.js: `apify-client`, Python: `apify-client`). The Console shows ready-to-paste code samples on the API tab.

**How does billing know a video failed?**

Billing fires on an explicit per-transcript charge event, not on dataset writes. The Actor only fires it when a transcript is delivered, so `success: false` records sit in your dataset for free. You still get every result in one place and branch on `success` and `code`, with a `request_id` for support correlation.

**What happens if YouTube rate-limits the request?**

The request retries with exponential backoff. If it still cannot fetch within the 30-second deadline, the video comes back as `success: false` with `code: DEADLINE_EXCEEDED`. You are not charged.

### Found a bug or want a feature?

Open an issue on this Actor's Issues tab and include the `request_id` from any error record you saw. We respond within one business day.

# Actor input Schema

## `videoId` (type: `string`):

11-character YouTube video ID. Use this for a single transcript. An invalid ID comes back as a success:false record (code VALIDATION\_FAILED), not a rejected run.

## `videoIds` (type: `array`):

List of 11-character YouTube video IDs. Merged with videoId and de-duplicated; up to 50 unique IDs are processed per run and any beyond that are dropped. Invalid IDs come back as success:false records, so one bad ID never blocks the batch.

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

ISO 639-1 language code, optionally with region (e.g. 'en', 'en-US', 'de'). If omitted, picks manual English first, then auto-generated English, then the first available track.

## `metadata` (type: `boolean`):

If true (default), include title, author, duration, and thumbnail in each transcript record.

## Actor input object example

```json
{
  "videoId": "jNQXAC9IVRw",
  "metadata": true
}
```

# Actor output Schema

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

One record per processed video. success:true carries the transcript; success:false carries the error code and message.

# 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 = {
    "videoId": "jNQXAC9IVRw"
};

// Run the Actor and wait for it to finish
const run = await client.actor("apihq/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 = { "videoId": "jNQXAC9IVRw" }

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

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

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

```

## CLI example

```bash
echo '{
  "videoId": "jNQXAC9IVRw"
}' |
apify call apihq/youtube-transcript-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=apihq/youtube-transcript-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/LAZQMLgS0xvmkVVHJ/builds/xV0PmD176bDFj8xFm/openapi.json
