YouTube Transcript API: Any Video, Channel or Playlist avatar

YouTube Transcript API: Any Video, Channel or Playlist

Pricing

from $3.00 / 1,000 transcripts

Go to Apify Store
YouTube Transcript API: Any Video, Channel or Playlist

YouTube Transcript API: Any Video, Channel or Playlist

Download YouTube transcripts through an API: one video, a whole channel, a playlist or a search in one run. Clean text with timestamps and language, plus views, dates and comments. Built for AI, RAG and LLM pipelines, callable from MCP clients. No YouTube API key. Pay only for delivered transcripts.

Pricing

from $3.00 / 1,000 transcripts

Rating

0.0

(0)

Developer

Lorenzo

Lorenzo

Maintained by Community

Actor stats

0

Bookmarked

15

Total users

3

Monthly active users

15 days ago

Last modified

Share

Download YouTube Transcripts — API for Any Video, Channel or Playlist

Get the full transcript of one YouTube video, or of every video on a channel or playlist, as clean JSON (or CSV / Excel). Timestamps, language, views and publish dates come back in the same run. No YouTube API key, no quota, nothing to install.

Use it when you need: transcripts for an AI / RAG / LLM pipeline · every video of a channel in one run · only the new videos since last time (publishedWithinDays) · transcripts plus comments for research.

Price: transcripts from $3 per 1,000, plus $0.50 per 1,000 videos. With transcripts on, you pay only for transcripts actually delivered: a video with no subtitles (NO_CAPTIONS), a blocked or rate-limited request, or a failed video costs nothing, its video row included.

Input:

{ "inputs": ["https://www.youtube.com/@veritasium"],
"includeTranscript": true, "transcriptLanguages": ["en"],
"maxResultsPerSource": 50 }

Output — one row per video:

{ "video_id": "JsBZOcqZerk",
"title": "The Insane Real Engineering of the Nazi Enigma Machine",
"channel": "Veritasium", "published_at": "2026-09-21T10:49:57-07:00",
"views": 16814797, "duration_s": 2861, "transcript_language": "en",
"transcript_status": "OK",
"transcript": "This is the Enigma, the encryption machine used by the Nazis in World …" }

Callable from any MCP client. Claude, Cursor, VS Code and anything else that speaks Model Context Protocol can find and run this through Apify's MCP server at mcp.apify.com — search-actors to discover it, call-actor to run it. No setup on your side and no separate integration.

Built for pipelines that run more than once

Most scrapers charge you the same on Monday as they did on Sunday. This one remembers what it already fetched.

Measured on 27 August 2026 — one channel, 500 videos, same input both times:

first runrun it again
rows delivered500500
videos charged48911
requests to YouTube52530
time174s46s

Video rows already fetched inside the cache window come back from stored state and are not charged. Eleven were charged the second time because those eleven had failed the first — you are not billed for a row that came back empty. A transcript is charged when it is fetched, normally once; the one exception is under Does it re-fetch videos I already pulled?

And it is one Actor, not three. Video metadata, transcripts and comments arrive from the same run, in the same dataset, on one bill. No joining three outputs on video_id and no three integrations to keep alive.

What you get back

video_typetitleviewspublished_atduration_s
videoGoogle Pixel 11/Pro/Fold Impressions: It Is What It Is31387802026-08-12T07:00:35-07:00673
shortThis ZOOM is Insane!35673932026-07-20T13:39:08-07:0066
stream5 New Phone Updates + Giveaway Update!9406082017-11-29T09:15:12-08:002501

Real rows from a real run. Exact counts, not 3.1M. Full timestamps, not 7 days ago. Both survive sorting.


How do I download the transcript of every video on a channel?

{ "inputs": ["https://www.youtube.com/@veritasium"],
"includeTranscript": true, "transcriptLanguages": ["en", "es"] }

Each row gains the transcript as text, its language, whether it was auto-generated, and how many timed cues it has.

Timed cues are a separate switch: includeTranscriptSegments. Turn it on and each row also carries transcript_segments — one entry per cue with start_s, duration_s and text — for citing a moment in the video. It costs nothing extra, because the cues arrive in the same request as the text. It is off by default because every export format flattens them: a 1,000-cue video becomes transcript_segments/0/text … /999/text, and a spreadsheet of a few videos turns into thousands of columns. transcript_cue_count ships either way.

