YouTube Video Downloader & Scraper - YouTube All-in-One avatar

YouTube Video Downloader & Scraper - YouTube All-in-One

Pricing

from $135.00 / 1,000 video downloads

Go to Apify Store
YouTube Video Downloader & Scraper - YouTube All-in-One

YouTube Video Downloader & Scraper - YouTube All-in-One

Download YouTube videos, Shorts, playlists, and channels as MP4. Up to 10 concurrent downloads with no browser needed. Extract comments, captions, and rich metadata. Metadata-only mode for fast, cheap research. Quality selection with automatic fallback. $0.15/video download, $0.005 metadata-only.

Pricing

from $135.00 / 1,000 video downloads

Rating

0.0

(0)

Developer

jy-labs

jy-labs

Maintained by Community

Actor stats

0

Bookmarked

141

Total users

23

Monthly active users

6 days ago

Last modified

Share

YouTube All-in-One Downloader & Scraper

Turn YouTube URLs, playlists, channels or search queries into structured JSON: title, channel, views, likes, duration, upload date, tags, chapters, category, captions, comments with replies, channel details and related videos. Choose between an LLM-ready transcript row for AI pipelines, metadata-only rows for research, or MP4/M4A file downloads saved to your run's key-value store. The Actor talks to YouTube's InnerTube API through youtubei.js, so no browser is started and no YouTube Data API key is needed. Up to 10 videos are processed in parallel, and you pay per delivered video.

Independent tool. Not affiliated with, endorsed by, or sponsored by YouTube or Google. YouTube is a trademark of Google LLC.

What you get

  • Transcripts for AI pipelines. Set outputFormat: "llm_ready" and each video becomes one flat row with a plain-text transcript, a wordCount, the transcript language, and the core metadata. No file is downloaded.
  • Rich metadata. videoId, title, description, channelName, channelUrl, viewCount, likeCount, duration, durationSeconds, uploadDate, thumbnailUrl, tags, category, chapters, isLive and isShort, plus playlistIndex and playlistTitle when the video came from a playlist.
  • Captions and subtitle files. You can get the caption text in the row, an SRT or VTT file in the key-value store, or both. You can prefer a language and optionally have YouTube translate the track.
  • Comments with replies. Top comments with author, text, likes, published time and reply count. You can add the reply threads and keep up to 500 comments per video.
  • Channel details and related videos. Subscriber count, channel description, banner, video count and join date, plus up to 20 related videos.
  • File downloads. Choose 1080p (the default), highest, 720p, 480p, 360p or audio_only. If the resolution you ask for isn't offered, the Actor falls back to the nearest lower one. If its file would be over maxFileSizeMb, the Actor downloads the highest lower resolution that fits and notes it in qualityFallback. The row's quality is the delivered format's label, such as 720p60, or 534p for a widescreen film. Files are stored in the run's key-value store, and each row links to its file with a signed downloadUrl. See Limits & notes for what a video file contains.
  • Batch and incremental runs. You can mix videos, Shorts, playlists, channels and search queries in one run. Duplicates are removed. Monitor mode skips videos that an earlier run already processed.
  • Honest billing. You're charged the video download price only when a file is actually delivered. If the metadata arrives but the file doesn't, the row is billed at the metadata price. Failed videos cost nothing.

Quick start

  1. Click Try for free and paste one or more YouTube URLs into YouTube URLs.
  2. Pick what you need: a transcript, metadata, or a file download.
  3. Click Start. Each video becomes one dataset row, and downloaded files go to the run's key-value store.

This is the listing's example input. It returns one transcript row and no file:

{
"startUrls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"],
"downloadVideo": false,
"extractMetadata": true,
"outputFormat": "llm_ready",
"extractCaptions": true,
"downloadCaptions": false,
"captionLanguage": "en",
"maxVideos": 1,
"proxyConfiguration": { "useApifyProxy": true }
}

To download a file, leave downloadVideo on (the default) and choose a quality:

{
"startUrls": ["https://www.youtube.com/watch?v=jNQXAC9IVRw"],
"quality": "360p",
"maxVideos": 1
}

