# YouTube Transcript Scraper - Timestamps, No API Key or Login (`tidytools/youtube-transcript`) Actor

YouTube transcript API with no YouTube API key or login. Get transcripts with timestamps from videos, channels or playlists, from manual or auto captions in any language, plus video details. Optional speech-to-text. $2 per 1,000 transcripts ($0.002 each). Failed videos are free.

- **URL**: https://apify.com/tidytools/youtube-transcript.md
- **Developed by:** [Yukai Lin](https://apify.com/tidytools) (community)
- **Categories:** Videos, AI
- **Features:** Limited permissions
- **Stats:** 11 total users, 7 monthly users, 99.4% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.60 / 1,000 video 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: transcripts with timestamps, no API key or login

Paste YouTube video, channel or playlist links. You get one row per video with the full transcript, timestamped segments and video details. The Actor turns YouTube subtitles and captions into text. It needs no YouTube API key, cookies or login.

**Price: $0.002 per video whose transcript was returned ($2 per 1,000, FREE/BRONZE rate).** There is no start fee. Failed videos, unavailable videos and videos without captions are free. Speech-to-text for videos without captions is off by default. When you turn it on, it costs $0.006 per audio minute.

**Input** (prefilled; one 19-second video that finishes in seconds and costs $0.002):

```json
{ "videos": ["https://www.youtube.com/watch?v=jNQXAC9IVRw"] }
```

**Output** (real result, shortened):

```json
{
    "success": true,
    "videoId": "jNQXAC9IVRw",
    "title": "Me at the zoo",
    "language": "en",
    "source": "manual_captions",
    "transcript": "All right, so here we are, in front of the elephants the cool thing about these guys is that they have really... really really long trunks and that's cool (baaaaaaaaaaahhh!!) and that's pretty much all there is to say",
    "segments": [{ "start": 1.2, "duration": 2.16, "text": "All right, so here we are, in front of the elephants" }],
    "wordCount": 39,
    "failureReason": null
}
```

**Need English only?** Use `{"videos":["https://www.youtube.com/watch?v=jNQXAC9IVRw"],"languages":["en"]}`; this URL demonstrates the setting, not a mismatch. For a *different* video with only Arabic captions, an illustrative default Dataset row is `{"success":false,"failureReason":"language_mismatch","availableLanguages":["ar"]}`. It returns no Arabic transcript and triggers 0 video charge events and 0 ASR charge events. The default row has no `charged` field; captions in another language do not trigger speech-to-text or translation.

Input (the default, copy and paste) → output (real result; a 19-second video, finishes in seconds):

```json
{ "videos": ["https://www.youtube.com/watch?v=jNQXAC9IVRw"] }
```

```json
{
    "success": true,
    "videoId": "jNQXAC9IVRw",
    "title": "Me at the zoo",
    "language": "en",
    "source": "manual_captions",
    "transcript": "All right, so here we are, in front of the elephants the cool thing about these guys is that they have really... really really long trunks and that's cool (baaaaaaaaaaahhh!!) and that's pretty much all there is to say",
    "segments": [{ "start": 1.2, "duration": 2.16, "text": "All right, so here we are, in front of the elephants" }],
    "wordCount": 39,
    "failureReason": null
}
```

**Price: $2 per 1,000 caption transcripts — $0.002 per video whose transcript was returned (FREE/BRONZE rate; no start fee; failed, unavailable and no-caption videos are free). Speech-to-text for videos without captions (Whisper, off by default) is $0.006 per audio minute.**

### What does YouTube Transcript Scraper do?

- **$2 per 1,000 caption transcripts** (FREE/BRONZE rate; speech-to-text for videos without captions is billed per audio minute), no start fee; failed and no-caption videos are free.
- **No YouTube API key, cookies or login**: paste video, channel or playlist links.
- **Every failure says why** (`failureReason`), and each result says which language and caption type (`source`) you got.
- Timestamps on by default (`segments`), Markdown video notes, RAG chunks that link to the second in the video and a channel digest, all at no extra charge.
- **Optional simple output:** set `compactTranscript: true` for `videoUrl` and caption segments with `start`, `end`, `text`; the default output still uses `url` and `start`, `duration`, `text`.
- **Optional subtitle downloads:** set `subtitleFormats: ["srt", "vtt"]` to add `subtitleSrtUrl` and `subtitleVttUrl` to successful rows. Select either format alone if preferred. These files are included at no extra charge.

For a channel's latest uploads: `{"channels": ["@mitocw"], "maxVideosPerSource": 10}`.

A successful video is one row with fields such as `videoId`, `title`, `channel`, `language`, `source`, `transcript`, `segments` and `wordCount`; failed videos come back as free rows with `success: false` and a `failureReason`. All fields, speech-to-text, RAG chunks and video notes: [Output example](#output-example).

#### How to get YouTube transcripts without an API key

1. Paste video links into **Video URLs**, or `@handles` and playlist URLs into **Channels and playlists**.
2. Optional: set **Preferred languages** (for example `en`, `es`).
3. Click **Start**, or call the Actor from the Apify API with your Apify token: no YouTube API key, cookies or login.
4. Each video with captions comes back with `transcript` and, unless you turn timestamps off, timestamped `segments`; export as JSON, CSV or Excel. 100 videos with captions cost $0.20 (100 × $0.002).

#### Why this one

- **$2 per 1,000 transcripts**, no start fee. The two most used YouTube transcript Actors charge $5 (starvibe) and $10 (pintostudio) per 1,000 ([comparison](#how-it-compares), checked 3 October 2026). Not the cheapest for captions: a few charge $1 or less.
- **Failed videos are free**, and each one says why (`failureReason`), plus which language you got and whether it came from manual captions, auto captions or speech-to-text.
- **Videos without captions**: optional speech-to-text (Whisper large-v3-turbo) at $0.006 per audio minute, off by default. The five most used transcript Actors have no priced speech-to-text event.
- **Notes, RAG chunks and a channel digest included**: a Markdown page per video, chunks that link to the second in the video (`&t=`), and only-new-videos runs for channels, at no extra charge.
- **No YouTube API key, no cookies, no login.** Public videos only.

### How much does it cost?

| Event | Price | When |
|---|---|---|
| Video transcript | **$2.00 / 1,000** ($0.002 each) | a video whose transcript was returned (captions) |
| Speech-to-text minute | **$6.00 / 1,000 minutes** ($0.006 per minute) | only when you turn speech-to-text on, per audio minute (rounded up per video) of a transcript returned by speech-to-text, instead of the video charge |

**No start fee**, no proxy costs, no platform usage on top. The price per event depends on your Apify plan tier:

| Plan tier | Video transcript | Speech-to-text minute |
|---|---|---|
| FREE, BRONZE | $0.002 | $0.006 |
| SILVER | $0.0018 | $0.0054 |
| GOLD, PLATINUM, DIAMOND | $0.0016 | $0.0048 |

The examples below use the FREE/BRONZE rates.

**Examples:** 1,000 videos with captions = $2.00. A 20-minute video without captions, transcribed = 20 × $0.006 = $0.12. A 19-second video transcribed with speech-to-text is billed one minute = $0.006.

#### Control your cost

- **Charged:** videos whose transcript was returned (`success: true`), at the event rates above. In rare cases a result can be saved but the charge fail; `SUMMARY.savedNotCharged` counts those.
- **Free:** every failure (`failureReason` set, `success: false`): no captions (with speech-to-text off), captions only in other languages, no speech (music, ambience), over your speech-to-text limits, private, members-only, age-restricted or removed videos, blocked or busy service, errors.
- **Cap it:** set your **maximum charge per run**. The Actor stops cleanly (`SUMMARY.status` is `LIMIT_REACHED`) and reserves the money for captions first, then speech-to-text minutes.
- Speech-to-text is **off by default**, so a normal run can never cost more than $0.002 per video.

### Features

Captions are the default (manual first, then auto-generated, in the language you ask for). For videos that have no captions at all you can switch on **speech-to-text** (Whisper large-v3-turbo, $0.006 per audio minute, off by default).

- **Videos, channels and playlists**: paste video links, `@handles`, channel URLs or playlist URLs. Channels give their latest uploads first.
- **Clear language results**: each item shows the languages you asked for (`requestedLanguages`), the language returned (`language`), whether it matched exactly or only the base language (`languageMatch`, e.g. `zh-Hans` returned for `zh-TW`), and where the text came from (`source`: `manual_captions`, `auto_captions` or `asr`).
- **Timestamps**: `segments` (start, duration, text) next to the full text. Styled "karaoke" captions are cleaned (no `<font>` tags, no repeated lines).
- **SRT and WebVTT files**: select `subtitleFormats` to get download links for successful videos. The files use the original segment times even when `includeTimestamps: false` hides `segments` from the dataset row. Failed videos have no subtitle files. No extra charge. If optional file storage fails, the transcript row remains available and reports `subtitleError`.
- **Ready-made outputs**: Markdown video notes, RAG chunks that link back to the moment in the video (`&t=`), and a "new videos" digest for scheduled channel runs. No extra charge.
- **Failed videos are free**, and each one has a `failureReason`: `captions_unavailable`, `language_mismatch`, `empty_text`, `blocked`, `video_unavailable`, `age_restricted`, `service_busy`, `service_unavailable` or `error`. If the transcript service is temporarily down or times out, every affected video gets a free `service_unavailable` row (with a retry hint) and the run still ends normally; run it again a few minutes later. Input problems get a free row too (`invalid_url`, `source_unavailable`, `no_videos_found`, `max_charge_too_low`) and the run still ends normally.
- **No YouTube API key, no cookies, no login.** Only public videos.

### Use cases

- **Video notes**: turn a talk, lecture or tutorial into a Markdown page for Notion, Obsidian or a doc. Each 3-minute section is headed by a link to that point of the video.
- **Channel digest**: schedule a channel daily and get only the new uploads, with links and the opening lines of each transcript. A run with nothing new costs nothing.
- **RAG and search data**: transcripts cut into time windows with a `url` that opens the video at that second, so an answer can cite "see 3:00". Ready for embeddings.
- **Research and content work**: quote search, subtitles in several languages, competitor or creator analysis, training data from videos you are allowed to use.

### Input

API calls also accept `urls`, `startUrls` (strings or `{url}` objects), `videoUrls`, `videoIds`, `url` and `youtubeUrls` when `videos` is empty; bare 11-character video IDs work in those fields too. Use `videos` or `channels` as the canonical fields.

| Field | What it does |
|---|---|
| Video URLs | Watch, youtu.be (also with `?t=`), Shorts, live-replay or mobile links, `attribution_link` links, or 11-character video ids. One per line or several in one cell; slightly broken pasted links (`/watch/?v=`, a trailing space or quote) still work. A channel or playlist link pasted here is expanded like one in the next field |
| Channels and playlists | `@handle`, `https://www.youtube.com/@handle`, `/channel/UC...` or playlist URLs |
| Max videos per channel or playlist | Latest N videos from each (default 20) |
| Max videos in total | Limit for the whole run (default 100, up to 500) |
| Preferred languages | Caption languages in order of preference (`en`, `es`, `zh-TW` ...) |
| Include timestamped segments | Adds `segments` next to the plain text (default on) |
| Download subtitle files (`subtitleFormats`) | Choose `srt`, `vtt`, or both; default `[]` keeps the usual output. Successful rows gain `subtitleSrtUrl` and/or `subtitleVttUrl`; no extra charge |
| Transcribe videos without captions | Speech-to-text for videos that have no caption track (default off) |
| Max minutes per video / per run | Speech-to-text limits (default 30 / 60). Your maximum charge per run also limits them |
| Add RAG chunks with timestamp links | Adds `chunks` (about 60 s each by default) with a link to that moment |
| Add video notes (Markdown) | Adds `notesMarkdown`: details plus the transcript in 3-minute sections with timestamp links |
| Only new channel videos | For schedules: skips channel/playlist videos processed in earlier runs (free) and saves a `DIGEST` page |

The default input is one 19-second video: it finishes in seconds and costs $0.002.

### How to use it

1. Paste video links (or channels, playlists) into **Video URLs** / **Channels and playlists**.
2. Optionally set **Preferred languages**, turn on **Add video notes** or **Add RAG chunks**.
3. Click **Start**, then export the results as JSON, CSV or Excel, or call the Actor from the API.

### Output example

When subtitle files are requested, these optional fields appear on successful rows. Download links appear in the **Transcripts (all columns)** view and the Dataset JSON; that view also shows a storage warning if one is needed. The example below predates this option and has no subtitle URLs.

| Output field | Meaning |
|---|---|
| `subtitleSrtUrl` | SRT download link when `srt` is selected and stored successfully |
| `subtitleVttUrl` | WebVTT download link when `vtt` is selected and stored successfully |
| `subtitleError` | Reason a requested file is missing; the transcript row remains available |

Real result (3 October 2026) for `https://www.youtube.com/watch?v=jNQXAC9IVRw` (human captions). The `description` and `keywords` fields are left out here.

```json
{
  "success": true,
  "status": "ok",
  "videoId": "jNQXAC9IVRw",
  "url": "https://www.youtube.com/watch?v=jNQXAC9IVRw",
  "title": "Me at the zoo",
  "channel": "jawed",
  "channelId": "UC4QobU6STFB0P71PMvOGN5A",
  "publishDate": "2005-04-23T20:31:52-07:00",
  "durationSec": 19,
  "viewCount": 439408167,
  "requestedLanguages": [],
  "language": "en",
  "languageMatch": null,
  "source": "manual_captions",
  "isGenerated": false,
  "availableLanguages": [{ "code": "en", "name": "English", "generated": false }, { "code": "de", "name": "German", "generated": false }],
  "transcript": "All right, so here we are, in front of the elephants the cool thing about these guys is that they have really... really really long trunks and that's cool (baaaaaaaaaaahhh!!) and that's pretty much all there is to say",
  "segments": [
    { "start": 1.2, "duration": 2.16, "text": "All right, so here we are, in front of the elephants" },
    { "start": 5.318, "duration": 2.656, "text": "the cool thing about these guys is that they have really..." }
  ],
  "wordCount": 39,
  "charCount": 217,
  "failureReason": null
}
```

Two more real results from the same run: a 3-minute cooking video with auto-generated English captions (`source: "auto_captions"`, 79 segments, 598 words) and a 3-minute French news video with auto-generated French captions (`language: "fr"`, 84 segments, 469 words). Auto-generated captions keep YouTube's own wording and may lack punctuation and have recognition errors.

#### Speech-to-text (video without captions)

Real result for an 89-second BBC News video with no caption track, with speech-to-text on. It was billed 2 minutes (89 seconds rounded up per video) = $0.012:

```json
{
  "success": true,
  "videoId": "CKRxRE7qRzc",
  "durationSec": 89,
  "language": "en",
  "source": "asr",
  "transcript": "Presidents Trump and Xi are about to meet here in the US. So what's at stake? We're in New York City. And Beijing to break it down. ...",
  "segments": [{ "start": 0, "duration": 3.64, "text": "Presidents Trump and Xi are about to meet here in the US." }],
  "wordCount": 256,
  "asr": { "status": "ok", "model": "large-v3-turbo", "minutes": 2, "audioSeconds": 89.4, "detectedLanguage": "en", "languageProbability": 0.999, "transcribeSeconds": 19 },
  "chargedMinutes": 2,
  "failureReason": null
}
```

The same video requested with `languages: ["zh-TW"]` came back as `status: "language_mismatch"`, `success: false`, `transcript: null`: the speech was English, so nothing was returned and nothing was charged.

#### RAG chunks and video notes

With **RAG chunks** on, each item also has `chunks` (here with a 60-second target; the 3-minute cooking video gave 4):

```json
"chunks": [{ "chunkIndex": 0, "start": 0, "end": 58.3, "url": "https://www.youtube.com/watch?v=oxbFSZADg-Y&t=0s", "text": "[Music] hello this is Chef John from foodwishes.com with Sherry brazed beef short ribs ..." }]
```

**Chapters.** Every video row (successful or failed) also has `chapters`, read from the video description the way YouTube does: timestamped lines such as `0:00 Intro`, `12:34 - Topic` or `1:02:03 Wrap-up`, used only when there are at least 3, the first is `0:00` and the times go up. Each chapter is `{ "startSec": 754, "title": "Topic" }`; a video without such a list gives `chapters: []`. When a video has chapters, RAG chunks never cross a chapter start and each chunk has a `chapter` field with its title (a caption line that runs past a chapter start stays with the chapter it began in, and its chunk ends at that start); without chapters the chunks are cut exactly as before. Chapters added only in YouTube Studio (not in the description) are not read. Rows that are not about one video (input errors, a channel or playlist that could not be listed, a video skipped because the service was down) have no `chapters` field.

With **video notes** on, `notesMarkdown` looks like this (shortened):

```markdown
## Me at the zoo

Video: https://www.youtube.com/watch?v=jNQXAC9IVRw
Channel: jawed
Published: 2005-04-23
Length: 0:19
Language: en (captions)

### [0:01](https://www.youtube.com/watch?v=jNQXAC9IVRw&t=1s)

All right, so here we are, in front of the elephants the cool thing about these guys ...
```

### Three ready-to-use recipes

1. **Video notes**: `{"videos": ["<url>"], "notes": true}` gives `notesMarkdown`, a Markdown page with the video details and the transcript in 3-minute sections; each heading links to that point of the video.
2. **New-videos digest for a channel**: `{"channels": ["@mitocw"], "maxVideosPerSource": 10, "onlyNewVideos": true, "monitorName": "mit"}` on a daily schedule. The first run takes the latest videos; later runs return only new uploads, and the run's `DIGEST` record (key-value store) is a Markdown list with links and the opening lines of each transcript. A run with nothing new costs nothing.
3. **RAG data with citations**: `{"videos": [...], "ragChunks": true, "chunkSeconds": 60, "includeTimestamps": false}` gives chunks with `start`, `end` and a `url` that opens the video at that moment.

### How it compares

Prices of the Apify Store's "YouTube transcript" Actors with the most users, read from the public Store API on **3 October 2026** (FREE-plan price per transcript; per-1,000 in brackets). Most also charge a small start event; we have none. Prices change, so check the listing before you decide.

| Actor | Users (30 days) | Price per transcript | Speech-to-text for videos without captions |
|---|---|---|---|
| **TidyTools YouTube Transcript Scraper (this Actor)** | new | **$0.002** ($2) | **yes, $0.006 / minute** |
| starvibe/youtube-video-transcript | 2,763 | $0.005 ($5) | no priced event |
| pintostudio/youtube-transcript-scraper | 2,324 | $0.01 ($10) | no priced event |
| johnvc/YoutubeTranscripts | 1,994 | listed at about $0.00001 | no priced event |
| supreme_coder/youtube-transcript-scraper | 862 | $0.001 ($1), $0.0007 on higher plans | no priced event |
| karamelo/youtube-transcripts | 817 | $0.007 ($7), $0.005 on higher plans | no priced event |
| codepoetry/youtube-transcript-ai-scraper | 444 | $0.001 ($1) + $0.0025 start | yes, $0.012 / minute |
| apple_yang/youtube-transcripts-scraper | 8 | $0.001 ($1) | yes, $0.0045 / minute |

What this means, honestly: **we are not the cheapest for captions.** Several Actors charge $0.001 or less per transcript. We compare on what you get for that price: transcripts that tell you which language and source you got, never charge a failed video, include notes, RAG chunks with timestamp links and a channel digest at no extra charge, and offer speech-to-text for videos without captions as an option. The table compares prices and priced events only; we did not run the other Actors, so we make no claim about their reliability or output quality.

### Measured results (our own test runs, October 2026)

- **Captions**: 300 public videos in 10+ languages; every video got a definite answer (222 transcripts, 73 without captions, 5 unavailable), no errors or blocks. Language requests: Spanish and Japanese returned exactly on 4/4 multilingual videos; `zh-TW` returned `zh` / `zh-Hans` tracks (marked `languageMatch: "base"`) because those videos have no Traditional track; 3/3 videos with captions only in other languages were reported as `language_mismatch`.

- **Speech-to-text** on 20 videos without captions (11 s to 25 min): 12 transcripts (6 English, 5 Chinese written in Traditional characters, 1 Korean), 7 with no speech (music, intros, ambience: not charged), 1 language mismatch (English speech when Chinese was requested: not charged), 0 errors. 73 audio minutes billed in total.

- **Accuracy against human captions** (same videos transcribed both ways): English word error rate 2.3%, 3.5% and 4.5% on three 2-5 minute talks (13% on a 19-second clip). Chinese is harder to score: against edited human subtitles of a 17-minute talk the character difference was 41%, partly because subtitles are not word-for-word; names are the most common real errors.

- **Speed**: about 5x faster than real time when the queue is empty (a 15-minute video in 2.5 minutes). The 89-second sample above took 19 seconds of transcription.

- **Re-check on 6 October 2026** (14 mixed inputs, run on the same code path as the service): 8 transcripts (manual and auto captions; English, Korean, Spanish, Traditional Chinese `zh-TW` exact), 2 `language_mismatch` (Klingon requested; Japanese requested on a video with English captions only), 1 age-restricted, 1 live stream still on air and 2 unknown video ids (all three `video_unavailable`). Every failure was a free, labelled row. A `/shorts/ID` link is read exactly like the normal link of the same video. Channel and playlist expansion (`@handle`, `/channel/`, `/playlist?list=`) returned the latest videos; a channel or playlist that does not exist returned an empty list, now reported as a free row.

### Limitations

- **Captions are not speech recognition.** Manual captions are the creator's; auto-generated captions are YouTube's and can be wrong. Speech-to-text is only used for videos with **no caption track at all**: a video that has captions, but not in your languages, is `language_mismatch` and is not sent to speech-to-text.
- **Many videos have no captions**: in our 300-video sample, 25% of playable videos had none. News channels in Chinese often burn the subtitles into the picture, so there is no caption track: turn on speech-to-text for those.
- **Public videos only.** The Actor never signs in: members-only, private and age-restricted videos are skipped (free). Live streams that are still running are not transcribed; a finished live stream (replay) is treated like any video.
- **No machine translation.** Asking for a language the video has no caption track in gives `language_mismatch` (free) with `availableLanguages`; pick one of those, or leave the language empty.
- **Links that are not videos are skipped, not charged.** A link that is neither a video, channel nor playlist (another site, a YouTube home or search page) gets its own free row (`failureReason: "invalid_url"`) and is listed in `SUMMARY.notes`; if nothing usable is left the run stops at once with one free error row saying what to paste. A channel or playlist that does not exist, is private or has no public videos gives a free `source_unavailable` or `no_videos_found` row.
- **Running live streams** (still on air) come back as `video_unavailable` (free); try again after the stream has ended.
- **Capacity is limited, on purpose.** Captions are fetched at about 20 videos per minute and up to 3,000 videos per day across all users. Speech-to-text runs one or more videos at a time, depending on how free our machines are, at up to about 5x real time, with a daily budget across all users (600 audio minutes for the speech-to-text of this Actor). When capacity is used up or the service is busy, videos come back as `service_busy` or `blocked` (free): run them again later. Very large runs (hundreds of videos) take time: 100 videos is about 5-6 minutes.
- **Speech-to-text limits:** at most 240 minutes per video, 30 by default; your per-run limit and maximum charge apply.
- **YouTube can change.** If YouTube blocks our access, results come back as `blocked` (free) until we fix it.
- No AI summary is included in notes: they are the transcript, organized with links.

### Responsible use

Transcripts belong to the video creators. Use them within YouTube's terms and copyright law: for personal notes, research, accessibility, search and analysis, or content you have the right to use. Do not republish other people's transcripts as your own.

### Use it from code

```bash
curl -X POST "https://api.apify.com/v2/acts/tidytools~youtube-transcript/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"videos":["https://www.youtube.com/watch?v=jNQXAC9IVRw"],"notes":true}'
```

**With AI agents (MCP):** connect Apify's MCP server (`https://mcp.apify.com?tools=tidytools/youtube-transcript`) and ask, for example, "Get the transcript of this YouTube video with timestamps".

### FAQ

**What happens to a video with no captions?** It comes back as a row with `success: false` and `failureReason: "captions_unavailable"`, and it is not charged. Turn on **Transcribe videos without captions** (`includeAsr`) to get a speech-to-text transcript instead, billed per audio minute rather than per video.

**Why did a video with captions come back as `language_mismatch`?** It has captions, but not in your languages. `availableLanguages` lists what exists. Speech-to-text is only used for videos with no caption track at all.

**Why did some videos come back as `service_busy`?** Caption capacity is shared by everyone using this Actor (about 3,000 videos per day), and speech-to-text has its own shared daily budget. Videos that arrive when the day's capacity is used up are returned as rows with `failureReason: "service_busy"` — free — and the run still finishes normally. Run them again a few hours later or the next day.

**When am I not charged?** Only rows with `success: true` are charged ($0.002 each on the FREE/BRONZE rate, so 1,000 transcripts cost $2.00). Everything else is a free row that says why in `failureReason`: no captions (with speech-to-text off), captions only in other languages (`language_mismatch`), videos with no speech, private, members-only, age-restricted or removed videos, a busy or blocked service, invalid links and empty channels. Input mistakes never fail the run and never cost anything.

**Why does my run say `PARTIAL_RESULTS`?** The run ends as `PARTIAL_RESULTS` when at least one transcript was saved, at least one video failed to process, and the cost limit was not reached. Invalid links are returned as free rows and do not count as failures.

**Which languages and auto-generated captions work?** Captions come in the languages the video has: set **Preferred languages** (`languages`, e.g. `["en","es"]`) in order; a manual track wins over an auto-generated one in the same language, and `source` says `manual_captions`, `auto_captions` or `asr`. There is no fixed language list and no machine translation: if the video has captions only in other languages the row is `language_mismatch` (free) with `availableLanguages`. With no languages set you get the video's original language.

**How do I run many videos?** Put several links in `videos`, or `@handles` and playlist URLs in `channels`. `maxVideosPerSource` (default 20, up to 500) limits each channel or playlist and `maxVideos` (default 100, up to 500) limits the whole run. For daily channel runs set `onlyNewVideos: true` so already processed videos are skipped. Videos over the daily capacity come back as free `service_busy` rows to run again later.

**How is the price calculated?** At the FREE/BRONZE rate, $0.002 per video whose captions were returned (FREE/BRONZE), so 1,000 videos = $2.00 and 100 videos = $0.20. Failed and no-caption videos are free. With speech-to-text on, a video transcribed that way costs $0.006 per audio minute (rounded up per video) instead of the video charge. SILVER and GOLD+ tiers pay less per event; see the tier table in [How much does it cost?](#how-much-does-it-cost).

**Does the Actor collect usage data?** The Actor sends one anonymous usage counter per run (input type, item count range, outcome) to improve the tool; no URLs, content or account data.

**Which caption is returned when there are several?** With languages set: the first language that exists, manual before auto-generated; `languageMatch` says whether it was an exact or only a base-language match. Without languages: the video's original language (manual if available).

**Is speech-to-text accurate?** It is Whisper large-v3-turbo (the same open model many transcription tools use). On our English samples the word error rate against human captions was 2-5%; names and rare terms are the usual mistakes. Chinese requested as `zh-TW` is written in Traditional characters.

**Why was a short video charged a full minute?** Speech-to-text is billed per started audio minute of the video, and only when text was returned.

**Which links work?** `watch?v=`, `youtu.be/ID` (with or without `?t=`), `/shorts/`, `/live/`, `/embed/`, m. and music. links, `attribution_link` links and bare 11-character ids; channels (`@handle`, `/channel/`, `/c/`, `/user/`) and playlists (`/playlist?list=`) are expanded to their latest videos. A watch link that also has `&list=` is that one video.

**What happens with a wrong link, a private video or an empty channel?** The run still ends normally (it never fails because of your input). Each problem is one free row with a `failureReason` and an `error` that says what to do: `invalid_url` (not a YouTube link), `video_unavailable` (private, removed, members-only, or a live stream still on air), `source_unavailable` / `no_videos_found` (channel or playlist missing, private or empty). Only rows with `success: true` are charged.

**Does it work for YouTube Shorts and live replays?** Yes: `/shorts/ID` and `/live/ID` links are read like normal videos (a finished live stream is a normal video). A stream that is still live is `video_unavailable` until it ends.

**Do I need a YouTube API key or an account?** No.

**What are the limits?** Up to 500 videos per run (**Max videos in total**, default 100) and up to 500 per channel or playlist (default 20). Speech-to-text: up to 240 minutes per video (default 30) and 3,000 per run (default 60). Capacity is shared by all users (about 3,000 caption videos a day); videos over it come back as free `service_busy` rows to run again later. See [Limitations](#limitations).

**How fast is it?** In our test runs captions came at about 20 videos per minute (100 videos in about 5-6 minutes), and speech-to-text ran at about 5x real time (a 15-minute video in 2.5 minutes). See [Measured results](#measured-results-our-own-test-runs-october-2026).

**Is it legal to scrape YouTube transcripts?** The Actor reads only the captions of public videos, without signing in, cookies or an API key; it skips private, members-only and age-restricted videos. Transcripts belong to the creators, so use them within YouTube's terms and copyright law ([Responsible use](#responsible-use)). This is not legal advice.

**Does it work with the API, webhooks, Make, n8n or Zapier?** Yes. One HTTP request returns the transcripts ([example](#use-it-from-code)); runs can also be started from a schedule, a webhook, the Apify apps for Make, n8n and Zapier, and from AI agents through MCP.

### Support

Open an issue in the **Issues** tab with the video URL and the `failureReason`. Issues are checked regularly.

# Actor input Schema

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

YouTube video URLs (watch, youtu.be, Shorts, live replay, mobile, attribution_link) or 11-character video ids. One per line; a channel or playlist link pasted here is expanded too.

## `channels` (type: `array`):

Channel URLs or @handles (latest uploads first) and playlist URLs. Each one adds up to "Max videos per channel or playlist" videos.

## `maxVideosPerSource` (type: `integer`):

How many of the latest videos to take from each channel or playlist.

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

Upper limit for the whole run (direct videos plus channels and playlists).

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

Caption languages in order of preference, as codes (en, es, zh-TW, ja). A manual caption track wins over an auto-generated one in the same language. Leave empty for the video's original spoken language. Each result shows the requested languages, the language returned and whether it matched exactly (e.g. zh-TW) or only the base language (e.g. zh-Hans for zh-TW). Videos with captions only in other languages are returned as "language_mismatch" (not charged).

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

Add the "segments" list (start, duration, text) next to the plain transcript text.

## `subtitleFormats` (type: `array`):

Optional SRT and/or WebVTT files for successful transcripts. Adds subtitleSrtUrl and/or subtitleVttUrl download links. Files are made even when timestamped segments are hidden. No extra charge.

## `compactTranscript` (type: `boolean`):

Use videoUrl and compact segments with start, end and text. The standard output shape remains the default.

## `includeAsr` (type: `boolean`):

For videos that have no caption track at all, download the audio and transcribe it with Whisper (large-v3-turbo). Charged per audio minute, only for transcripts returned; videos with no speech (music, ambience) are not charged. Off by default.

## `asrMaxMinutesPerVideo` (type: `integer`):

Videos longer than this are not transcribed (listed with the reason, not charged).

## `asrMaxMinutesTotal` (type: `integer`):

Upper limit of audio minutes transcribed in one run. Your maximum charge per run also limits it.

## `ragChunks` (type: `boolean`):

Adds "chunks": the transcript cut into time windows (start, end, text) with a link that opens the video at that moment (\&t=). Ready for embeddings and citations. No extra charge.

## `chunkSeconds` (type: `integer`):

Target length of each RAG chunk. Segments are never split.

## `notes` (type: `boolean`):

Adds "notesMarkdown": title, channel, date, length and the transcript in 3-minute sections, each headed by a timestamp link. No extra charge (no AI summary).

## `onlyNewVideos` (type: `boolean`):

Remembers which channel and playlist videos were already processed (per monitor name) and skips them in later runs, so a daily schedule returns only new uploads. Skipped videos are free. A Markdown digest of the run is saved as DIGEST in the key-value store.

## `monitorName` (type: `string`):

Keeps separate "already processed" lists for different schedules.

## Actor input object example

```json
{
  "videos": [
    "https://www.youtube.com/watch?v=jNQXAC9IVRw"
  ],
  "maxVideosPerSource": 20,
  "maxVideos": 100,
  "includeTimestamps": true,
  "subtitleFormats": [],
  "compactTranscript": false,
  "includeAsr": false,
  "asrMaxMinutesPerVideo": 30,
  "asrMaxMinutesTotal": 60,
  "ragChunks": false,
  "chunkSeconds": 60,
  "notes": false,
  "onlyNewVideos": false,
  "monitorName": "default"
}
```

# Actor output Schema

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

No description

## `rag` (type: `string`):

No description

## `notes` (type: `string`):

No description

## `full` (type: `string`):

No description

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

// Run the Actor and wait for it to finish
const run = await client.actor("tidytools/youtube-transcript").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=jNQXAC9IVRw"] }

# Run the Actor and wait for it to finish
run = client.actor("tidytools/youtube-transcript").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=jNQXAC9IVRw"
  ]
}' |
apify call tidytools/youtube-transcript --silent --output-dataset

```

## MCP server setup

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

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/HquiZorfA0G2SsVDc/builds/JrGhyUcjCmoPr58lc/openapi.json