transcriptLanguages is a preference order and human-written subtitles beat auto-generated ones. Subtitles off returns NO_CAPTIONS, free.

includeLikes adds a like count from one small extra request, not charged separately.


What a row actually looks like

One video, with transcripts, timed segments and comments all switched on. Real output from a real run — long fields trimmed with …, nothing else changed.

{
"_row_type": "video",
"video_id": "o4SSoURPODY",
"video_type": "video",
"title": "Google Pixel 11/Pro/Fold Impressions: It Is What It Is",
"channel": "Marques Brownlee",
"channel_id": "UCBJycsmduvYEL83R_U4JriQ",
"views": 3148445,
"likes": 84617,
"published_at": "2026-08-12T07:00:35-07:00",
"category": "Science & Technology",
"duration_s": 673,
"description": "Every year, a new Pixel, and new hopes and dreams... …",
"keywords": ["Pixel 11", "Pixel 11 Pro", "Pixel 11 Pro Fold", "…"],
"thumbnail": "https://i.ytimg.com/vi_webp/o4SSoURPODY/maxresdefault.webp",
"subscriber_count": "21.1M subscribers",
"is_live": false,
"is_private": false,
"is_crawlable": true,
"transcript": "[music] >> You know, I've been using Pixel phones for a long time. …",
"transcript_segments": [
{ "start_s": 2.619, "duration_s": 2.741, "text": "[music]" }, "…"
],
"transcript_cue_count": 331,
"transcript_language": "en",
"transcript_is_generated": true,
"transcript_status": "OK",
"_source_url": "https://www.youtube.com/watch?v=o4SSoURPODY",
"_cached": false,
"_error_kind": null
}

A comment from the same run — its own row, not a nested field:

{
"_row_type": "comment",
"video_id": "o4SSoURPODY",
"comment_id": "Ugz7VRjqhsV-60jGjwR4AaABAg",
"comment_text": "Adding a physical RGB LED to the back of the phone just to …",
"comment_author": "@Mosesplusofficial",
"comment_author_channel_id": "UCr3n8BfGuDIzhO8ALIxMe4w",
"comment_author_is_verified": false,
"comment_published_text": "8 days ago",
"comment_like_count_text": "13K",
"comment_reply_count_text": "200",
"comment_is_reply": false
}

Three things that make this different

🟢 Failures are typed, never blank

Subtitles off returns NO_CAPTIONS. Age-restricted and members-only videos return UNAVAILABLE: AGE_RESTRICTED / UNAVAILABLE: MEMBERS_ONLY. A transcript request that failed returns UNAVAILABLE or TRACK_FETCH_FAILED; a video that could not be fetched returns FETCH_FAILED. Different values, so you always know which happened.

🟢 Transcripts and comments in the same run

One actor, one pass. Transcript text, timed segments, comments and replies — alongside the metadata, not in a separate job.

🟢 With transcripts on, you pay only for delivered transcripts

A video with no subtitles, a blocked request or a failed fetch costs nothing, video row included. With transcripts off, a video row is charged when it has data. A deleted video is always free, and so is one your filter removed, or a video row served from cache.


How do I scrape all videos from a YouTube channel?

{ "inputs": ["https://www.youtube.com/@mkbhd"], "maxResultsPerSource": 500 }

@handle, a channel URL, or a /channel/UC… ID all work.

You get all three tabs. Long videos, Shorts and live streams sit in separate tabs that do not overlap — one channel measured 30, 48 and 5. video_type says which is which, and the tabs are interleaved so asking for 30 returns a mix. channelTabs narrows it.

Channel rows also carry subscriber_count and channel_description, free — same request that lists the videos.

What happens when something fails?

You get a typed reason in the row, never an empty cell.

_error_kindmeans
REJECTED_INPUTnot a recognised channel, playlist, video or search
RESOLVE_FAILEDthe listing could not be read
NO_VIDEOSit was read and contained none
FETCH_FAILEDthe video request failed
NO_DATAit loaded but carried no video details — private and removed videos say so in _error (PRIVATE: …, REMOVED: …), cost nothing, and are not asked again inside the cache window

Blocked requests retry automatically on a fresh IP. In a 36-video run that turned 3 blocks into 0 failures.

How do I export a YouTube playlist to CSV?