Before you download. Keep the RESIDENTIAL proxy group (the default). YouTube's bot check blocks most datacenter IPs, so downloads without it mostly fail (details). The default quality: "1080p" takes the highest resolution at or below 1080p, in H.264 when YouTube offers it. quality: "highest" can pick an AV1 1440p or 4K stream: a ~120 MB file costs about $1 of residential transfer, and it can exceed the default 200 MB maxFileSizeMb on longer videos. The Actor then downloads the highest lower resolution that fits, such as 1080p, and the row's qualityFallback says so. You get a size_limit_exceeded metadata row only when no lower resolution has a size YouTube reports within the cap (one with no reported size isn't picked in advance), or when the download still overflows mid-way after two step-downs. To keep 4K on long videos, set quality: "highest" and raise maxFileSizeMb (highest is capped at 500 MB).

Input

At least one of startUrls or searchQueries is required. Everything else is optional.

Key fields

FieldDefaultWhat it does
startUrlsnoneVideo, Shorts, playlist or channel URLs, mixed freely
searchQueriesnoneYouTube searches; the videos found are processed too
outputFormatdefaultllm_ready returns a flat transcript row and no file
downloadVideotruefalse returns metadata-only rows at the metadata price
quality1080phighest, 1080p, 720p, 480p, 360p, audio_only
maxVideos1001–1000 videos per run, across all URLs and searches
maxFileSizeMb2001–2000. Over it, a lower resolution with a reported size that fits is delivered (qualityFallback); otherwise skipped. Your cap on transfer cost (details)
extractMetadatatruefalse returns only sourceUrl, downloadUrl, videoId, quality, fileSize
extractCaptionsfalseCaption text in captions
extractCommentsfalseTop comments in comments
maxConcurrency4Videos processed in parallel (1–10)
proxyConfigurationApify, RESIDENTIALGroups downloads run on, from the first attempt. Keep RESIDENTIAL (details)

extractMetadata switches on automatically when you set downloadVideo: false or turn on captions, comments, channel info, related videos, caption files or thumbnails. outputFormat: "llm_ready" also forces extractCaptions: true and downloadVideo: false, and turns off channel info, related videos, thumbnails, comments and caption files, because the transcript row has no field for them.

Captions

  • captionLanguage (default en) is the preferred language code, such as en, ko, ja or pt-BR. In that language, manual captions win over auto-generated ones. If the language isn't available, caption text uses the first manual track (or the first track), and the caption file uses the first track.
  • autoTranslateLanguage asks YouTube to translate the chosen track. If the translated caption text can't be fetched, the original is returned.
  • downloadCaptions also saves a caption file and returns captionFileUrl and captionFileLanguage. captionFormat is srt (default) or vtt.
  • maxComments (default 100, max 500) sets how many top-level comments to take, in YouTube's "Top" order. extractReplies adds a replies array to comments that have replies.
  • extractChannelInfo adds a channelInfo object. Each channel is looked up only once per run.
  • extractRelatedVideos adds up to 20 relatedVideos.
  • downloadThumbnail saves the thumbnail and returns thumbnailDownloadUrl.

File names

filenameTemplate (default {videoId}_{type}) accepts {videoId}, {title}, {quality}, {channelName}, {date} (the run date) and {type}. Invalid characters are removed and names are cut to 200 characters. Videos get .mp4 and audio gets .m4a. Caption files are always named {videoId}_captions_{language}.{srt|vtt}.

Monitoring and webhook

  • With monitorMode and a stateStoreName, the processed video IDs are kept in that named key-value store, and later runs skip them. Use one store name per job. A video whose file you asked for but didn't get (a download_failed row) isn't recorded at first, so the next run tries it again and emits and bills its row again. After its third download_failed row across runs, the video is recorded and later runs skip it. A size_limit_exceeded row is recorded, because the size cap is your own setting: later runs skip that video, so to get its file, raise maxFileSizeMb or lower quality and run it without monitor mode or with a new stateStoreName. In monitor mode a spending limit too low for the next download doesn't fall back to metadata rows: the run stops starting videos, charges nothing for them, and the next run tries them again.
  • webhookUrl receives a POST when the run finishes, including runs with nothing to process, such as a monitor run that found no new videos. The body contains actorId, runId, processed, downloaded, downloadFailed, sizeLimitExceeded, downloadSkippedByChargeLimit, failed, stoppedByChargeLimit, total, successRate, totalTimeSeconds, avgTimePerVideoSeconds, noNewVideos and completedAt. noNewVideos is true only when monitor mode skipped every video as already processed. A run that found no valid video, for example because the URLs were invalid, reports total: 0 with noNewVideos: false.

Reliability and proxy

  • maxRequestRetries (default 3, max 10) sets retries per video, with exponential backoff. includeFailedVideos adds rows for videos that delivered nothing.
  • Downloads start on the groups in proxyConfiguration (RESIDENTIAL by default) from the first attempt. YouTube's bot check blocks about 4 in 5 datacenter exits, and a datacenter try could waste up to about 2.5 minutes per video. With Apify Proxy on and at least one retry allowed, metadata-only and llm_ready runs (and videos a download run delivers as insufficient_budget metadata rows) make the first attempt for each video through Apify's datacenter proxy, which is cheaper and works for metadata, and retry on your groups if it fails. Your proxy country carries over. Custom proxy URLs are used for every attempt.
  • A download counts as failed when the metadata arrives and the download was tried, but no file arrives. A failed download gets one more attempt at the download, and then the metadata row is returned. A dead proxy exit is different: when every client on that exit gets no response at all and no file bytes have arrived, the Actor moves to a new exit right away, with no backoff. It does this up to 3 times per video, and these attempts don't use up the failed download's one more attempt. After 3 dead exits, the next one counts as a failed download. If an exit dies after file bytes have arrived, it counts as a failed download too.
  • Retries follow the reason YouTube gives. Private, deleted, removed, unavailable and members-only videos are never retried. Age-restricted and geo-blocked videos stop after two attempts on your proxy group. YouTube's "Sign in to confirm you're not a bot" check is about the exit IP, not the video, so it is retried on a new exit, up to three attempts on your proxy group. Rate limits and bot checks on your proxy group get longer backoff. An attempt the bot check stops before any download is tried counts toward these three bot-check attempts, not toward a failed download's one more attempt, so a failed download followed by a bot check still gets a new exit. A video never gets more than maxRequestRetries + 1 attempts.
  • Downloads need the RESIDENTIAL proxy group. YouTube answers most datacenter IPs with "Sign in to confirm you're not a bot", for every client and with or without a token, and usually returns no metadata at all. In our test on September 30, 2026, about 4 in 5 Apify datacenter exits got that answer. When an attempt hits the check, the Actor doesn't try the other clients on that exit. On a metadata-only run, a datacenter attempt that hits it moves to your proxy group right away, with no backoff. If your proxy groups are datacenter only (for example { "useApifyProxy": true } with no group selected), the run log warns at the start, most downloads fail, and each failed row's error says the bot check blocked it and that downloads need RESIDENTIAL.
  • How a file is fetched. YouTube now stops a normal video stream at about 60 seconds of media unless the request carries a proof-of-origin (PO) token. The Actor starts one BotGuard session per run, through the proxy of the first download that needs it, and mints a token from each video's ID. It then downloads the video and audio tracks over YouTube's SABR streaming protocol, the way the web player does, through the same proxy exit as the rest of that attempt. If this fails, the Actor falls back to the mobile web client with the token (only when YouTube still gives it direct file URLs), then to the iOS client, which mostly helps with Shorts. Starting BotGuard and minting a token each get 30 seconds. If either fails or takes longer, that download goes on without a token. After a failed BotGuard start, the Actor waits 5 minutes before trying again, and it stops trying after 3 failed starts in a run. Metadata-only and llm_ready runs never mint a token.

Supported URLs

watch?v=ID, youtu.be/ID, /shorts/ID, /embed/ID, /playlist?list=ID, /@handle and /channel/ID. A URL with both v= and list= is treated as that single video.

Output

Each processed video becomes one row in the default dataset. Rows are written as each video finishes, so their order may differ from the order of your input. The dataset has these views: Overview, Full Details, Captions, Comments, Chapters, Related Videos, LLM Ready and Errors.

Default row

This row is from a real quality: "360p" run, shortened. The video's best format was 240p, so the Actor fell back to it:

{
"sourceUrl": "https://www.youtube.com/watch?v=jNQXAC9IVRw",
"downloadUrl": "https://api.apify.com/v2/key-value-stores/STORE_ID/records/jNQXAC9IVRw_video.mp4?signature=…",
"videoId": "jNQXAC9IVRw",
"title": "Me at the zoo",
"description": "…",
"channelName": "jawed",
"channelUrl": "http://www.youtube.com/@jawed",
"viewCount": 438024470,
"likeCount": 19954003,
"duration": "0:19",
"durationSeconds": 19,
"uploadDate": "Apr 24, 2005",
"thumbnailUrl": "https://i.ytimg.com/vi/jNQXAC9IVRw/hqdefault.jpg?…",
"quality": "240p",
"fileSize": "218.53 KB",
"captions": [],
"comments": [],
"tags": ["me at the zoo", "jawed karim", "first youtube video"],
"isLive": false,
"isShort": false,
"chapters": [
{ "title": "Intro", "startTime": "0:00", "startTimeSeconds": 0 },
{ "title": "End", "startTime": "0:17", "startTimeSeconds": 17 }
],
"category": "Film & Animation",
"relatedVideos": []
}

quality shows what was actually delivered. It is a height such as 720p for a specific request, best for highest or audio_only, and none when no file was saved. isShort is true only when the input URL used the /shorts/ form. Chapters come from YouTube's chapter markers, or from timestamps in the description if there are none.

Optional fields

These fields are filled when the matching input is on. The values below are illustrative:

{
"captions": [{ "language": "English", "languageCode": "en", "text": "We're no strangers to love…" }],
"captionFileUrl": "https://api.apify.com/v2/key-value-stores/STORE_ID/records/dQw4w9WgXcQ_captions_en.srt?signature=…",
"captionFileLanguage": "en",
"comments": [
{
"author": "@someone",
"authorChannelUrl": "https://www.youtube.com/@someone",
"text": "Still a classic.",
"likes": 1200,
"publishedTime": "2 years ago",
"replyCount": 14,
"replies": [
{
"author": "@another",
"authorChannelUrl": "https://www.youtube.com/@another",
"text": "Agreed.",
"likes": 30,
"publishedTime": "1 year ago"
}
]
}
],
"channelInfo": {
"channelId": "@RickAstleyYT",
"subscriberCount": "4.5M subscribers",
"description": "…",
"bannerUrl": "https://yt3.googleusercontent.com/…",
"videoCount": "400 videos",
"joinedDate": "Joined Feb 2, 2015"
},
"relatedVideos": [
{ "videoId": "VIDEO_ID", "title": "…", "channelName": "…", "viewCount": "1.2M views", "duration": "3:24" }
],
"thumbnailDownloadUrl": "https://api.apify.com/v2/key-value-stores/STORE_ID/records/dQw4w9WgXcQ_thumbnail?signature=…",
"playlistIndex": 1,
"playlistTitle": "My playlist"
}

captions holds at most one track: your preferred language, or the fallback described in Captions. If a video has no captions, captions is [] and the rest of the row is still delivered. Counts inside channelInfo and relatedVideos are the text YouTube displays, not numbers.

Metadata-only row (downloadVideo: false)

This is the same shape as the default row, with downloadUrl: null, quality: "none" and fileSize: "". It has no downloadSkippedReason.

LLM-ready row (outputFormat: "llm_ready")

{
"videoId": "dQw4w9WgXcQ",
"title": "Rick Astley - Never Gonna Give You Up (Official Video) (4K Remaster)",
"channelName": "Rick Astley",
"channelUrl": "http://www.youtube.com/@RickAstleyYT",
"transcript": "We're no strangers to love You know the rules and so do I…",
"wordCount": 427,
"language": "English",
"languageCode": "en",
"sourceUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"duration": "3:33",
"durationSeconds": 213,
"viewCount": 1820844904,
"uploadDate": "Oct 25, 2009",
"category": "Music",
"tags": ["rick astley", "Never Gonna Give You Up", "nggyu"],
"description": "The official video for “Never Gonna Give You Up” by Rick Astley…"
}

transcript is the caption text as one plain string. wordCount is the number of words separated by whitespace. language is YouTube's track name. When auto-translate is used, it reads like English → ko and languageCode holds the target language. If a video has no captions, you still get the row, with transcript: "" and wordCount: 0.

Download requested but not delivered

Sometimes the metadata can be read but the file can't be delivered. The usual reasons are that YouTube's bot check blocked the download on every exit tried (see Reliability and proxy), that every streaming client was refused (age restriction, DRM, region lock), or that the file is larger than the size cap and no lower resolution could be delivered within it (see Limits & notes). A refused download first gets one more attempt, as described in Reliability and proxy. A file over the size cap is not retried, because another exit would fetch the same file. If no attempt delivers the file and extractMetadata is on (the default), you still get a full metadata row, billed at the metadata price (shortened here):

{
"sourceUrl": "https://www.youtube.com/watch?v=9bZkp7q19f0",
"downloadUrl": null,
"videoId": "9bZkp7q19f0",
"title": "PSY - GANGNAM STYLE(강남스타일) M/V",
"channelName": "officialpsy",
"viewCount": 6076408859,
"duration": "4:12",
"quality": "none",
"fileSize": "",
"downloadSkippedReason": "download_failed",
"category": "Music"
}

downloadSkippedReason is download_failed, size_limit_exceeded, or insufficient_budget (your spending limit no longer covered a video-download, so the download was not started; see Spending limit). It appears only on these rows, so you can filter on it. With extractMetadata: false, failed and oversized downloads count as failed instead, and a run whose limit no longer covers a download stops starting videos.

Lightweight row (extractMetadata: false)

{
"sourceUrl": "https://www.youtube.com/watch?v=jNQXAC9IVRw",
"downloadUrl": "https://api.apify.com/v2/key-value-stores/STORE_ID/records/jNQXAC9IVRw_video.mp4?signature=…",
"videoId": "jNQXAC9IVRw",
"quality": "240p",
"fileSize": "218.53 KB"
}

Failed row (includeFailedVideos: true)

A failed row means nothing was delivered: no metadata and no file. These rows are never billed.

{
"sourceUrl": "https://www.youtube.com/watch?v=VIDEO_ID",
"downloadUrl": null,
"error": "…",
"status": "failed"
}

Pricing

This Actor uses pay-per-event pricing, with no monthly rental. You pay the event prices below, and you also pay the Apify platform usage of your runs: compute, storage and proxy data transfer. These prices are current as of September 2026.

Event prices

EventCharged whenFreeBronzeSilver / Gold / Platinum / Diamond
video-downloadA video or audio file was delivered$0.15$0.1425$0.135
metadata-extractionA row was delivered without a file$0.005$0.00475$0.0045
apify-actor-startOnce per run, per GB of run memory (minimum one)$0.00005$0.00005$0.00005

Your price tier follows your Apify subscription plan.

Which event a row triggers

  • video-download: a file was delivered (downloadUrl is set).
  • metadata-extraction: a row without a file. That covers llm_ready rows (even with an empty transcript), downloadVideo: false, and rows with downloadSkippedReason.
  • Nothing: failed rows, and videos skipped by monitor mode.

Spending limit. If you set a maximum cost per run, the Actor never delivers a row it can't charge for. It starts a new video only while your limit still covers one more row (a video-download on download runs), counting the videos already in progress. When what is left of your limit is less than one video-download but still covers a metadata-extraction, extractMetadata is on (the default), and monitor mode with a stateStoreName is not on, the Actor stops downloading: each further video is delivered as its metadata row without the file, marked downloadSkippedReason: "insufficient_budget" and billed as metadata-extraction. Like a metadata-only run, these videos make their first attempt through Apify's datacenter proxy (with Apify Proxy on and at least one retry allowed) and move to your groups only if it fails, so they add little residential transfer. The run's status message says how many downloads were skipped this way (downloadSkippedByChargeLimit in the webhook body). To get the files, raise the limit above your tier's video-download price. When the limit can't cover even a metadata row (or, with monitor mode and a stateStoreName, can't cover the next download), the Actor starts no more videos and ends the run with a status message saying how many videos the spending limit stopped (stoppedByChargeLimit in the webhook body). Rows already delivered stay.

