# YouTube Transcript & Subtitles Bulk — Multilingual, No API Key (`ntriqpro/youtube-transcript-bulk`) Actor

Bulk YouTube transcript and subtitles extractor. Up to 200 video URLs, a channel or a playlist in; one row per video out: full text, timestamped segments, language, manual or auto-generated, word count, duration, verdict. Multilingual with auto-translation. No API key, no cookies, no video download.

- **URL**: https://apify.com/ntriqpro/youtube-transcript-bulk.md
- **Developed by:** [daehwan kim](https://apify.com/ntriqpro) (community)
- **Categories:** Videos, Social media
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 transcript fetcheds

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?

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 & Subtitles Bulk — Multilingual, No API Key

**Paste a list of YouTube videos, get every transcript back as clean JSON.** This
**YouTube transcript scraper** and **subtitles extractor** reads up to 200 videos
per run — or a whole channel or playlist — and returns one row per video: the
full plain text, timestamped `segments`, the caption language, whether it was
manually written or auto-generated, word count, duration and a one-line verdict
such as `12 min, 1,840 words, auto-generated EN`.

No YouTube API key, no cookies, no browser. Pick your languages in priority
order and the Actor finds the best caption track for each video — manual first,
then auto-generated, then YouTube's own auto-translation — and tells you which
one you got. Feed the rows into an LLM summariser, a search index, a content
audit or a research corpus straight from the Apify API, a webhook, Google
Sheets, Zapier or Make.

Powered by **[youtube-transcript-api](https://github.com/jdepoix/youtube-transcript-api)**
(MIT), with **[yt-dlp](https://github.com/yt-dlp/yt-dlp)** as a metadata-only
fallback engine for caption tracks the first engine cannot serve.

> **Unofficial.** This Actor is **not affiliated with, endorsed by, or sponsored
> by** YouTube or Google. It reads only caption tracks the video already exposes
> publicly and **never downloads or delivers video or audio files**. See
> `LICENSE-NOTICE.md` and the disclaimer section below.

### Why use YouTube Transcript & Subtitles Bulk?

- **Bulk, not one video at a time** — up to 200 video URLs per run, plus a
  `channelUrl` or `playlistUrl` that is expanded into its newest videos, plus a
  `datasetId` from any YouTube search or channel scraper on Apify. Duplicates
  are removed across all sources.
- **Multilingual with a real priority list** — `["en", "en-US", "ko"]` means:
  a manual English track if there is one, else an auto-generated English track,
  else YouTube's auto-translation into English, else (optionally) the video's own
  language. Every row says exactly which it got: `language`, `isAutoGenerated`,
  `isTranslated`, `matchedRequestedLanguage`.
- **Two engines** — when the primary engine is blocked or has no track in your
  language, yt-dlp lists the same video's caption tracks (including all
  auto-translations) and the Actor fetches the caption file directly. The row's
  `engine` and `attempts` fields show the path taken.
- **Both formats at once** — `fullText` for LLMs and search, `segments` with
  `start` / `dur` / `text` for subtitle files, clipping and timestamps. Choose
  `outputFormat` to keep only one.
- **A verdict, not just text** — `headline`, `wordCount`, `segmentCount`,
  `durationSec`, `channelName`, `videoTitle` and the full `availableLanguages`
  list on every row, so you can triage 200 videos from the table view alone.
- **Honest about what it cannot get** — a video with captions disabled, a
  deleted or private video, an age-restricted video or a bad URL becomes a
  `notice` row with a code (`transcripts-disabled`, `video-unavailable`,
  `age-restricted`, `invalid-url`, `ip-blocked`, …). Scheduled runs keep going;
  the run still ends as SUCCEEDED.
- **No key, no login, no bypassing** — public caption endpoints only, an honest
  bot User-Agent, no CAPTCHA or bot-protection circumvention. YouTube's own
  rate limits are reported, not evaded.

### How to use YouTube Transcript & Subtitles Bulk

1. Paste video URLs into **Video URLs or IDs** (watch, shorts, youtu.be, embed
   or bare 11-character IDs), and/or set a **Channel URL** or **Playlist URL**.
2. Put your preferred **Languages** in order, e.g. `en`, `de`, `ja`.
3. Click **Start**. Read the rows in the **Output** tab or pull them through the
   Apify API. Filter on `rowType == "transcript"` for the transcripts.

Reading a channel's newest uploads every day? Pair it with our
[RSS Feed Reader](https://apify.com/ntriqpro/rss-feed-reader) on a schedule —
every YouTube channel publishes an RSS feed — and pass the new links here. For
videos that come back as `no-transcript`, our
[Whisper Speech to Text](https://apify.com/ntriqpro/whisper-speech-to-text)
transcribes from the audio.

### Input

| Field | Type | Description |
|---|---|---|
| `videoUrls` | array | Up to 200 video URLs or 11-character IDs. |
| `channelUrl` | string | Channel URL (`https://www.youtube.com/@handle`); its newest uploads are added, up to `maxVideos`. |
| `playlistUrl` | string | Playlist URL; its videos are added, up to `maxVideos`. |
| `maxVideos` | integer | Videos taken from the channel and from the playlist (each), 1–200. Default `50`. |
| `datasetId` | string | Apify dataset whose rows carry a `url`, `videoUrl`, `link`, `videoId` or `id` field. |
| `languages` | array | Language codes in priority order. Default `["en"]`. |
| `allowAutoGenerated` | boolean | Accept auto-generated (ASR) tracks. Default `true`. |
| `allowTranslated` | boolean | Accept YouTube's auto-translation into the first language. Default `true`. |
| `fallbackToAnyLanguage` | boolean | If nothing matches, return the video's own track and flag `matchedRequestedLanguage: false`. Default `true`. |
| `outputFormat` | string | `both` (default), `fulltext` or `segments`. |
| `proxyConfiguration` | object | Apify proxy settings. Default: Apify **RESIDENTIAL** proxy, because YouTube blocks nearly every datacenter and cloud IP (measured: 1 of 8 videos through datacenter IPs, 3 of 3 through residential). Proxy usage is included in the price. |
| `disclaimerAck` | boolean | Acknowledge that this is an unofficial tool and that you are responsible for how the transcripts are used. Required. |

The whole run is capped at 200 videos across all sources.

```json
{
  "videoUrls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "https://www.youtube.com/watch?v=jNQXAC9IVRw",
    "https://youtu.be/9bZkp7q19f0"
  ],
  "channelUrl": "https://www.youtube.com/@Apify",
  "maxVideos": 20,
  "languages": ["en", "ko"],
  "allowAutoGenerated": true,
  "allowTranslated": true,
  "outputFormat": "both",
  "disclaimerAck": true
}
```

### Output

Every dataset row carries a `rowType`:

| `rowType` | What it is | Charged |
|---|---|---|
| `transcript` | One video's transcript — text, segments, language, verdict | yes, once per video |
| `notice` | Status rows: one per video without a transcript (`transcripts-disabled`, `no-transcript`, `video-unavailable`, `video-unplayable`, `age-restricted`, `invalid-video-id`, `ip-blocked`, `rate-limited`, `fetch-failed`), plus `summary`, `invalid-url`, `channel-listing-failed`, `playlist-listing-failed`, `dataset-read-failed`, `no-input`, `targets-truncated`, `free-plan-cap`, `charge-limit-reached`, `billing-unavailable`, `invalid-input` | no |

A transcript row:

```json
{
  "rowType": "transcript",
  "videoId": "4KgZ83cultE",
  "url": "https://www.youtube.com/watch?v=4KgZ83cultE",
  "videoTitle": "After eight years in finance, she became an engineer | Kateřina Hroníková",
  "channelName": "Apify",
  "language": "en",
  "languageName": "English (auto-generated)",
  "isAutoGenerated": true,
  "isTranslated": false,
  "matchedRequestedLanguage": true,
  "headline": "4 min, 696 words, auto-generated EN",
  "wordCount": 696,
  "segmentCount": 102,
  "durationSec": 259.2,
  "fullText": "It's never too late. And it's totally worth it. It can be …",
  "segments": [
    { "start": 0.0, "dur": 5.0, "text": "It's never too late." },
    { "start": 2.0, "dur": 6.4, "text": "And it's totally worth it. It can be" }
  ],
  "availableLanguages": [
    { "code": "en", "name": "English (auto-generated)", "isAutoGenerated": true }
  ],
  "engine": "youtube-transcript-api",
  "attempts": ["youtube-transcript-api:ok"],
  "sourceList": "https://www.youtube.com/@Apify",
  "fetchedAt": "2026-09-03T01:31:59.816086+00:00",
  "disclaimer": "Unofficial. …"
}
```

A video without a usable track:

```json
{
  "rowType": "notice",
  "noticeCode": "transcripts-disabled",
  "videoId": "XXXXXXXXXXX",
  "url": "https://www.youtube.com/watch?v=XXXXXXXXXXX",
  "headline": "XXXXXXXXXXX — transcripts-disabled: Captions are disabled for this video by its owner.",
  "attempts": ["youtube-transcript-api:transcripts-disabled", "yt-dlp:no-transcript"]
}
```

The `summary` notice row at the end carries `transcriptsDelivered`,
`videosWithoutTranscript`, `wordsTotal`, `languageDistribution`,
`noticeDistribution` and `fallbackEngineUsed` for the whole run.

### Pricing

Pay per event. You are charged once when a run starts and once for every
`transcript` row written to the dataset — one per video. Notice rows (videos
without a transcript, the run summary, input problems) are not results and are
not charged.

| Event | Price |
|---|---|
| Run started | $0.001 per run |
| Transcript fetched | $0.007 per video (lower on higher Apify plans) |

Prices are shown on the Actor's Pricing tab. Free-plan runs return up to 25
transcripts per run and then finish cleanly with a `free-plan-cap` notice.

### Tips

- **Long videos are one row.** A 3-hour lecture is a single transcript row with
  a `segments` array of a few thousand entries; if you only need the text, set
  `outputFormat: "fulltext"` to keep rows small.
- **Language codes are exact.** YouTube distinguishes `en`, `en-US` and `en-GB`.
  List the variants you accept, or rely on `allowTranslated` /
  `fallbackToAnyLanguage`.
- **`ip-blocked` or `rate-limited` rows** mean YouTube throttled the request,
  which happens on almost every datacenter IP and, even through residential
  proxies, on auto-translated tracks. Keep the default RESIDENTIAL proxy, re-run
  those videos later, or request the original language.
- **Chaining:** run any YouTube channel, search or hashtag scraper first, then
  pass its dataset ID in `datasetId` to transcribe everything it found.
- **Subtitle files:** `segments` maps 1:1 to SRT / VTT cues (`start`, `dur`,
  `text`); converting is a few lines of code.

### FAQ, disclaimers, and support

**Is this an official YouTube product?** No. It is an independent integration of
two open-source libraries and is not affiliated with, endorsed by, or sponsored
by YouTube or Google.

**Does it download videos?** No. Both engines are used for caption metadata
only; the Actor never requests, stores or delivers a video or audio file.

**Whose data is it, and who is responsible?** Transcripts are the copyrighted
work of the video's author. Under the
[Apify Standard Actor Contract](https://docs.apify.com/legal/standard-actor-contract)
(§2.2, §5.2.1) you are the data controller and you are responsible for
complying with YouTube's Terms of Service, copyright law and any other
applicable law in how you store, redistribute or reuse the text. Use this Actor
for lawful purposes such as research, accessibility, indexing and analysis you
are permitted to carry out.

**Does it work on private, unlisted or age-restricted videos?** Private and
age-restricted videos require a signed-in session, which this Actor does not
use; they come back as notice rows. Unlisted videos work if you have the link.

**What about videos with no captions at all?** They come back as
`no-transcript`. Transcribe them from the audio with
[Whisper Speech to Text](https://apify.com/ntriqpro/whisper-speech-to-text).

**Where do I report a problem?** Open an issue on the Actor's **Issues** tab.
Include the `videoId` and the `attempts` field from the row.

# Actor input Schema

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

Up to 200 YouTube video URLs (watch, shorts, youtu.be, embed, live) or bare 11-character video IDs. Combine with a channel, a playlist or a dataset below; duplicates are removed. Free plan: each run returns up to 25 results. Paid Apify plans receive the full result set.

## `channelUrl` (type: `string`):

A YouTube channel URL such as https://www.youtube.com/@Apify. Its newest uploads are added to the list, up to Max videos from a channel or playlist. Listing uses yt-dlp metadata only; no media is downloaded.

## `playlistUrl` (type: `string`):

A YouTube playlist URL such as https://www.youtube.com/playlist?list=PL.... Its videos are added to the list, up to Max videos from a channel or playlist.

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

How many videos to take from the channel and from the playlist (each). The whole run is capped at 200 videos.

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

ID of an Apify dataset produced by another Actor (for example a YouTube channel or search scraper). Video IDs are read from its url, videoUrl, link, videoId or id fields.

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

BCP-47 language codes in order of preference, e.g. \["en", "en-US", "ko"]. For each video the first matching manually created track wins, then the first matching auto-generated track, then an auto-translation into the first code.

## `allowAutoGenerated` (type: `boolean`):

Use YouTube's automatic speech-recognition captions when no manually created track matches. Rows carry isAutoGenerated so you can tell them apart.

## `allowTranslated` (type: `boolean`):

When no track matches any requested language, translate an existing track into the first requested language using YouTube's own translation. Rows carry isTranslated.

## `fallbackToAnyLanguage` (type: `boolean`):

When nothing matches the requested languages (and translation is unavailable or throttled), return the caption track the video does have, in its original language. Rows carry matchedRequestedLanguage=false so you can filter them.

## `outputFormat` (type: `string`):

both = fullText and timestamped segments on every row; fulltext = only the plain text; segments = only the \[{start, dur, text}] array.

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

YouTube blocks almost every datacenter and cloud IP (measured 2026-09-03: 1 of 8 videos succeeded through datacenter proxies, 3 of 3 through residential). Keep the default Apify RESIDENTIAL proxy unless you know your own proxies are clean.

## `disclaimerAck` (type: `boolean`):

This Actor is not affiliated with YouTube or Google. It reads only public caption tracks and never downloads video or audio. Transcripts are copyrighted works: you are the data controller (Apify Standard Actor Contract §2.2, §5.2.1) and responsible for complying with YouTube's Terms of Service and applicable law in how you use them.

## Actor input object example

```json
{
  "videoUrls": [
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "https://www.youtube.com/watch?v=jNQXAC9IVRw",
    "https://youtu.be/9bZkp7q19f0"
  ],
  "maxVideos": 50,
  "languages": [
    "en"
  ],
  "allowAutoGenerated": true,
  "allowTranslated": true,
  "fallbackToAnyLanguage": true,
  "outputFormat": "both",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "disclaimerAck": true
}
```

# Actor output Schema

## `transcripts` (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 = {
    "videoUrls": [
        "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
        "https://www.youtube.com/watch?v=jNQXAC9IVRw",
        "https://youtu.be/9bZkp7q19f0"
    ],
    "maxVideos": 50,
    "languages": [
        "en"
    ],
    "allowAutoGenerated": true,
    "allowTranslated": true,
    "fallbackToAnyLanguage": true,
    "outputFormat": "both",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    },
    "disclaimerAck": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("ntriqpro/youtube-transcript-bulk").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=dQw4w9WgXcQ",
        "https://www.youtube.com/watch?v=jNQXAC9IVRw",
        "https://youtu.be/9bZkp7q19f0",
    ],
    "maxVideos": 50,
    "languages": ["en"],
    "allowAutoGenerated": True,
    "allowTranslated": True,
    "fallbackToAnyLanguage": True,
    "outputFormat": "both",
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
    "disclaimerAck": True,
}

# Run the Actor and wait for it to finish
run = client.actor("ntriqpro/youtube-transcript-bulk").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=dQw4w9WgXcQ",
    "https://www.youtube.com/watch?v=jNQXAC9IVRw",
    "https://youtu.be/9bZkp7q19f0"
  ],
  "maxVideos": 50,
  "languages": [
    "en"
  ],
  "allowAutoGenerated": true,
  "allowTranslated": true,
  "fallbackToAnyLanguage": true,
  "outputFormat": "both",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "disclaimerAck": true
}' |
apify call ntriqpro/youtube-transcript-bulk --silent --output-dataset

```

## MCP server setup

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

```

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/xsZ6pwev1E6ByTlAu/builds/ur270IGWPl2AjtU3X/openapi.json