{ "inputs": ["https://www.youtube.com/playlist?list=PLbpi6ZahtOH6Blw3RGYpWkSByi_T7Rygb"],
"maxResultsPerSource": 100 }

Then hit Export — CSV, JSON, Excel, or an API endpoint. Playlists return 100 videos per request, so multiples of 100 go furthest.

How do I get YouTube comments with replies?

{ "inputs": ["https://www.youtube.com/@mkbhd"], "includeComments": true,
"maxCommentsPerVideo": 100, "maxRepliesPerComment": 5 }

Comments arrive as their own rows, not extra columns — _row_type is video or comment. Folding 100 comments into a video row would export as 600 unusable columns.

Replies are rows too (comment_is_reply: true), capped because each comment with replies costs an extra request.

How do I search YouTube and export the results?

{ "inputs": ["drone review", "sourdough starter"] }

One quirk: a bare 11-character word is read as a video ID, because that is exactly how long a YouTube ID is — helicopters is eleven characters. Write search:helicopters to force a search.

Can I filter, or work in another language?

inputeffect
publishedAfter / publishedBeforeYYYY-MM-DD, inclusive
durationMinSecondsminimum length in seconds (to exclude Shorts, use channelTabs: ["videos"] — Shorts can run up to 3 minutes)
durationMaxSecondsupper bound in seconds
languageen, de, ja, es — text YouTube generates
regionUS, DE, GB — how results are ranked

Filters run after each video is read, so they are exact — and filtered-out videos are never charged.

subscriber_count follows the language as YouTube wrote it: 21.1M subscribers, 21,1 Mio. Abonnenten, チャンネル登録者数 2110万人. Titles, descriptions and category are unaffected.

What people use this for

📈 Track a competitor's channel every week

  1. Run with the competitor's channel and maxResultsPerSource: 50, maxAgeHours: 0 so view counts are fresh.
  2. Schedule it weekly in the Apify Console.
  3. Join runs on video_id and diff views to get per-video growth, and published_at to get posting cadence.

Set maxAgeHours: 0 here — the cache is a time window, and stale view counts would flatten exactly the trend you are measuring.

🔔 Watch a channel and get only what is new

  1. Run with the channel and publishedWithinDays: 7.
  2. Schedule it weekly in the Apify Console. That is the whole setup.

publishedWithinDays is a rolling window, counted from the day each run starts — so a saved task keeps returning what is new instead of the same seven days forever. publishedAfter is an absolute date and is the wrong tool for a schedule: set once, it returns the same videos every week until someone edits it. If you set both, the explicit date wins and the run log says which applied.

This is the cheapest thing this Actor does. Video rows it has already fetched come back from stored state and are not charged, and anything outside the window is filtered and free. Measured on a 500-video channel: the first run charged 489 videos, the second charged 11.

Use publishedWithinDays a little wider than your schedule — 8 or 9 days for a weekly run — so a late-publishing channel or a missed run does not leave a hole.

How this differs from a YouTube RSS feed. Every channel has a free RSS feed at youtube.com/feeds/videos.xml?channel_id=…, and if all you need is a title and a link, use it — it is free and instant. It gives you the most recent 15 videos and nothing else: no transcript, no view count, no duration, no description, no comments, and no way to reach further back. This Actor returns new videos with the full record, transcripts included, for any number of channels at once.

📺 Watch several channels at once

Put every channel in inputs — one run, one schedule, one dataset:

{
"inputs": [
"https://www.youtube.com/@veritasium",
"https://www.youtube.com/@3blue1brown",
"https://www.youtube.com/@kurzgesagt"
],
"publishedWithinDays": 8,
"includeTranscript": true,
"transcriptLanguages": ["en"]
}

Save it as a task and schedule it weekly. Each row carries _input, so you can tell which channel it came from. Video rows already fetched come back from stored state and are not charged again, so each run costs you mainly its new videos.

🤖 Build an LLM corpus from a channel's transcripts

  1. Run with the channel, includeTranscript: true, maxResultsPerSource: 500.
  2. Export as JSON and use transcript for plain text. Add includeTranscriptSegments: true when you need timestamps to cite back to a moment in the video — it is free, but it makes the rows much wider.
  3. Filter on transcript_status == "OK" — NO_CAPTIONS means that video never had subtitles, and it cost you nothing.

