1# YouTube Thumbnail Downloader — Input/Output Schema
2
3This document describes the actor's input and output. Thumbnails are fetched
4directly from YouTube's public image CDN (`i.ytimg.com`) — no `yt-dlp`, no
5browser, no API key.
6
7---
8
9## Input
10
11The actor accepts a `startUrls`-style input: a list of YouTube video URLs.
12For each URL, it fetches the requested thumbnail resolution(s) and saves the
13image(s) to the default Key-Value Store. One dataset item is produced per
14(video × resolution) pair that was successfully downloaded.
15
16### Input fields
17
18
19|------------------|------------|----------|--------------------------|-------------|
20| `startUrls` | `array` | yes | — | List of YouTube URLs (watch / share / shorts / embed / legacy `/v/`). Each entry is a `{ "url": "..." }` object (the Apify `requestListSources` editor format); plain strings are also accepted. Invalid entries are skipped and logged. |
21| `resolutions` | `string[]` | no | `["maxresdefault"]` | Which thumbnail qualities to download. Allowed: `maxresdefault` (1280×720), `sddefault` (640×480), `hqdefault` (480×360), `mqdefault` (320×180), `default` (120×90). Unknown values are ignored. |
22| `filenamePrefix` | `string` | no | `"youtube-thumbnail-"` | Prefix for filenames saved in the KV-store. Final filename: `<prefix><videoId>_<resolution>.jpg` |
23
24### Example input
25
26```json
27{
28 "startUrls": [
29 { "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" },
30 { "url": "https://youtu.be/9bZkp7q19f0" },
31 { "url": "https://www.youtube.com/shorts/jNQXAC9IVRw" }
32 ],
33 "resolutions": ["maxresdefault", "sddefault"],
34 "filenamePrefix": "yt-thumb-"
35}
36```
37
38### URL parsing rules
39
40The actor extracts the 11-character YouTube video ID with a single regex that
41matches the ID in any common URL form:
42
43- `https://www.youtube.com/watch?v=<ID>`
44- `https://m.youtube.com/watch?v=<ID>`
45- `https://youtu.be/<ID>`
46- `https://www.youtube.com/shorts/<ID>`
47- `https://www.youtube.com/embed/<ID>`
48- `https://www.youtube.com/v/<ID>` (legacy)
49
50Anything that does not match is logged at `warning` level and skipped — it does
51not fail the run.
52
53### Resolution notes
54
55- `maxresdefault` may be missing for older videos. There is **no automatic
56 fallback**: a missing resolution is simply skipped for that video. Request
57 multiple resolutions to get whichever are available.
58- All thumbnail URLs are public on `i.ytimg.com`, so no watch-page scraping is
59 needed — the CDN URL is constructed directly from the video ID.
60
61---
62
63## Output
64
65The actor writes to two places:
66
671. **Default Key-Value Store** — the actual image files, one per successfully
68 downloaded (video × resolution) pair. Filename:
69 `<filenamePrefix><videoId>_<resolution>.jpg`.
702. **Default Dataset** — one JSON item per saved image, described below.
71
72### Dataset item shape
73
74
75|--------------------|----------|-------------|
76| `videoId` | `string` | The 11-character YouTube video ID extracted from the input URL. |
77| `sourceUrl` | `string` | The original input URL the user provided (verbatim, for traceability). |
78| `resolution` | `string` | The thumbnail resolution key fetched (`maxresdefault`, `sddefault`, `hqdefault`, `mqdefault`, `default`). |
79| `thumbnailUrl` | `string` | The `i.ytimg.com` CDN URL the image was downloaded from. |
80| `width` | `number` | Pixel width of the image (fixed per resolution key). |
81| `height` | `number` | Pixel height of the image (fixed per resolution key). |
82| `bytes` | `number` | File size of the saved image in bytes. |
83| `contentType` | `string` | MIME type of the saved image — always `image/jpeg`. |
84| `keyValueStoreKey` | `string` | The filename in the default KV-store (e.g. `yt-thumb-dQw4w9WgXcQ_maxresdefault.jpg`). |
85| `publicDownloadUrl`| `string` | A URL the user can click to download/view the image. |
86| `fetchedAt` | `string` | ISO-8601 timestamp of when the image was downloaded. |
87
88### Example output (one row per saved image)
89
90```json
91[
92 {
93 "videoId": "dQw4w9WgXcQ",
94 "sourceUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
95 "resolution": "maxresdefault",
96 "thumbnailUrl": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
97 "width": 1280,
98 "height": 720,
99 "bytes": 65324,
100 "contentType": "image/jpeg",
101 "keyValueStoreKey": "yt-thumb-dQw4w9WgXcQ_maxresdefault.jpg",
102 "publicDownloadUrl": "https://api.apify.com/v2/key-value-stores/<storeId>/records/yt-thumb-dQw4w9WgXcQ_maxresdefault.jpg",
103 "fetchedAt": "2026-06-22T08:26:59.755Z"
104 },
105 {
106 "videoId": "dQw4w9WgXcQ",
107 "sourceUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
108 "resolution": "sddefault",
109 "thumbnailUrl": "https://i.ytimg.com/vi/dQw4w9WgXcQ/sddefault.jpg",
110 "width": 640,
111 "height": 480,
112 "bytes": 31029,
113 "contentType": "image/jpeg",
114 "keyValueStoreKey": "yt-thumb-dQw4w9WgXcQ_sddefault.jpg",
115 "publicDownloadUrl": "https://api.apify.com/v2/key-value-stores/<storeId>/records/yt-thumb-dQw4w9WgXcQ_sddefault.jpg",
116 "fetchedAt": "2026-06-22T08:26:59.760Z"
117 }
118]
119```
120
121---
122
123## Edge cases / behavior
124
125- **Duplicate resolutions in input** — de-duplicated before fetching.
126- **Invalid URL** — logged at `warning` level and skipped; the run continues.
127- **Requested resolution unavailable** — YouTube's CDN returns a ~1 KB grey
128 placeholder JPEG (HTTP 404). The actor detects this via response size and
129 skips that (video × resolution) pair. No dataset item is pushed for it; the
130 run continues. There is no automatic fallback — request multiple
131 resolutions if you want a guaranteed result.
132- **Empty `startUrls`** — the actor exits cleanly with a warning, no items are
133 pushed and no files are written.
134- **Network error mid-download** — logged at `warning` level; that
135 (video × resolution) pair is skipped and the run continues.
136- **`publicDownloadUrl` locally** — when running locally with `apify run`, the
137 key-value store has no remote ID, so `publicDownloadUrl` contains `undefined`
138 in place of the store ID. On the Apify platform the real store ID is used.