YouTube Transcript Scraper — captions to Markdown avatar

YouTube Transcript Scraper — captions to Markdown

Pricing

from $2.00 / 1,000 transcript fetcheds

Go to Apify Store
YouTube Transcript Scraper — captions to Markdown

YouTube Transcript Scraper — captions to Markdown

Get YouTube video transcripts as clean Markdown and timed segments. Accepts video, channel and playlist URLs, picks your preferred caption language with automatic fallback, and returns title, channel, publish date, duration, word count and a per-video status. Failed videos are never charged.

Pricing

from $2.00 / 1,000 transcript fetcheds

Rating

0.0

(0)

Developer

Yosuke Sakaguchi

Yosuke Sakaguchi

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

10 days ago

Last modified

Share

Turn YouTube videos into clean, LLM-ready transcripts. Pass video URLs, channel URLs or playlist URLs and get one record per video with the caption text as paragraph-formatted Markdown, the raw timed segments, title, channel, publish date, duration, the caption language that was used, word count and a per-video status. Built for RAG pipelines, summarizers, research agents and anyone who needs the words from a video without watching it.

What you get

Each video produces exactly one dataset record:

FieldTypeDescription
videoIdstring | null11-character YouTube video ID. null only when an input item could not be parsed or a channel/playlist could not be expanded
urlstringCanonical https://www.youtube.com/watch?v=... URL
sourcestringThe input item this record came from (useful when a channel or playlist was expanded)
titlestring | nullVideo title
channelNamestring | nullChannel display name
channelIdstring | nullChannel ID (UC...)
publishedAtstring | nullPublish date, ISO 8601 (UTC)
durationSecinteger | nullVideo length in seconds
languagestring | nullLanguage code of the caption track that was used, e.g. en, en-US, ja
isAutoGeneratedboolean | nulltrue when the track is YouTube's automatic speech recognition
availableLanguagesstring[]All caption tracks the video offered, e.g. ["en", "en (auto)", "ja"]
transcriptstring | nullParagraph-formatted transcript (Markdown). null when outputFormat is segments
segments{ start, dur, text }[]Timed caption segments in seconds. Empty when outputFormat is markdown
wordCountintegerWords in the transcript (whitespace-separated tokens; Japanese, Chinese and Korean characters are counted individually)
fetchedAtstringISO 8601 timestamp of the fetch
statusok | failed | skippedfailed = no captions, unavailable video or fetch error (see error); skipped = spending limit reached
errorstringPresent only when status is not ok

The run always finishes as SUCCEEDED, even when some videos have no captions or do not exist. Check status per record instead of the run status, so one private video never breaks your pipeline.

Input

FieldTypeDefaultDescription
videoUrlsstring[]requiredVideo URLs (watch?v=, youtu.be/, /shorts/, /embed/, /live/), bare 11-character video IDs, channel URLs (/@handle, /channel/UC..., /c/..., /user/...) or playlist URLs (playlist?list=...). Mixed lists are fine
languagesstring[]["en"]Preferred caption languages in order. See Language fallback below
outputFormatmarkdown | segments | bothbothWhich transcript fields to include
includeTimestampsbooleanfalsePrefix each paragraph of transcript with its start time, e.g. [12:34]
maxVideosPerChannelinteger50Upper bound on videos taken from each channel or playlist (1–1000). Channels are expanded newest first
maxConcurrencyinteger5Videos processed in parallel (1–10)
proxyConfigurationobjectApify residential proxyYouTube blocks most datacenter IP ranges, so residential proxies are the default. You can switch to datacenter or custom proxies, but expect more failed records

Example input:

{
"videoUrls": [
"https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"https://www.youtube.com/@apify",
"https://www.youtube.com/playlist?list=PLxxxxxxxxxxxxxxxxxx"
],
"languages": ["en", "ja"],
"outputFormat": "both",
"includeTimestamps": true,
"maxVideosPerChannel": 20
}

Output example

{
"videoId": "jNQXAC9IVRw",
"url": "https://www.youtube.com/watch?v=jNQXAC9IVRw",
"source": "https://www.youtube.com/watch?v=jNQXAC9IVRw",
"title": "Me at the zoo",
"channelName": "jawed",
"channelId": "UC4QobU6STFB0P71PMvOGN5A",
"publishedAt": "2005-04-24T03:31:52.000Z",
"durationSec": 19,
"language": "en",
"isAutoGenerated": false,
"availableLanguages": ["en", "de"],
"transcript": "[00:01] 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, "dur": 2.16, "text": "All right, so here we are, in front of the elephants" },
{ "start": 5.318, "dur": 2.656, "text": "the cool thing about these guys is that they have really..." }
],
"wordCount": 39,
"fetchedAt": "2026-10-15T05:12:40.118Z",
"status": "ok"
}

A video without captions looks like this and is not charged:

{
"videoId": "a9LDPn-MO4I",
"url": "https://www.youtube.com/watch?v=a9LDPn-MO4I",
"source": "https://www.youtube.com/watch?v=a9LDPn-MO4I",
"status": "failed",
"error": "No caption tracks available for this video",
"title": null,
"transcript": null,
"segments": [],
"wordCount": 0,
"fetchedAt": "2026-10-15T05:12:40.118Z"
}

Other typical error values: Video not available (ERROR): This video is unavailable, Video not available (LOGIN_REQUIRED): This video may be inappropriate for some users. (age-restricted), Playlist or channel unavailable: The playlist does not exist., Not a YouTube URL.

Language fallback

