---
name: youtube_connector_workflows
description: Run a YouTube channel's daily operations through the YouTube Data and Analytics APIs: scheduled/resumable video uploads, comment sweeps with moderation, and analytics pulls for week-over-week reporting. Use when someone asks to automate YouTube uploads, schedule videos for later release, reply to or moderate YouTube comments, pull channel or video analytics, or run YouTube channel ops on a schedule.
---

# YouTube Connector Workflows

## Purpose

A bare connector reads YouTube. This skill runs the channel: the full operations loop a channel manager repeats weekly. Three playbooks cover it: scheduled uploads with the resumable protocol, comment operations (sweep, reply, moderate), and analytics pulls that produce a reportable numbers block. Each playbook pairs curl-grade endpoint knowledge with `bin/yt-channel.py`, a stdlib-only CLI that builds and (outside dry-run) executes the calls.

## Tooling

`bin/yt-channel.py` — one file, no dependencies beyond the Python standard library. Subcommands mirror the three playbooks:

```
yt-channel.py auth        # refresh an OAuth access token, print it
yt-channel.py upload      # resumable upload, optional --publish-at scheduling
yt-channel.py comments    # list comment threads for a video or the channel
yt-channel.py reply       # post a reply to a comment thread
yt-channel.py moderate    # set moderation status (published | heldForReview | rejected, --ban-author)
yt-channel.py analytics   # reports.query pull with metrics/dimensions/filters
```

Every subcommand accepts `--dry-run`: it prints the exact HTTP method, URL, headers, and body it would send, then stops. Use dry-run before any live run against a real channel. Live runs need env vars `YT_CLIENT_ID`, `YT_CLIENT_SECRET`, `YT_REFRESH_TOKEN`.

## Auth

YouTube does not support service accounts for ordinary channel work. Authenticate once as the channel owner with OAuth 2.0 for installed apps, keep the refresh token, reuse it forever (or until revoked).

1. In Google Cloud Console: create a project, enable **YouTube Data API v3** and **YouTube Analytics API**, create an OAuth client of type Desktop app.
2. Open the consent URL once with the needed scopes (see scope ladder below), approve as the channel owner, exchange the code for tokens, and store the refresh token in `YT_REFRESH_TOKEN`.
3. `yt-channel.py auth` exchanges it for a fresh access token on every run. Access tokens last about an hour; the CLI handles refresh automatically.

Scope ladder, least privilege first:

| Task | Scope |
|---|---|
| Read public/private video and channel metadata | `https://www.googleapis.com/auth/youtube.readonly` |
| Analytics reads | `https://www.googleapis.com/auth/yt-analytics.readonly` (+ `-monetary.readonly` for revenue data) |
| Upload only | `https://www.googleapis.com/auth/youtube.upload` |
| Full channel management, comment writes and moderation | `https://www.googleapis.com/auth/youtube.force-ssl` or `https://www.googleapis.com/auth/youtube` |

Watch for: projects created after 2020-07-28 whose audit is not passed get all API uploads forced to private. This is Google's documented restriction, not a bug in your call. The audit lives at the quota and compliance page linked in `references/`.

## Operating Rules

1. **Quota is the budget, not units.** Since 2026-06-01 the quota is three buckets: `videos.insert` 100 calls/day, `search.list` 100 calls/day, everything else 10,000 units/day (reads cost 1, writes cost 50). Resets midnight Pacific. Full cost table in `references/`. Before any batch run, estimate the call count and compare; a batch that cannot finish in quota gets split across days, never retried in a tight loop.
2. **Resumable uploads, always.** Never use simple multipart upload for real files; one dropped connection loses the whole file. Initiate, PUT the session URI, and on `308 Resume Incomplete` continue from the Range header offset, in 256KB chunk multiples. Retry transient 5xx with exponential backoff and jitter.
3. **Scheduling is private + publishAt.** `status.publishAt` (RFC 3339 timestamp) requires `privacyStatus: private` on upload; YouTube flips it public at the scheduled time. To reschedule, `videos.update` the `status` part.
4. **Comment writes are owner-only and 50 units each.** `comments.setModerationStatus` needs the channel/video owner's OAuth. Up to 50 comment IDs per call. `rejected` also hides all replies to the comment; you cannot re-publish a rejected comment; `comments.delete` works only for the comment's own author. `banAuthor=true` only pairs with `rejected`.
5. **Analytics responses lag reality.** `reports.query` data is complete only through the last day where all requested metrics are available; recent days may be partial. Never mix partial trailing days into week-over-week math. `day` and `month` cannot both be dimensions.
6. **Dry-run first, then live, then verify.** Every live run ends with a verification read: uploaded videos checked with `videos.list`, comments with `commentThreads.list`, analytics eyeball-checked against the response row count. Report what the API returned, not what was requested.