transcript_is_generated tells you whether a human wrote it. Auto-generated captions carry more errors, which matters if you are grounding an answer on them.

💬 Find what an audience actually asks about

  1. Run with the channel, includeComments: true, maxCommentsPerVideo: 100, and maxRepliesPerComment: 5 to catch the threads under the popular ones.
  2. Filter rows to _row_type == "comment".
  3. Group by video_id, sort by comment_like_count_text, read the top of each.

Useful for support-content planning, sponsorship research, and finding the question a whole audience keeps asking that nobody has answered yet.

What fields do I get?

fieldexample
video_id · video_typedQw4w9WgXcQ · video, short, stream
title · channel · channel_idRick Astley - Never Gonna Give You Up · Rick Astley · UCuAXFkgsw1L7xaCfnd5JJOw
views · likes1806183998 · 136841
published_at · duration_s2009-10-24T23:57:33-07:00 · 213
category · keywords · thumbnail · descriptionMusic · array · highest-res URL · full text
subscriber_count · channel_descriptionfrom channel inputs
is_live · is_private · is_crawlablebooleans
every row_row_type _input _source_url _site _cached _cached_at _error_kind _error
with transcriptstranscript transcript_cue_count transcript_language transcript_language_matched transcript_is_generated transcript_status
with includeTranscriptSegmentstranscript_segments — one entry per cue, start_s duration_s text
comment rowscomment_id comment_text comment_author comment_author_channel_id comment_author_is_verified comment_author_is_creator comment_published_text comment_like_count_text comment_reply_count_text comment_is_reply

Three comment fields end in _text because YouTube publishes them rounded and relative — 13K, 7 days ago — and they pass through as given. 13K is not expanded to 13000: it could be 13,499, and inventing digits hands you a wrong number that looks precise.

Not included: no sort order, no video or audio downloads, no total comment count per video.

What transcript_status means — and what it costs

statusmeaningtranscript charged?video row charged?asked again next run?
OKtranscript deliveredyes — the only charged statusyesno, while the cache holds it
NO_CAPTIONSthe video has no subtitles at allnonono
EMPTY_TRACKa subtitle track exists but is emptynonono
UNAVAILABLE: AGE_RESTRICTED · PRIVATE · MEMBERS_ONLY · REMOVEDYouTube will not serve it to anyone signed outnonono — remembered, not retried
UNAVAILABLE: … (anything else)YouTube refused after several attempts on fresh IPsnonoyes
TRACK_FETCH_FAILED: …the track was found but could not be downloadednonoyes
NO_REQUESTED_LANGUAGEonly with strictTranscriptLanguage: none of your languages exists, so the transcript was withheldnonono
emptytranscripts were off, or the video itself could not be fetched (see _error_kind)notranscripts off: yes · video failed: no—

If none of your transcriptLanguages exists, the video's first track is returned as OK with transcript_language_matched: false. Turn on strictTranscriptLanguage to withhold it instead.

Does it re-fetch videos I already pulled?

Any video fetched in the last 24 hours comes back from stored state — and the video row is not charged. Marked _cached: true with _cached_at.

A transcript is charged when it is fetched — normally once. A cached transcript is served free. The exception: the cache keeps only the most recent ~1.5 MB of transcript text, so an older transcript may have been dropped from it. Ask for that one again and it is fetched again, and charged again.

This is a time window, not change detection. It checks how long ago a video was read, not whether it changed — so set maxAgeHours to 0 when sorting on views or measuring growth. The window is per actor: yesterday's run counts for today's.

Cached rows still follow this run's settings. Turn transcripts on and a cached video with no stored transcript gets one fetched and charged; turn them off and the row carries no transcript fields at all, whatever an earlier run stored. The cache never adds a field you did not ask for, and never withholds one you did.

What does it cost?

you getyou pay
a video row$0.50 / 1,000 — with transcripts on, only when its transcript is delivered
a transcript$3.00–$5.00 / 1,000 — $5 on the Apify Free plan, $4 Starter, $3.50 Scale, $3 Business
a comment or reply$0.20 / 1,000
a failed or filtered row, a cached video row, or (with transcripts on) a video whose transcript was not deliverednothing
a transcript served from cachenothing — unless it had been dropped from the cache and is fetched again

Transcripts and comments are billed separately — each is a separate request to YouTube, not an extra field.

