YouTube Thumbnail Downloader

YouTube Thumbnail Bulk Download: 30 IDs per Page Load, Then a POST

There is no bulk endpoint, and the images themselves need nothing — no key, no cookie, no login. A channel's /videos page hands back exactly 30 video IDs — thirty on each of three different channels we tried — and, on the channel we sorted, the same 30 in the same order whether you append ?sort=popular or ?sort=oldest, because the sort is applied later by script, not in the HTML you download. The next thirty are not a URL at all — they are a POST to /youtubei/v1/browse carrying a token copied out of that HTML, and each round of thirty costs about 1.2 MB of JSON. The pictures never pushed back: 180 requests over 30 videos and 6 sizes, zero 403, zero 429. What breaks a bulk run is elsewhere — two of five long-standing uploads have no 1280 × 720 file at all, and 180 files arrive under six names. All of it measured on 22 October 2026.

Flat vector diagram: a card holding a six-by-five grid of thirty small video frames labelled 30 IDs, an arrow leading to a door labelled POST with a key, and on the right a list of four file names each tagged x30
Thirty frames per page load; the next thirty come through a door marked POST; everything lands under six names.

One request, thirty IDs — and the feed's fifteen

We fetched the same tab from three unrelated channels with large catalogues and counted the eleven-character IDs in the raw HTML. Not "about thirty" — thirty on every one of them, after de-duplicating 210 occurrences of the key down to 30 distinct values. The public RSS feed is the other door, and it is exactly half as wide.

SourceHTML sizeIDs in the HTMLDistinct IDs
/channel/<id>/videos — channel 11,340,928 B21030
/channel/<id>/videos — channel 21,335,529 B21030
/channel/<id>/videos — channel 31,349,995 B21030
/feeds/videos.xml?channel_id=<id>35,395 B / 20,514 B / 40,021 B15 entries15

Every one of those IDs is real, not a template artefact. We took the first twenty from each channel — 60 IDs — and asked the image host for hqdefault.jpg on each: 60 of 60 came back as an actual 480 × 360 JPEG. So the extraction step is reliable; the limit is the page, not your parser.

One number worth keeping: 210 occurrences for 30 videos means each ID is written into the page seven times. If you dedupe by counting matches instead of collecting unique values, a 30-video page looks like a 210-video page and your "missing" arithmetic is wrong from the start.

The ?sort= parameter is not a second door

The obvious trick for bulk work is to ask for a different order and harvest another page. It does not work on the static HTML. Three URLs, three identical lists — identical down to the order:

URL variantDistinct IDsFirst threeLast one
?view=0&sort=dd&flow=grid30RE3…, plN…, v9Q…_Ab…
?view=0&sort=p&flow=grid30RE3…, plN…, v9Q…_Ab…
?view=0&sort=da&flow=grid30RE3…, plN…, v9Q…_Ab…

All three lists compared equal element by element, not just as sets: 30/30 overlap for every pair, and the union of all three orders was still 30. The parameter is read client-side after the page has loaded; what arrives over the wire is the newest thirty regardless. That single fact is why "download a whole channel" is not a matter of looping over pages of a URL.

Page two is a POST, and it costs 1.2 MB of JSON

The page does contain the way forward — six continuationCommand tokens and its own INNERTUBE_API_KEY, both sitting in the HTML as plain text. You post one token back to the browse endpoint and receive the next thirty. Three rounds, three fresh batches, 120 distinct IDs in total, zero repeats:

RoundResponse sizeIDs in responseNew IDs
Page load (no POST)1,336,716 B3030
POST round 11,210,953 B3030
POST round 21,208,555 B3030
POST round 31,178,575 B3030

Read the two middle columns together: every response contains thirty IDs and every one of them is new, so the endpoint is handing out clean pages rather than re-sending what you already have. Read the left column against it and the trade is visible — 1.18 to 1.21 MB of JSON to learn thirty short strings, roughly 40 KB per ID, before a single image is fetched. For a channel with a thousand uploads that is thirty-four rounds and about 40 MB of JSON. It works; it is simply the expensive half of the job. If you only need the newest videos, the page load and the RSS feed cover 30 and 15 of them for free.

The images never refused: 180 requests, zero 403

Thirty videos from one page, six file names each, requested one after another with no delay:

File nameAnsweredTotal bytesPixels
maxresdefault.jpg30 / 309,551,3921280 × 720 (30/30)
hq720.jpg30 / 309,551,3921280 × 720 (30/30)
sddefault.jpg30 / 302,924,858640 × 480 (30/30)
hqdefault.jpg30 / 30740,188480 × 360 (30/30)
mqdefault.jpg30 / 30477,412320 × 180 (30/30)
default.jpg30 / 30105,169120 × 90 (30/30)

The whole run took 4.4 seconds for 180 requests — 40.6 per second — and returned nothing but 200s. We then pushed harder on purpose: 40 requests across 8 parallel connections finished in 0.9 seconds, and 24 requests fired simultaneously finished in 0.4 seconds, every one of them 200. No 403, no 429, no Retry-After. Whatever rate limiting exists on this host, it sits far above the pace a bulk job needs.

