co youtube
September 12, 2026 · View on GitHub
Use the official YouTube Data API with your saved Google login, like co gmail.
List recent uploads, read channels/videos, and preview uploads or metadata edits.
Writes require the exact digest of the current preview.
co auth google
co youtube
co youtube --help
co youtube channel @YouTube --json
co youtube list @YouTube -n 20
co youtube video 1 --json
co youtube put clip.mp4 --title "Demo #Shorts" --channel UCxxxxxxxxxxxxxxxxxxxxxx --dry-run --json
co youtube update 1 --title "A clearer title" --dry-run --json
Connect once using co auth google. This extends the normal Google
OAuth flow with YouTube permission; subsequent commands use the saved login
and refresh tokens through the same backend as Gmail. Tokens are never supplied
as command arguments or pasted for each command. YouTube operations call the
API directly.
This requires the matching local-credential oo-api broker. Default Google login
requests YouTube alongside Gmail, Calendar and Drive; use --scopes to restrict
the request. Actual granted scopes are stored locally, not in a database column.
If permission is denied, expired or missing, reconnect using the
printed command. The CLI accepts read-only grants for reads but checks upload
and management permission before a confirmed write.
The operator must enable YouTube Data API for the existing Google OAuth project and satisfy Google's app verification requirements. Authorization uses Google's normal consent page; it does not automate a YouTube webpage. Local acceptance in this PR uses synthetic credentials and mocked HTTP only.
Read results
Bare co youtube lists the authorized channel's recent uploads.
list also accepts a channel ID, @handle or channel URL. It traverses the
uploads playlist, preserves order, batches metadata reads, and caps requests at
200 items and ten playlist pages. It does not spend search quota.
video reads metadata and counts; it does not download video or audio. It
accepts an 11-character ID, watch/Shorts/youtu.be URL, or number from the last
API listing. That number resolves only through
~/.co/youtube_last_list.json, an atomic file containing only the number-to-ID
map with mode 0600. Empty lists preserve the previous mapping. A missing or
corrupt number fails without fetching a replacement list. Prefer a full video
ID after changing grants; an update checks the actual owner again.
TTY output uses a table. Piped lists use tab-separated row number, full ID,
title, visibility and views. Every execution ends with one literal next
command. --json emits one object, including ok and next_command. Counts are
integers or null, so a missing value cannot become a fabricated zero. Missing
videos are “not returned,” not assumed deleted/private. Subscriber counts are
the possibly rounded API value.
Confirmed writes
An upload preview is offline: it checks a regular nonempty video-named file,
hashes its bytes, and includes the intended channel, title, description,
category, privacy and notification setting. File-extension validation is not
codec/media validation. --privacy defaults to private; subscriber
notifications are disabled. No file is uploaded while making the plan.
An update preview reads the selected video and verifies that the token's channel owns it. The preview includes the old snippet, proposed snippet and ETag. Only title and description can be changed; omitted snippet fields are preserved and the status part is never sent.
After the user reviews and approves the concrete plan, rerun its identical
command with --confirm <plan.confirmation>. There
is no broad --yes switch. Changed file bytes, metadata, channel or ETag invalidate
the digest. Confirmed upload creates a temporary private snapshot of the exact
bytes and sends one resumable request with chunk progress on stderr. It needs
disk space for that copy. Confirmed update sends If-Match to prevent overwriting
concurrent changes.
Before a write, an exclusive local receipt at
~/.co/youtube_operations/<digest>.json consumes the plan. Only its digest and
attempted state are retained, never content or tokens. A crash, timeout or
ambiguous response does not release that receipt. Inspect the account before a
new action; there is intentionally no automatic retry or receipt-reset command.
The server accepting an upload does not prove processing or public visibility.
Report the returned visibility and “processing not verified.”
The old #261 estimate of 1,600 quota units per upload is obsolete. The current official page documents an upload-specific default bucket of 100 calls/day; metadata update costs 50 units. Local preview cannot inspect the project's remaining quota. Uploads from unverified projects may be restricted to private. See videos.insert, videos.update, and ETag update protection.
Limits
No search, analytics, captions, comments, playlist editing, deletion, scheduling or media download ships here. TikTok is excluded from this Google-only release and remains deferred; see #1426 and the preview in #1439.
Exit 0 means a read/preview/write result; exit 1 means an operational failure with a sanitized cause and next command; exit 2 is a Typer usage error. Preview mode is always explicit in output. Provider error bodies, URLs and token locals are never printed.
Each write preview prints a complete next_command containing the exact arguments
and confirmation digest. After approving that plan, use the printed command.
The command remains visible when output is piped.