Send everything in one run, not one run each — it starts once and finishes sooner. Requests run 8 at a time, one per second each; concurrency adjusts that between 1 and 16.

Use it from code

Set APIFY_TOKEN to your token from Console → Settings → API & Integrations. Every example asks for three transcripts from one channel and keeps the rows where transcript_status is OK.

Python

# pip install apify-client
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("aquixlabs/youtube-channel-scraper").call(run_input={
"inputs": ["https://www.youtube.com/@veritasium"],
"maxResultsPerSource": 3,
"includeTranscript": True,
"transcriptLanguages": ["en"],
})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
if row.get("transcript_status") == "OK":
print(row["video_id"], row["title"], len(row["transcript"]), "chars")

JavaScript / Node

// npm install apify-client
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('aquixlabs/youtube-channel-scraper').call({
inputs: ['https://www.youtube.com/@veritasium'],
maxResultsPerSource: 3,
includeTranscript: true,
transcriptLanguages: ['en'],
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const row of items.filter((r) => r.transcript_status === 'OK')) {
console.log(row.video_id, row.title, row.transcript.length, 'chars');
}

curl (one call, rows straight back)

curl -s -X POST \
"https://api.apify.com/v2/acts/aquixlabs~youtube-channel-scraper/run-sync-get-dataset-items" \
-H "Authorization: Bearer $APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"inputs": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"],
"includeTranscript": true, "transcriptLanguages": ["en"]}'

The synchronous endpoint waits for the run to finish, so keep it to a few videos. For a whole channel, use a client library or start a run and fetch the dataset when it finishes.

LangChain (one Document per transcript)

# pip install langchain-apify langchain-core
import os
from langchain_apify import ApifyWrapper
from langchain_core.documents import Document
os.environ.setdefault("APIFY_API_TOKEN", os.environ["APIFY_TOKEN"])
loader = ApifyWrapper().call_actor(
actor_id="aquixlabs/youtube-channel-scraper",
run_input={"inputs": ["https://www.youtube.com/@veritasium"],
"maxResultsPerSource": 3, "includeTranscript": True,
"transcriptLanguages": ["en"]},
dataset_mapping_function=lambda row: Document(
page_content=row.get("transcript") or "",
metadata={"video_id": row.get("video_id"), "title": row.get("title"),
"url": row.get("_source_url"), "published_at": row.get("published_at")}),
)
docs = [d for d in loader.load() if d.page_content]

From an AI assistant (MCP)

Add Apify's MCP server to your client with this Actor preloaded:

https://mcp.apify.com/?tools=aquixlabs/youtube-channel-scraper

The tool appears as aquixlabs--youtube-channel-scraper. Ask for "the transcripts of the last 3 videos on @veritasium" and the assistant fills in the input itself.

For AI agents: pay per run with x402

This Actor is eligible for Apify's agentic payments. In Apify's words: "With the x402 protocol, AI agents can run Apify Actors and pay with USDC on the Base blockchain - no Apify account needed." The agent pays once (minimum $1) for a prepaid, spend-capped Apify API token from Apify AGI, then calls this Actor with that token like any other. Unused balance is non-refundable and the token expires 14 days after purchase. Setup, wallet and current terms: Apify's x402 docs.

  • MCP tool: aquixlabs--youtube-channel-scraper (load it with https://mcp.apify.com/?tools=aquixlabs/youtube-channel-scraper)
  • One request, with the prepaid token as $TOKEN:
curl -s -X POST \
"https://api.apify.com/v2/acts/aquixlabs~youtube-channel-scraper/run-sync-get-dataset-items" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"inputs": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"],
"includeTranscript": true, "transcriptLanguages": ["en"]}'
  • Price per event: a video row $0.0005 (with transcripts on, only when its transcript is delivered) · a transcript $0.003–$0.005 (by Apify plan, see What does it cost?) · a comment or reply $0.0002. Failed and filtered rows, and videos without a delivered transcript, cost nothing. Each run draws from the token's balance until it is used up.

Will it quietly break?

Every run emits a schema fingerprint — which fields resolved, which came back empty, the row count — compared against a stored baseline. A field that starts returning empty is caught as drift, not discovered months later.

Scrapers rarely break loudly. They thin out.

Frequently asked questions

How do I get a transcript from a YouTube video?