hq720 and maxresdefault were the same file 30 times out of 30

Both names answered on all thirty videos, both at 1280 × 720, and the byte totals are identical to the digit — which is not a coincidence but the same file served twice. We hashed every pair: 30 of 30 had matching SHA-1. On an earlier sample from 5 October 2026 the two names disagreed on one video out of ten, so "they are the same file" is the usual case and not a law; on this channel's newest page it held without exception.

Practical consequence: a script that asks for all six names downloads the 1280 × 720 image twice, which at 30 videos is payload worth 9.55 MB that buys nothing. Ask for one of them, or compare the two byte counts before keeping both — the two-names-one-file page walks through why they diverge at all.

What one page of thirty actually costs

Where a bulk run really breaks: the old uploads

The thirty newest uploads on a busy channel are the easy case — every size exists. Sample five long-standing public videos instead and the ladder falls apart at the top:

Uploadedmaxresdefaulthq720sddefaulthqdefaultmqdefaultdefault
23 Apr 2005404 · 1,097 B404404200 · 15,921 B200 · 8,710 B200 · 4,523 B
1 Aug 2008404 · 1,097 B404404200 · 23,549 B200 · 9,518 B200 · 2,948 B
24 Oct 2009200 · 65,324 B200 · 65,324 B200 · 31,029 B200 · 21,011 B200 · 10,303 B200 · 2,888 B
15 Jul 2012200 · 184,051 B200 · 184,051 B200 · 84,176 B200 · 53,433 B200 · 26,633 B200 · 5,096 B
12 Jan 2017200 · 82,327 B200 · 82,327 B200 · 40,116 B200 · 25,889 B200 · 14,551 B200 · 3,963 B

Two of five have no 1280 × 720 file, and sddefault went with it — the ceiling is 480 × 360, exactly the size a 2005 upload can offer. The failure is not an error your script will notice: it is a 404 carrying a 1,097-byte grey JPEG at 120 × 90, served with Content-Type: image/jpeg and Cache-Control: public, max-age=30, and all six misses in that table share the same SHA-1. Download a whole channel without checking bytes and you will quietly save that grey square a thousand times under six different names. The check is in the verification page; the size rules are in the missing-file page.

The other break: 180 files, six names

Bulk pulling does not produce 180 filenames. It produces six, repeated thirty times each, because the name comes from the URL and the URL only ever says maxresdefault.jpg. Save a page of thirty into one folder and you get thirty collisions; the browser will rename them for you, and the numbers it appends are in an order that has nothing to do with the videos. If you need the files identifiable, name them from the video ID before anything else touches them — the saver page covers what the server does and does not tell you at save time. This page of thirty offered 6 distinct names across 180 available files, and nothing in a response says which video a saved file belonged to — Content-Disposition was missing from every one of the 32 responses checked on 11 October 2026. There is also a name the menus never show — oar2.jpg — which on older uploads is where the 1920 × 1080 frame lives, measured separately on 2 October 2026.

An order of operations that does not waste the big half

Questions people actually ask

"Can I just grab every thumbnail from a channel in one go?"
Not in one request. One page load gives you thirty video IDs, the RSS feed gives fifteen, and the rest arrive thirty at a time through a POST — each of those rounds carries about 1.2 MB of JSON. After you have the IDs, the images are the easy part: 180 of them, six sizes each, all answered in 4.4 seconds.

"Why do I only get 30 videos when the channel has hundreds?"
Because that is what the HTML contains. We checked the obvious workaround — ?sort=popular, ?sort=oldest — and got the identical thirty in the identical order. The page holds six continuation tokens; those are the real page-2 door, and each one returns thirty new IDs.

"Do I need an API key to pull the images?"
No. Every one of the 180 image requests in this test was anonymous: no key, no cookie, no referer. The key that appears in the HTML belongs to the browse endpoint, which is only needed for video IDs beyond the first thirty.

"Will a few hundred downloads get me blocked?"
On the evidence we have, no. 180 sequential requests finished in 4.4 seconds, 40 across 8 parallel connections in 0.9 seconds, and 24 fired at once in 0.4 seconds — all 200. That is a burst test, not a published quota; we did not run a sustained job of thousands, so we cannot put a ceiling on it and do not claim one.

"Half the channel came back as a grey square. What happened?"
Those videos have no 1280 × 720 file, and often no 640 × 480 one either — the placeholder is 1,097 bytes at 120 × 90 and arrives with a successful-looking image/jpeg type. Check the byte count; the grey square has a single SHA-1 across every video that returns it.

Everything above was measured by us on 22 October 2026: three channel pages and three RSS feeds, 60 extracted IDs validated against the image host, three sort orders compared element by element, four browse rounds followed from one page's tokens, 180 ladder requests over one page of thirty videos, two concurrency bursts, five long-standing uploads with dates read from their own pages, and the placeholder re-fetched to confirm its size and hash. No channel is named, no video title is quoted and no watching link is reproduced here; the three channels are referred to only by what they returned. Three things we did not measure and therefore do not claim: whether the thirty-per-page number holds for channels with fewer than thirty uploads, whether private or members-only videos behave the same way, and how long a continuation token stays valid. Nothing here is an estimate.