# YouTube Transcript Scraper API - Captions and Subtitles (`neverempty/youtube-transcript-reliable`) Actor

Extracts YouTube transcripts (manual captions and auto-generated). Tells you whether a video is private, has no captions, or was blocked - instead of returning a blank row - and charges only for transcripts that actually contain text.

- **URL**: https://apify.com/neverempty/youtube-transcript-reliable.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** Videos, AI, MCP servers
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$8.00 / 1,000 transcript returneds

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?

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 API - Captions and Subtitles

Get the transcript of any public YouTube video — manual captions and auto-generated ones — as clean text, or as timestamped lines.

**The difference is what happens when something goes wrong.** Most transcript scrapers return an empty result whether the video has no captions, the video is private, or YouTube blocked the request. You cannot tell which, so you cannot fix it. This one tells you, in plain words, and **only charges you for transcripts that actually contain text**.

### Measured, not claimed

A run of 15 videos on 2026-08-25, from the shipped build:

- **14 returned a transcript.** 83,647 characters total.
- **1 did not — and it said why: "この動画は非公開です" (this video is private).** Nobody can scrape that one. It was not silently dropped, and it was not charged.
- 48.7 seconds for all 15.

### What you get

| Field | Example |
|---|---|
| `ok` | `true` / `false` — did this video produce a transcript |
| `videoId` / `url` | `aircAruvnKk` |
| `title` / `author` | `But what is a neural network?` / `3Blue1Brown` |
| `lengthSeconds` | `1132` |
| `language` | `en` |
| `isAutoGenerated` | `false` — manual captions are preferred when both exist |
| `availableLanguages` | `["en","es","fr","ja", ...]` |
| `segmentCount` | `286` |
| `text` | The whole transcript as one string |
| `segments` | `[{ "start": 12.4, "duration": 3.1, "text": "..." }, ...]` |
| `reason` | Why it failed, when `ok` is `false` |

### Why transcripts come back empty elsewhere

The caption URL published on a YouTube watch page returns **zero bytes** for every format — measured on every video tried. Anything built on that path silently produces nothing. This Actor does not use it. It reads the same endpoint the official mobile apps use, which still serves captions, and it requests only the fields it needs, so a lookup transfers about 19 KB instead of 228 KB.

That is also why it is cheap to run at volume.

### Honest failure reasons

When a video does not produce a transcript, you get one of these in `reason` — never a blank row:

- `この動画は非公開です` — private video
- `メンバー限定の動画です` — members-only
- `年齢制限のある動画です` — age-restricted
- `この地域では再生できない動画です` — geo-blocked
- `この動画に字幕がありません` — the video genuinely has no captions
- `ボット判定でブロック` — YouTube rate-limited the request; retried automatically with a fresh IP first

**A private video and a blocked request are not the same problem, and this Actor does not pretend they are.**

### Input

```json
{
  "videos": [
    "https://www.youtube.com/watch?v=aircAruvnKk",
    "https://youtu.be/dQw4w9WgXcQ",
    "M7lc1UVf-VE"
  ],
  "languages": ["en"],
  "includeTimestamps": true,
  "proxyMode": "auto"
}
```

Watch URLs, `youtu.be` links, Shorts URLs, embed URLs, and bare 11-character IDs all work.

| Field | Default | Meaning |
|---|---|---|
| `videos` | *(required)* | URLs or IDs |
| `languages` | `["en"]` | Preferred order. Manual captions win over auto-generated |
| `includeTimestamps` | `true` | Include per-line `segments` alongside the full `text` |
| `proxyMode` | `auto` | `auto` tries datacenter first (cheaper), falls back to residential when YouTube blocks |
| `maxRetries` | `3` | Retries with a fresh IP on a block or a network failure |

### Pricing

**You are charged only when a transcript with actual text comes back.** Private videos, videos without captions, and blocked requests cost you nothing.

That matters more than the headline number: a cheaper Actor that charges for empty rows costs more per *usable* transcript.

### Typical uses

- Feeding video content to an LLM for summaries, Q\&A or RAG
- Searching across a channel's back catalogue
- Subtitling, translation and repurposing workflows
- Research and content analysis at scale

### FAQ

**Does it work on auto-generated captions?**
Yes. Manual captions are preferred when both exist, and `isAutoGenerated` tells you which you got.

**What about videos in other languages?**
Pass `languages` in your order of preference. `availableLanguages` lists everything the video has.

**Why does it need a proxy?**
YouTube rate-limits repeated caption requests from one address. Requests are retried from a fresh IP automatically. Residential proxying is used only when it is actually needed.

**Will I be charged for a video with no captions?**
No. Charging happens after a transcript with text is produced.

### Other tools by NeverEmpty

Every NeverEmpty Actor follows the same rule: it never returns an empty result to mean two different things, and it only charges for rows that actually carry an answer.

- **[github-repo-search](https://apify.com/neverempty/github-repo-search)** - GitHub repository search through the official API, with activity columns
- **[seo-site-audit](https://apify.com/neverempty/seo-site-audit)** - technical SEO audit: meta, canonical, robots, headings

### Support

If a video that should work does not, open an issue on the **Issues** tab with the video ID. The `reason` field on the failed row tells me most of what I need.

# Actor input Schema

## `videos` (type: `array`):

YouTube video URLs (watch, youtu.be, shorts, embed) or bare 11-character video IDs.

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

Language codes in order of preference, e.g. en, ja, es. Manual captions are preferred over auto-generated ones.

## `includeTimestamps` (type: `boolean`):

Include the per-line segments with start time and duration in addition to the full text.

## `proxyMode` (type: `string`):

auto = try datacenter first (cheaper), fall back to residential when YouTube blocks. Use residential if you scrape at volume.

## `maxRetries` (type: `integer`):

How many times to retry a video with a fresh IP when YouTube blocks or rate-limits the request.

## Actor input object example

```json
{
  "videos": [
    "https://www.youtube.com/watch?v=aircAruvnKk"
  ],
  "languages": [
    "en"
  ],
  "includeTimestamps": true,
  "proxyMode": "auto",
  "maxRetries": 3
}
```

# Actor output Schema

## `results` (type: `string`):

All rows produced by this run.

# 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 = {
    "videos": [
        "https://www.youtube.com/watch?v=aircAruvnKk"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/youtube-transcript-reliable").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 = { "videos": ["https://www.youtube.com/watch?v=aircAruvnKk"] }

# Run the Actor and wait for it to finish
run = client.actor("neverempty/youtube-transcript-reliable").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 '{
  "videos": [
    "https://www.youtube.com/watch?v=aircAruvnKk"
  ]
}' |
apify call neverempty/youtube-transcript-reliable --silent --output-dataset

```

## MCP server setup

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

```

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/kPRNnssI5G54gXyR6/builds/ntRiux5tKDkmYm8hq/openapi.json
