Youtube publisher skill
Upload videos to YouTube (single video or a numbered series), set titles + descriptions + custom thumbnails, build a playlist, and order it.
by timscheuerai·MIT license·★ 37 Stars on the repo·GitHub ↗
npx degit timscheuerai/content-vault/skills/youtube-publisher#main ~/.claude/skills/youtube-publisherChecked ·commit main
Inside this skill
SKILL.md · the author's own words# youtube-publisher The YouTube publishing pipeline. Takes raw video files on disk and ends with a fully-published series: titles in the right scheme, descriptions with the right CTA, custom on-brand thumbnails on every video, and a properly-ordered playlist. Idempotent state files make every step re-runnable so future series ship in minutes, not hours. --- ## SETUP (run this once) YouTube publishing needs your own Google Cloud project + OAuth. There is no shared key — this talks to *your* channel. 1. **Google Cloud project** → enable **YouTube Data API v3**.
Word-for-word excerpt, not a result. Read the full SKILL.md ↓
Files of Youtube publisher
Show the full text310 lines
youtube-publisher
The YouTube publishing pipeline. Takes raw video files on disk and ends with a fully-published series: titles in the right scheme, descriptions with the right CTA, custom on-brand thumbnails on every video, and a properly-ordered playlist. Idempotent state files make every step re-runnable so future series ship in minutes, not hours.
SETUP (run this once)
YouTube publishing needs your own Google Cloud project + OAuth. There is no shared key — this talks to your channel.
- Google Cloud project → enable YouTube Data API v3.
- OAuth Desktop client → download
client_secrets.json(keep it out of git — see.gitignore). - Verify the channel at https://www.youtube.com/verify (phone + SMS). Required before custom thumbnails work, and before description links become clickable.
- Fill the placeholders used in the schemes below:
<YOUR_BRAND>,<YOUR_DOMAIN>. - The first real API call pops a browser for OAuth and caches the token
locally (
token.json, auto-refreshed, gitignored).
This skill documents the pipeline (upload → render thumbnails → finalize
→ playlist) using the YouTube Data API v3. The two scripts referenced
below (upload.py, finalize.py) are small, resumable, idempotent
wrappers — set them up once in <YOUR_YT_SCRIPTS>/ (or have the agent
build them from this spec) and reuse them per series.
When to use
Trigger on:
- "Upload these videos to YouTube"
- "Publish the [series name] videos"
- "Make a YT playlist for X"
- "Set custom thumbnails on these videos"
- "Update YT titles / descriptions"
- "Redo the thumbnails"
- User types
/youtube-publisher
Skip for:
- Editing raw footage →
/video-use - Building motion-graphics launch videos →
/launch-video - Writing the YouTube script itself →
/youtube-script - Designing the thumbnail art →
/youtube-thumbnail(interview) or/graphics-designer(tutorial / solo). This skill sets the thumbnail; those make it.
Pipeline shape
<YOUR_YT_SCRIPTS>/
├── upload.py # resumable uploads, OAuth flow, idempotent (uploaded.json)
├── finalize.py # thumbnails + titles + descriptions + playlist (finalize-state.json)
├── metadata.json # per-video title/description/tags for the UPLOAD step
├── client_secrets.json # OAuth Desktop client (gitignored, one-time setup)
├── token.json # cached OAuth token (gitignored, auto-refreshed)
├── uploaded.json # idempotency: file → {video_id, video_url, ...}
└── finalize-state.json # idempotency: {thumbnails, titles, descriptions, playlist_id, playlist_items}
Keep the upload-time metadata (per-video tags/description footers)
and the finalize-time meta (thumbnail design fields) separate. They
reference the same video_id after upload.
Source videos large enough to need Git LFS (*.mp4, *.mov) should be
tracked before committing — GitHub rejects files >100 MB without it.
Definition of done · HARD GATE
A series is NOT done until all six are true:
- Source video files in place (Git LFS if >100 MB)
- Each video uploaded to YouTube,
video_idcaptured inuploaded.json - Each video's title set per the scheme below
- Each video's description set per the scheme below (CTA + playlist link for series videos)
- Each video has a custom thumbnail pushed
- The series playlist exists and contains all videos in the intended order
Do not declare "done" until you can paste the playlist URL and every video's URL with the new title + thumbnail visible in Studio.
Title scheme
Topic first, series second. Topic wins for click-through; series tag is identification. Use the middot separator (no em dash, no hyphen, no colon — brand rule).
Series video:
CLI Install · <Series Name>
MCP Server · <Series Name>
Connections · <Series Name>
...
Standalone / non-series:
<YOUR_BRAND> Demo · <one-line tagline>
For a standalone flagship video, the tagline can live in the title instead of a series tag.
Description scheme
Terse. CTA first. YouTube collapses descriptions after the first
~150 chars, so the link to <YOUR_DOMAIN> goes in the very first line.
Series videos:
Try <YOUR_BRAND> free → <YOUR_DOMAIN>
Full <Series> playlist → https://www.youtube.com/playlist?list=<playlist_id>
Standalone:
Try <YOUR_BRAND> free → <YOUR_DOMAIN>
That's it. Don't pad with chapter timestamps, social links, hashtag
walls. If the user wants more (timestamps from /youtube-description,
related links), add them explicitly — never default to bloat.
Gotcha: Links in descriptions render as plain text until Google finishes a one-time identity verification on the channel (~24h). After that they're clickable automatically. Nothing to script — just warn the user once.
Thumbnail
Set the thumbnail produced by /youtube-thumbnail (interview/podcast) or
/graphics-designer (tutorial/solo). One unified design per series —
geometry stays identical, only the per-video kicker + title swap. Render
at 1280×720, push via youtube.thumbnails().set() in the finalize step.
Workflow
1. Stage the videos
Drop the cut files into your series folder, numbered for order
(NN-topic.mov). Track large files with Git LFS first if you version
them:
git lfs track "media/<series-slug>/*.mp4" "media/<series-slug>/*.mov"
git add .gitattributes
Files >100 MB will be rejected by GitHub without LFS, so set this up first.
2. Configure upload metadata
Edit metadata.json. A _defaults block (category, privacy, tags,
descriptionFooter) applies to every video; the videos[] array carries
per-video title/description/tags + the file path.
Default privacy: unlisted. Always. Public-flip happens after the
user reviews the live videos in Studio.
3. Upload
python3 <YOUR_YT_SCRIPTS>/upload.py --dry-run --all # preview
python3 <YOUR_YT_SCRIPTS>/upload.py --only 0 # one as a smoke test
python3 <YOUR_YT_SCRIPTS>/upload.py --all # the rest
First real call pops a browser for OAuth on the channel-owner Google
account. Token caches in token.json. Resumable uploads, 8 MB chunks,
500/502/503/504 auto-retry with a short sleep.
Quota: 1,600 units/upload × default 10k/day = 6 uploads/day. For 7+ videos either spread across a day boundary or request a quota increase via the YouTube API quota form (~1-3 weeks).
4. Render thumbnails
Produce them with /youtube-thumbnail or /graphics-designer. Required
per video at finalize time: slug, video_id (from uploaded.json),
kicker, title, yt_title. Sanity-check every PNG before pushing —
the visual review here is the only thing between you and a bad thumbnail
going live. Specifically check:
- Long single-word titles fit the column
- Kicker reads cleanly
- Logo + wordmark visible, NOT cropped
- The graphic is centered, not bleeding off
5. Finalize
python3 <YOUR_YT_SCRIPTS>/finalize.py --dry-run # preview
python3 <YOUR_YT_SCRIPTS>/finalize.py # ship
Does, in order:
- Thumbnails —
youtube.thumbnails().set()per video - Titles —
youtube.videos().update()snippet - Descriptions —
youtube.videos().update()snippet - Playlist — creates if missing, adds each video at
position: idx
Flags:
--only-thumbnails/--only-titles/--only-descriptions/--only-playlist--force— re-run actions already recorded infinalize-state.json--delay-between N— seconds between thumbnail uploads (avoids rate limit)--dry-run
6. Public flip
Last step the user controls. Either:
- Studio → each video → Visibility → Public (manual review)
- Or extend
finalize.pywith a--make-publicflag that callsvideos.updatewithstatus.privacyStatus: "public"per video
Default to the manual review — the user wants to confirm the actual live thumbnail and metadata before publishing.
Known gotchas (each one bites at least once)
Custom thumbnails require channel verification. First attempt returns
403 The authenticated user doesn't have permissions to upload and set custom video thumbnails. Fix: user goes to
https://www.youtube.com/verify, enters phone + SMS code. ~2 min. After
that, thumbnails work.
Thumbnail upload rate limit. YouTube caps thumbnails.set per
channel in a rolling window. A burst of 4-5 back-to-back returns 429 The user has uploaded too many thumbnails recently. The state file preserves
what succeeded; pass --delay-between 120 (2 min between uploads) when
re-running. Window is opaque but often clears in ~1-2h, sometimes resets
at midnight PT. Fallback when blocked: open Finder + the Studio edit
tabs and drag-drop manually — Studio's upload path isn't subject to the
same limit.
open -R output/<series>-00-<slug>.png
for vid in <id1> <id2> ...; do open "https://studio.youtube.com/video/$vid/edit"; done
Identity verification for clickable links. First time you put URLs in any description, Google triggers a one-time channel identity verification (~24h). Until done, links render as plain text. Nothing to script.
Daily upload quota. 10k units/day default → 6 uploads/day. 7+ videos crosses a day boundary. Have the script catch quota errors and break the loop with a clear message — resume tomorrow.
Position-0 truthy check. if state["playlist_items"].get(vid):
treats a position-0 entry as not-in-playlist because 0 is falsy. Use
if vid in state["playlist_items"]: instead.
Don'ts
- Don't restart from scratch when state exists.
uploaded.jsonandfinalize-state.jsonmake the whole pipeline idempotent. Re-running any step is a no-op for what's already done. Use--forceonly when re-pushing because the artifact actually changed. - Don't push thumbnails in a tight loop. The rate limit will bite.
Use
--delay-between 120for batches; fall back to Studio drag-drop if the API is already cooling off. - Don't write rich descriptions. Terse CTA + playlist link is the whole brief. Timestamps, social links, hashtag walls without the user asking is the kind of bloat that reads as AI slop.
- Don't auto-flip to public. Default is
unlisteduntil the user manually reviews each video in Studio. Public flip is an explicit ask. - Don't change the thumbnail design without showing renders first. Iterate visually, get a thumbs-up, then ship.
- Don't add an em dash anywhere. Brand rule. Title separator is the
middot (
·). - Don't bypass the channel verification step. Tell the user to verify at youtube.com/verify before the first thumbnail upload, not after the first 403.
- Don't commit
client_secrets.jsonortoken.json. They're in.gitignorealready; double-check before anygit add -A.
See also
- Thumbnail art:
/youtube-thumbnail(interview) ·/graphics-designer(tutorial/solo) - Description + chapters:
/youtube-description - Script before filming:
/youtube-script - Raw footage edit:
/video-use
| 1 | |
| 2 | name youtube-publisher |
| 3 | description Upload videos to YouTube (single video or a numbered series), set titles + descriptions + custom thumbnails, build a playlist, and order it. End-to-end pipeline using the YouTube Data API v3 with resumable uploads, OAuth token caching, and idempotent state. Use when asked to "upload to YouTube", "publish these videos", "make a YT series", "set up a YT playlist", "redo thumbnails", or "/youtube-publisher". |
| 4 | |
| 5 | |
| 6 | # youtube-publisher |
| 7 | |
| 8 | The YouTube publishing pipeline. Takes raw video files on disk and ends |
| 9 | with a fully-published series: titles in the right scheme, descriptions |
| 10 | with the right CTA, custom on-brand thumbnails on every video, and a |
| 11 | properly-ordered playlist. Idempotent state files make every step |
| 12 | re-runnable so future series ship in minutes, not hours. |
| 13 | |
| 14 | |
| 15 | |
| 16 | ## SETUP (run this once) |
| 17 | |
| 18 | YouTube publishing needs your own Google Cloud project + OAuth. There is |
| 19 | no shared key — this talks to *your* channel. |
| 20 | |
| 21 | **Google Cloud project** → enable **YouTube Data API v3**. |
| 22 | **OAuth Desktop client** → download `client_secrets.json` (keep it |
| 23 | out of git — see `.gitignore`). |
| 24 | **Verify the channel** at https://www.youtube.com/verify (phone + |
| 25 | SMS). Required before custom thumbnails work, and before description |
| 26 | links become clickable. |
| 27 | Fill the placeholders used in the schemes below: `<YOUR_BRAND>`, |
| 28 | `<YOUR_DOMAIN>`. |
| 29 | The first real API call pops a browser for OAuth and caches the token |
| 30 | locally (`token.json`, auto-refreshed, gitignored). |
| 31 | |
| 32 | This skill documents the pipeline (upload → render thumbnails → finalize |
| 33 | → playlist) using the YouTube Data API v3. The two scripts referenced |
| 34 | below (`upload.py`, `finalize.py`) are small, resumable, idempotent |
| 35 | wrappers — set them up once in `<YOUR_YT_SCRIPTS>/` (or have the agent |
| 36 | build them from this spec) and reuse them per series. |
| 37 | |
| 38 | |
| 39 | |
| 40 | ## When to use |
| 41 | |
| 42 | Trigger on: |
| 43 | "Upload these videos to YouTube" |
| 44 | "Publish the [series name] videos" |
| 45 | "Make a YT playlist for X" |
| 46 | "Set custom thumbnails on these videos" |
| 47 | "Update YT titles / descriptions" |
| 48 | "Redo the thumbnails" |
| 49 | User types `/youtube-publisher` |
| 50 | |
| 51 | Skip for: |
| 52 | Editing raw footage → `/video-use` |
| 53 | Building motion-graphics launch videos → `/launch-video` |
| 54 | Writing the YouTube script itself → `/youtube-script` |
| 55 | Designing the thumbnail art → `/youtube-thumbnail` (interview) or |
| 56 | `/graphics-designer` (tutorial / solo). This skill *sets* the |
| 57 | thumbnail; those *make* it. |
| 58 | |
| 59 | |
| 60 | |
| 61 | ## Pipeline shape |
| 62 | |
| 63 | |
| 64 | <YOUR_YT_SCRIPTS>/ |
| 65 | ├── upload.py # resumable uploads, OAuth flow, idempotent (uploaded.json) |
| 66 | ├── finalize.py # thumbnails + titles + descriptions + playlist (finalize-state.json) |
| 67 | ├── metadata.json # per-video title/description/tags for the UPLOAD step |
| 68 | ├── client_secrets.json # OAuth Desktop client (gitignored, one-time setup) |
| 69 | ├── token.json # cached OAuth token (gitignored, auto-refreshed) |
| 70 | ├── uploaded.json # idempotency: file → {video_id, video_url, ...} |
| 71 | └── finalize-state.json # idempotency: {thumbnails, titles, descriptions, playlist_id, playlist_items} |
| 72 | |
| 73 | |
| 74 | Keep the **upload-time** metadata (per-video tags/description footers) |
| 75 | and the **finalize-time** meta (thumbnail design fields) separate. They |
| 76 | reference the same `video_id` after upload. |
| 77 | |
| 78 | Source videos large enough to need Git LFS (`*.mp4`, `*.mov`) should be |
| 79 | tracked before committing — GitHub rejects files >100 MB without it. |
| 80 | |
| 81 | |
| 82 | |
| 83 | ## Definition of done · HARD GATE |
| 84 | |
| 85 | A series is NOT done until **all six** are true: |
| 86 | |
| 87 | Source video files in place (Git LFS if >100 MB) |
| 88 | Each video uploaded to YouTube, `video_id` captured in `uploaded.json` |
| 89 | Each video's **title** set per the scheme below |
| 90 | Each video's **description** set per the scheme below (CTA + playlist link for series videos) |
| 91 | Each video has a custom **thumbnail** pushed |
| 92 | The series **playlist** exists and contains all videos in the intended order |
| 93 | |
| 94 | Do not declare "done" until you can paste the playlist URL and every |
| 95 | video's URL with the new title + thumbnail visible in Studio. |
| 96 | |
| 97 | |
| 98 | |
| 99 | ## Title scheme |
| 100 | |
| 101 | **Topic first, series second.** Topic wins for click-through; series tag |
| 102 | is identification. Use the middot separator (no em dash, no hyphen, no |
| 103 | colon — brand rule). |
| 104 | |
| 105 | |
| 106 | Series video: |
| 107 | CLI Install · <Series Name> |
| 108 | MCP Server · <Series Name> |
| 109 | Connections · <Series Name> |
| 110 | ... |
| 111 | |
| 112 | Standalone / non-series: |
| 113 | <YOUR_BRAND> Demo · <one-line tagline> |
| 114 | |
| 115 | |
| 116 | For a standalone flagship video, the tagline can live in the title |
| 117 | instead of a series tag. |
| 118 | |
| 119 | |
| 120 | |
| 121 | ## Description scheme |
| 122 | |
| 123 | **Terse. CTA first.** YouTube collapses descriptions after the first |
| 124 | ~150 chars, so the link to `<YOUR_DOMAIN>` goes in the very first line. |
| 125 | |
| 126 | Series videos: |
| 127 | |
| 128 | Try <YOUR_BRAND> free → <YOUR_DOMAIN> |
| 129 | |
| 130 | Full <Series> playlist → https://www.youtube.com/playlist?list=<playlist_id> |
| 131 | |
| 132 | |
| 133 | Standalone: |
| 134 | |
| 135 | Try <YOUR_BRAND> free → <YOUR_DOMAIN> |
| 136 | |
| 137 | |
| 138 | That's it. Don't pad with chapter timestamps, social links, hashtag |
| 139 | walls. If the user wants more (timestamps from `/youtube-description`, |
| 140 | related links), add them explicitly — never default to bloat. |
| 141 | |
| 142 | **Gotcha:** Links in descriptions render as plain text until Google |
| 143 | finishes a one-time **identity verification** on the channel (~24h). |
| 144 | After that they're clickable automatically. Nothing to script — just |
| 145 | warn the user once. |
| 146 | |
| 147 | |
| 148 | |
| 149 | ## Thumbnail |
| 150 | |
| 151 | Set the thumbnail produced by `/youtube-thumbnail` (interview/podcast) or |
| 152 | `/graphics-designer` (tutorial/solo). One unified design per series — |
| 153 | geometry stays identical, only the per-video kicker + title swap. Render |
| 154 | at 1280×720, push via `youtube.thumbnails().set()` in the finalize step. |
| 155 | |
| 156 | |
| 157 | |
| 158 | ## Workflow |
| 159 | |
| 160 | ### 1. Stage the videos |
| 161 | |
| 162 | Drop the cut files into your series folder, numbered for order |
| 163 | (`NN-topic.mov`). Track large files with Git LFS first if you version |
| 164 | them: |
| 165 | |
| 166 | |
| 167 | git lfs track "media/<series-slug>/*.mp4" "media/<series-slug>/*.mov" |
| 168 | git add .gitattributes |
| 169 | |
| 170 | |
| 171 | Files >100 MB **will** be rejected by GitHub without LFS, so set this up |
| 172 | first. |
| 173 | |
| 174 | ### 2. Configure upload metadata |
| 175 | |
| 176 | Edit `metadata.json`. A `_defaults` block (category, privacy, tags, |
| 177 | descriptionFooter) applies to every video; the `videos[]` array carries |
| 178 | per-video title/description/tags + the `file` path. |
| 179 | |
| 180 | **Default privacy: `unlisted`.** Always. Public-flip happens after the |
| 181 | user reviews the live videos in Studio. |
| 182 | |
| 183 | ### 3. Upload |
| 184 | |
| 185 | |
| 186 | python3 <YOUR_YT_SCRIPTS>/upload.py --dry-run --all # preview |
| 187 | python3 <YOUR_YT_SCRIPTS>/upload.py --only 0 # one as a smoke test |
| 188 | python3 <YOUR_YT_SCRIPTS>/upload.py --all # the rest |
| 189 | |
| 190 | |
| 191 | First real call pops a browser for OAuth on the channel-owner Google |
| 192 | account. Token caches in `token.json`. Resumable uploads, 8 MB chunks, |
| 193 | 500/502/503/504 auto-retry with a short sleep. |
| 194 | |
| 195 | **Quota:** 1,600 units/upload × default 10k/day = 6 uploads/day. For 7+ |
| 196 | videos either spread across a day boundary or request a quota increase |
| 197 | via the YouTube API quota form (~1-3 weeks). |
| 198 | |
| 199 | ### 4. Render thumbnails |
| 200 | |
| 201 | Produce them with `/youtube-thumbnail` or `/graphics-designer`. Required |
| 202 | per video at finalize time: `slug`, `video_id` (from `uploaded.json`), |
| 203 | `kicker`, `title`, `yt_title`. Sanity-check every PNG before pushing — |
| 204 | the visual review here is the only thing between you and a bad thumbnail |
| 205 | going live. Specifically check: |
| 206 | Long single-word titles fit the column |
| 207 | Kicker reads cleanly |
| 208 | Logo + wordmark visible, NOT cropped |
| 209 | The graphic is centered, not bleeding off |
| 210 | |
| 211 | ### 5. Finalize |
| 212 | |
| 213 | |
| 214 | python3 <YOUR_YT_SCRIPTS>/finalize.py --dry-run # preview |
| 215 | python3 <YOUR_YT_SCRIPTS>/finalize.py # ship |
| 216 | |
| 217 | |
| 218 | Does, in order: |
| 219 | **Thumbnails** — `youtube.thumbnails().set()` per video |
| 220 | **Titles** — `youtube.videos().update()` snippet |
| 221 | **Descriptions** — `youtube.videos().update()` snippet |
| 222 | **Playlist** — creates if missing, adds each video at `position: idx` |
| 223 | |
| 224 | Flags: |
| 225 | `--only-thumbnails` / `--only-titles` / `--only-descriptions` / `--only-playlist` |
| 226 | `--force` — re-run actions already recorded in `finalize-state.json` |
| 227 | `--delay-between N` — seconds between thumbnail uploads (avoids rate limit) |
| 228 | `--dry-run` |
| 229 | |
| 230 | ### 6. Public flip |
| 231 | |
| 232 | Last step the user controls. Either: |
| 233 | Studio → each video → Visibility → Public (manual review) |
| 234 | Or extend `finalize.py` with a `--make-public` flag that calls |
| 235 | `videos.update` with `status.privacyStatus: "public"` per video |
| 236 | |
| 237 | Default to the manual review — the user wants to confirm the actual live |
| 238 | thumbnail and metadata before publishing. |
| 239 | |
| 240 | |
| 241 | |
| 242 | ## Known gotchas (each one bites at least once) |
| 243 | |
| 244 | **Custom thumbnails require channel verification.** First attempt returns |
| 245 | `403 The authenticated user doesn't have permissions to upload and set |
| 246 | custom video thumbnails.` Fix: user goes to |
| 247 | https://www.youtube.com/verify, enters phone + SMS code. ~2 min. After |
| 248 | that, thumbnails work. |
| 249 | |
| 250 | **Thumbnail upload rate limit.** YouTube caps `thumbnails.set` per |
| 251 | channel in a rolling window. A burst of 4-5 back-to-back returns `429 The |
| 252 | user has uploaded too many thumbnails recently`. The state file preserves |
| 253 | what succeeded; pass `--delay-between 120` (2 min between uploads) when |
| 254 | re-running. Window is opaque but often clears in ~1-2h, sometimes resets |
| 255 | at midnight PT. **Fallback when blocked:** open Finder + the Studio edit |
| 256 | tabs and drag-drop manually — Studio's upload path isn't subject to the |
| 257 | same limit. |
| 258 | |
| 259 | |
| 260 | open -R output/<series>-00-<slug>.png |
| 261 | for vid in <id1> <id2> ...; do open "https://studio.youtube.com/video/$vid/edit"; done |
| 262 | |
| 263 | |
| 264 | **Identity verification for clickable links.** First time you put URLs in |
| 265 | any description, Google triggers a one-time channel identity verification |
| 266 | (~24h). Until done, links render as plain text. Nothing to script. |
| 267 | |
| 268 | **Daily upload quota.** 10k units/day default → 6 uploads/day. 7+ videos |
| 269 | crosses a day boundary. Have the script catch quota errors and break the |
| 270 | loop with a clear message — resume tomorrow. |
| 271 | |
| 272 | **Position-0 truthy check.** `if state["playlist_items"].get(vid):` |
| 273 | treats a position-0 entry as not-in-playlist because `0` is falsy. Use |
| 274 | `if vid in state["playlist_items"]:` instead. |
| 275 | |
| 276 | |
| 277 | |
| 278 | ## Don'ts |
| 279 | |
| 280 | **Don't restart from scratch when state exists.** `uploaded.json` and |
| 281 | `finalize-state.json` make the whole pipeline idempotent. Re-running |
| 282 | any step is a no-op for what's already done. Use `--force` only when |
| 283 | re-pushing because the artifact actually changed. |
| 284 | **Don't push thumbnails in a tight loop.** The rate limit will bite. |
| 285 | Use `--delay-between 120` for batches; fall back to Studio drag-drop if |
| 286 | the API is already cooling off. |
| 287 | **Don't write rich descriptions.** Terse CTA + playlist link is the |
| 288 | whole brief. Timestamps, social links, hashtag walls without the user |
| 289 | asking is the kind of bloat that reads as AI slop. |
| 290 | **Don't auto-flip to public.** Default is `unlisted` until the user |
| 291 | manually reviews each video in Studio. Public flip is an explicit ask. |
| 292 | **Don't change the thumbnail design without showing renders first.** |
| 293 | Iterate visually, get a thumbs-up, then ship. |
| 294 | **Don't add an em dash anywhere.** Brand rule. Title separator is the |
| 295 | middot (`·`). |
| 296 | **Don't bypass the channel verification step.** Tell the user to verify |
| 297 | at youtube.com/verify *before* the first thumbnail upload, not after |
| 298 | the first 403. |
| 299 | **Don't commit `client_secrets.json` or `token.json`.** They're in |
| 300 | `.gitignore` already; double-check before any `git add -A`. |
| 301 | |
| 302 | |
| 303 | |
| 304 | ## See also |
| 305 | |
| 306 | Thumbnail art: `/youtube-thumbnail` (interview) · `/graphics-designer` (tutorial/solo) |
| 307 | Description + chapters: `/youtube-description` |
| 308 | Script before filming: `/youtube-script` |
| 309 | Raw footage edit: `/video-use` |
| 310 |
Discussion
Alternatives
Browse more free Claude skills or everything in Development.