Platform usage

Platform usage depends on run memory, run time, and how much data goes through the proxy. For downloads, proxy transfer is by far the largest part.

  • Downloads run on the residential proxy. Download runs start on your RESIDENTIAL group, because YouTube's bot check blocked about 4 in 5 datacenter exits in our September 30, 2026 test. The PO token a download needs is minted through that same exit, which adds the youtube.com page and the BotGuard script to the residential transfer, usually once per run. On a download run, expanding playlists, channels, and search queries into videos also goes through RESIDENTIAL, but those are small pages.
  • Residential transfer costs about $8/GB in our runs (your plan's rate applies), and you pay it as platform usage on top of the event price. A 100 MB file costs about $0.80 of usage on top of the $0.15 video-download event. A 4-minute video at 720p was 27 MB in our test, or about $0.22 of transfer.
  • To control cost, keep the default 1080p or pick 720p rather than highest, which can choose a large AV1 1440p or 4K stream (about $1 of transfer for a ~120 MB file). Set maxFileSizeMb (1–2000) to the biggest file you're willing to pay transfer for. A video over it comes at the highest lower resolution that fits; a video with no such resolution is skipped and billed as a metadata row.

Metadata-only runs move little data. One metadata-only video took 9 s and under $0.001 of platform usage at 1 GB run memory. Compute scales with memory, so at the default 2 GB expect up to about twice that, under $0.002. These figures are for metadata only, not for downloads. If you only extract metadata or use llm_ready, you can lower memory to 512–1024 MB in the run options to save compute.

Worked examples (Free tier prices)

JobEvent charges
1,000 transcripts or metadata rows1,000 × $0.005 = $5.00 ($4.50 on Silver+)
100 video downloads, all delivered100 × $0.15 = $15.00 ($13.50 on Silver+)
50 downloads: 45 files, 3 download_failed, 2 private45 × $0.15 + 3 × $0.005 = $6.765 (private: $0)
One run at the default 2 GB memoryapify-actor-start 2 × $0.00005 = $0.0001

Platform usage is added on top of every example.

Use cases

  • RAG and embeddings. Pull transcripts from a playlist or channel with llm_ready, split them into chunks, and embed them. Chapters in default mode give natural chunk boundaries.
  • Training and evaluation datasets. Use searchQueries plus llm_ready to collect topic-specific transcripts with title, channel and tags. Export the dataset as JSONL.
  • Channel and competitor monitoring. Schedule a run on a channel with monitorMode and downloadVideo: false. Each run then processes only videos it hasn't seen, and the webhook tells your system when it finishes.
  • Comment and sentiment analysis. Set downloadVideo: false, extractComments: true and extractReplies: true, and feed the threads to your NLP pipeline.
  • Subtitle files. Set downloadCaptions: true with captionFormat: "srt" or "vtt". Add autoTranslateLanguage for a translated track.
  • Archiving your own content. Download files you own or have permission to store, together with their metadata.

Use via API

The Actor ID is jy-labs/youtube-all-in-one-downloader-scraper. In URLs, use jy-labs~youtube-all-in-one-downloader-scraper. Get your API token from Apify Console → Settings → API & Integrations.

cURL: run and get items in one call

curl -X POST \
"https://api.apify.com/v2/acts/jy-labs~youtube-all-in-one-downloader-scraper/run-sync-get-dataset-items?token=YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"startUrls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"],
"outputFormat": "llm_ready",
"captionLanguage": "en"
}'