For every video the Actor looks at the caption tracks YouTube offers and picks one in this order:

  1. For each language in languages, in order: a manually created track in that language, then an auto-generated track in that language. en also matches regional variants such as en-US or en-GB; an exact match wins over a variant.
  2. If none of the preferred languages exists: the first manual track in any language.
  3. Finally: the first auto-generated track in any language.

The chosen track is reported in language and isAutoGenerated, and every track that was on offer is listed in availableLanguages, so you can decide whether the result is good enough for your use case. YouTube sometimes exposes auto-generated tracks in many languages for the same video and sometimes only in the original language; the list can vary between requests.

Transcripts are returned as published by YouTube: the Actor does not translate, summarize or correct them. Auto-generated tracks usually contain recognition errors and, for older videos, no punctuation.

Pricing

Pay per event. You are charged only for videos that return status: "ok".

EventPriceWhen
transcript-fetched$0.002 per videoA caption track was fetched and converted

Videos without captions, unavailable or private videos, invalid URLs, and the expansion of channels and playlists are free. Example: transcribing a 200-video channel where 180 videos have captions costs $0.36. You can cap spending per run with the Maximum total charge setting in Apify Console or the maxTotalChargeUsd API parameter; when the cap is reached, remaining videos are returned with status: "skipped".

Usage

Apify Console

Open the Actor, paste video, channel or playlist URLs into Video, channel or playlist URLs, click Start, and download the dataset as JSON, CSV or Excel. The Overview tab shows one row per video with status, language and word count.

JavaScript (apify-client)

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('fre.d/youtube-transcript').call({
videoUrls: ['https://www.youtube.com/watch?v=dQw4w9WgXcQ', 'https://www.youtube.com/@apify'],
languages: ['en'],
outputFormat: 'markdown',
maxVideosPerChannel: 10,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const video of items) {
if (video.status === 'ok') console.log(video.title, video.language, video.wordCount);
else console.warn(video.url, video.error);
}

Python (apify-client)

import os
from apify_client import ApifyClient
client = ApifyClient(token=os.environ["APIFY_TOKEN"])
run = client.actor("fre.d/youtube-transcript").call(run_input={
"videoUrls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"],
"languages": ["en", "ja"],
"includeTimestamps": True,
})
for video in client.dataset(run["defaultDatasetId"]).iterate_items():
if video["status"] == "ok":
print(video["title"], video["transcript"][:200])
else:
print("failed:", video["url"], video["error"])

AI agents via MCP

Add the Apify MCP server to your agent (Claude Desktop, Cursor, LangGraph, etc.) and allow this Actor:

{
"mcpServers": {
"apify": {
"url": "https://mcp.apify.com/?tools=fre.d/youtube-transcript",
"headers": { "Authorization": "Bearer <APIFY_TOKEN>" }
}
}
}

The agent can then call the tool with {"videoUrls": ["https://www.youtube.com/watch?v=..."]} and read transcript directly from the result. Because videoUrls accepts channels and playlists, an agent can pull a whole series in one tool call.

HTTP API

curl -X POST "https://api.apify.com/v2/acts/fre.d~youtube-transcript/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"videoUrls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"], "outputFormat": "markdown"}'

How it works

  1. Each input item is classified as a video, channel or playlist. Channels are resolved to their uploads list; playlists and channels are paged through until maxVideosPerChannel is reached. Duplicate videos across inputs are fetched once.
  2. For each video the Actor asks YouTube's internal player endpoint for the video details and the list of caption tracks, picks a track using the language fallback above, and downloads that track through the same proxy session (caption URLs are bound to the IP that requested them).
  3. Caption cues are cleaned (HTML entities decoded, whitespace collapsed) and grouped into paragraphs: a new paragraph starts after a pause of more than 2.5 seconds, at a sentence end once a paragraph is longer than 30 seconds, or at 60 seconds / 150 words at the latest. Japanese, Chinese and Korean cues are joined without inserted spaces.
  4. The publish date is looked up in a second lightweight call; if it fails, publishedAt is null and the record is still returned.

Requests go through Apify residential proxies by default because YouTube refuses or empties caption responses for most datacenter IP ranges. Each parallel worker keeps its own proxy session; a session that gets a bot check or an empty caption body is discarded and the video is retried on a fresh one.

Limitations

  • Only videos that have captions (manual or auto-generated) can be transcribed. Live streams return failed until the stream has ended and YouTube has processed captions.
  • Private, deleted, members-only and age-restricted videos return failed. The Actor never signs in and never uses cookies.
  • Channel expansion takes videos from the channel's uploads list (newest first), which includes Shorts and past live streams. There is no filter by date or type yet.
  • Auto-generated captions reflect YouTube's speech recognition quality. The Actor does not correct, translate or summarize them.
  • wordCount is approximate for mixed-script text.
  • This Actor relies on YouTube's undocumented internal endpoints, which change from time to time. If you see a sudden rise in failed records, check the Actor's Issues tab; fixes are usually released within a few days.

This Actor is not affiliated with or endorsed by YouTube or Google and does not use the official YouTube Data API. It retrieves the caption tracks that YouTube serves publicly to viewers.

Captions and transcripts are the copyrighted work of the video owner (or of YouTube for auto-generated tracks under the owner's content). You are responsible for how you store, redistribute or otherwise use the text you retrieve, including compliance with YouTube's Terms of Service and applicable copyright and data-protection law. Fetching a transcript to summarize or analyze a video for yourself is a different matter from republishing it; when in doubt, link to the video instead of redistributing the text.

Support

Open an issue on the Actor's Issues tab in Apify Store, or email the maintainer listed on the Actor page. Feature requests (date filters for channels, translation via YouTube's translated tracks, SRT/VTT output, chunking for embeddings) are welcome.