Paste the video URL into inputs and set includeTranscript to true. You get the full transcript as text in the transcript field, along with its language and whether YouTube auto-generated it. No API key and no browser extension.

{ "inputs": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"],
"includeTranscript": true }

How do I get the script of a YouTube video?

The same way — what YouTube calls captions or subtitles is what most people mean by the script. transcript holds it as one block of text, ready to paste into a document or feed to a model.

How do I download YouTube transcripts for a whole channel at once?

Give it the channel instead of the video. inputs accepts an @handle, a channel URL or a /channel/UC… ID, and maxResultsPerSource decides how many videos deep to go. With includeTranscript: true, every video comes back as a row with its own transcript.

Can I get the transcript with timestamps?

Yes. Turn on includeTranscriptSegments and each row also carries transcript_segments — one entry per cue with start_s, duration_s and text — so you can cite the exact moment something was said. It costs nothing extra. It is off by default because spreadsheets flatten it into thousands of columns; transcript_cue_count ships either way.

What happens if a video has no subtitles?

transcript_status comes back as NO_CAPTIONS and you are not charged for that video at all — neither the transcript nor the video row. It is a different value from UNAVAILABLE and TRACK_FETCH_FAILED (the transcript request failed), so you can always tell "this video has no subtitles" apart from "we could not reach it" — and re-run only the second kind. A failed request is never served from the cache: the next run asks again. Age-restricted and members-only videos come back as UNAVAILABLE: AGE_RESTRICTED / UNAVAILABLE: MEMBERS_ONLY — final, never charged, and not asked again inside the cache window.

Do I need the YouTube Data API or an API key?

No. There is no key to obtain, no project to create, and no daily quota to run out of. That is the main reason people use this instead of the official API.

Is there a free way to do this?

Apify's free plan includes $5 of platform usage every month, no credit card required. At the Free-plan price ($5 per 1,000 transcripts plus $0.50 per 1,000 videos) that is about 900 transcripts a month at no cost to you. On top of that, failed, filtered and cached video rows cost nothing, and so does any video whose transcript was not delivered, so you pay for the transcripts you actually receive — normally once per video inside the cache window.

Can I get transcripts in another language?

transcriptLanguages is a preference order, for example ["en", "es"]. Human-written subtitles are preferred over auto-generated ones when both exist. transcript_language and transcript_is_generated tell you which you got.

If none of your languages exists for a video, its own first track comes back (human-written before auto-generated) and transcript_language_matched is false, so you can filter on it. Turn on strictTranscriptLanguage to withhold those instead: transcript_status becomes NO_REQUESTED_LANGUAGE, there is no text, and the transcript is not charged.

Can I use this from Claude, Cursor or another AI assistant?

Yes. It is exposed through Apify's MCP server at mcp.apify.com, so any client that speaks Model Context Protocol can discover it with search-actors and run it with call-actor under the name aquixlabs/youtube-channel-scraper. Nothing to install and nothing to configure.

A useful shape for an agent: ask it for the transcript of a video or a channel, and it can call this directly rather than scraping the page itself.

Can I get the comments as well as the transcript?

Yes, in the same run. Set includeComments and each comment arrives as its own row — not a column — tagged _row_type: comment. The dataset's Comments view is where to read them.

Was this useful?

If it saved you time, a short review on the Store is the main way other people find it. If something broke or a field is missing, open an issue on the Issues tab instead; I read every one, and a broken run is fixed before anything new is built.

Terms

You are responsible for your use of the data you collect, including under YouTube's Terms of Service and any applicable data protection law.


中文说明

YouTube 字幕 / 转录 API。 输入单个视频、整个频道、播放列表或搜索词,一次运行即可批量获取 YouTube 视频的完整字幕文本(含时间戳和语言),以及播放量、发布日期和评论。输出 JSON、CSV 或 Excel,适用于 AI、RAG 和大语言模型(LLM)数据管道。无需 YouTube API 密钥,没有配额限制。

价格: 字幕每 1,000 条起价 $3(Apify 免费套餐为 $5),另加每 1,000 个视频 $0.50。 没有字幕的视频不收取字幕费用,失败的视频免费。

输入示例:

{ "inputs": ["https://www.youtube.com/@veritasium"],
"includeTranscript": true, "transcriptLanguages": ["zh", "en"] }