The response is the dataset rows as a JSON array. Synchronous runs have a time limit on Apify's side (currently 300 seconds), so for large batches, start the run asynchronously with POST /v2/acts/jy-labs~youtube-all-in-one-downloader-scraper/runs and read the dataset when the run finishes.

JavaScript (apify-client)

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('jy-labs/youtube-all-in-one-downloader-scraper').call({
startUrls: ['https://www.youtube.com/playlist?list=PLAYLIST_ID'],
outputFormat: 'llm_ready',
maxVideos: 50,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const item of items) {
console.log(item.title, item.wordCount);
}

Python (apify-client)

import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("jy-labs/youtube-all-in-one-downloader-scraper").call(run_input={
"startUrls": ["https://www.youtube.com/watch?v=jNQXAC9IVRw"],
"quality": "720p",
"maxFileSizeMb": 100,
})
for item in client.dataset(run["defaultDatasetId"]).list_items().items:
print(item.get("title"), item.get("downloadUrl"), item.get("downloadSkippedReason"))

The same input works from Apify integrations, schedules and webhooks.

Limits & notes

  • Every video file has audio. YouTube often serves video and audio as separate streams. When it does, the Actor downloads both and merges them into one .mp4 without re-encoding. The video is MP4: H.264 when YouTube offers it at the chosen resolution, otherwise AV1 (usually above 1080p, so highest often gives AV1). The audio is AAC. The size caps below apply to the merged file. A silent file is never saved or billed: if no attempt can get an audio stream, the row gets downloadSkippedReason: "download_failed" and is billed as metadata-extraction, not video-download.
  • Size caps. A file may not be larger than maxFileSizeMb (default 200 MB, at most 2000) or the fixed cap for your quality setting, whichever is smaller. The fixed caps are: 360p 100 MB, 480p 150 MB, 720p 250 MB, 1080p 400 MB, highest 500 MB, audio_only 50 MB. With the default settings, 720p and above are limited to 200 MB, and a highest download that picks AV1 4K can exceed that on longer videos. When the resolution you asked for is over the cap, explicit ones like 1080p included, the Actor picks the highest lower resolution whose size YouTube reports within the cap, before downloading anything. The row then carries qualityFallback: { "requested": "2160p", "delivered": "1080p", "reason": "size_limit" }, its quality is the delivered one, and it is billed as a normal video-download. A lower resolution with no reported size isn't picked this way. If a stream turns out larger than reported and crosses the cap mid-way, the Actor stops it and tries the next lower resolution, at most twice per video. The video is skipped (size_limit_exceeded) only when no lower resolution has a size YouTube reports within the cap (one with no reported size isn't picked in advance), or when the download still overflows mid-way after two step-downs. audio_only has no lower step: an audio track over the cap is skipped.
  • Time per attempt. The SABR download gets 60 seconds plus 1 second per MB of the expected file, up to 10 minutes, for the video and audio tracks together. A stalled small file therefore fails in about a minute. The merge then gets 60 seconds. Each fallback client gets 60 seconds for the video stream, another 60 seconds for the separate audio stream, and 60 seconds for the merge. Large files on slow routes can time out. A mid-stream step-down runs the download again within the same attempt with a fresh time budget, so with its two step-downs one attempt can transfer up to three times the size cap and take about three times as long. The fallback clients and one more attempt on your proxy group are tried first, and if they fail the row ends up as download_failed.
  • Memory and disk. Every download streams its tracks to temporary files, merges them on disk and uploads the result from the file, so memory use doesn't grow with file size. Each download in progress needs free disk for about twice its file size while its tracks are merged.
  • Videos with no file. Private, deleted and unavailable videos give no metadata and become failed rows. Age-restricted, DRM-protected or region-locked videos may still return metadata. In that case they become download_failed rows after one more attempt on your proxy group.
  • Playlists and channels. The Actor reads only the first page of results that YouTube returns for a playlist or a channel's Videos tab. Long playlists and big channels can therefore give fewer videos than maxVideos. Supported channel URLs are @handle and /channel/ID.
  • Search. Queries run in order, before the URLs, and share the maxVideos budget. This means the first query can use up the whole budget.
  • Shorts found by expanding a playlist, channel or search get isShort: false, because only /shorts/ input URLs are flagged.
  • Storage. Files are saved in the run's default key-value store and kept for your plan's data-retention period. Copy anything you want to keep. The downloadUrl links are signed, so they open without your API token.
  • Proxy. Turning the proxy off takes downloads off the residential proxy they run on, and YouTube may then block requests. Downloads through datacenter exits alone mostly fail at YouTube's bot check (see Reliability and proxy).

Download only content you own or have the rights or permission to use. Respect YouTube's Terms of Service, copyright law, and the privacy of commenters. Comments and channel data can include personal data, so handle them according to the laws that apply to you, such as GDPR. You are responsible for how you use the data and files this Actor returns.

FAQ

How do I get only transcripts, as cheaply as possible? Set outputFormat: "llm_ready". No file is downloaded, and each video is billed as metadata-extraction ($0.005 on the Free tier).

What if a video has no captions? In llm_ready mode you get the row with an empty transcript and wordCount: 0, billed as metadata. In default mode, captions is [].

Can I get transcripts in another language? Yes. Set captionLanguage. If that language isn't available, the Actor uses a different track, so check languageCode on the row. To get a translation instead, set autoTranslateLanguage.

What happens when my quality isn't available? The Actor takes the highest resolution at or below your request. If every format is above your request, it takes the lowest one available. If the file at that resolution is over maxFileSizeMb, it goes down to the highest resolution that fits and sets qualityFallback. The quality field shows what you actually got.

Am I charged for failed videos? No. Rows with status: "failed" and videos skipped by monitor mode aren't charged. You still pay the platform usage of the run.

Can I get only the new videos from a channel every day? Yes. Turn on monitorMode, set a stateStoreName, and schedule the Actor. Because channels are read from their first page of videos, schedule often enough that new uploads are still on that page.

Do I need a YouTube API key? No. The Actor doesn't use the YouTube Data API.

Support

Found a bug or need a feature? Open an issue on the Actor's Issues tab and include the run ID. Rows with includeFailedVideos: true and the run log make problems much faster to fix.