YouTube Data API skill for Codex

Channel automation that fits in 10,000 units. Codex lists a channel's videos through its uploads playlist and batches statistics 50 IDs per call, so a full export costs dozens of quota units instead of thousands spent on search. Uploads use resumable OAuth with private-by-default status, metadata edits fetch and resend whole snippets after you confirm the diff, and errors such as quotaExceeded are handled with checkpoints.

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- youtube-data-api

Installs to ~/.agents/skills/youtube-data-api/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $youtube-data-api, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Automates YouTube with the YouTube Data API v3 in Python - list a channel's uploads via the uploads playlist instead of search, batch video statistics 50 IDs at a time, read comments, resumable uploads with OAuth, playlists and metadata updates - while budgeting the 10,000-unit daily quota and following API policy. Use when the user wants to export channel or video stats, bulk-update titles or descriptions, upload or schedule videos from a script, pull comments, or fix quotaExceeded and 403 errors.

Use it when

  • You need channel or video data exported or refreshed on a schedule
  • Videos should be uploaded, scheduled or updated in bulk from a script
  • Comments or playlists need to be read or managed programmatically
  • A YouTube script fails with quotaExceeded or 403 errors

Use something else when

How to check the result

Count API calls times unit cost and compare with the Cloud console quota graph, confirm exported IDs match the channel's video count, and re-fetch edited videos to diff against intended values.

The mistake it prevents

Using search.list to enumerate a channel's videos, which costs 100 units per page and still misses videos.

~/.agents/skills/youtube-data-api/SKILL.md

--- name: youtube-data-api description: Automates YouTube with the YouTube Data API v3 in Python - list a channel's uploads via the uploads playlist instead of search, batch video… license: MIT ---

Done well means: the job fits comfortably inside the daily quota, uses API keys for public reads and OAuth only where a channel owner must consent, every write (upload, metadata change, comment) was confirmed by the user, and stored data follows YouTube’s developer policies.

Quota first

Default projects get 10,000 units per day, reset at midnight Pacific time. Cost is per request, not per item, and failed requests still cost at least 1 unit.

CallApprox. unitsUse it for
channels.list, videos.list, playlistItems.list, playlists.list, commentThreads.list1Almost everything read-side
search.list100Only real keyword search; never to list a channel’s videos
videos.update, playlists.insert, playlistItems.insert, comments.insert50Writes
thumbnails.set50Custom thumbnails (channel must be verified)
captions.list / captions.download50 / 200Owner-only caption access
videos.insert (upload)High, see the quota calculatorUploads; budget a handful per day by default

Check Google’s “Quota Calculator” page for current values before planning a large job; costs have changed over time. Request a quota increase through the YouTube API audit form, which requires compliance with the developer policies.

Quick estimate: exporting stats for a 2,000-video channel costs about 40 playlist pages plus 40 videos.list batches = ~80 units. The same job via search.list would cost 4,000+ units and still miss videos (search results are capped and not exhaustive).

Credentials

NeedAuth
Public data (any channel’s videos, stats, public comments)API key, restricted to the YouTube Data API
The user’s own channel: uploads, private videos, edits, captionsOAuth 2.0 client (Desktop app type) with the narrowest scope

Scopes: youtube.readonly (read), youtube.upload (upload only), youtube (manage), youtube.force-ssl (needed for comments and captions writes). Store the refresh token outside the repo; never print tokens. Creating the Cloud project, enabling the API and the OAuth consent screen happen in the Google Cloud console and need the user.

pip install google-api-python-client google-auth-oauthlib   # needs network

Read: every upload of a channel with stats

import os
from googleapiclient.discovery import build

yt = build("youtube", "v3", developerKey=os.environ["YT_API_KEY"])

ch = yt.channels().list(part="contentDetails,statistics", forHandle="@SomeChannel").execute()
if not ch.get("items"):
    raise SystemExit("channel not found")
uploads = ch["items"][0]["contentDetails"]["relatedPlaylists"]["uploads"]

ids: list[str] = []
req = yt.playlistItems().list(part="contentDetails", playlistId=uploads, maxResults=50)
while req is not None:
    resp = req.execute()
    ids += [it["contentDetails"]["videoId"] for it in resp["items"]]
    req = yt.playlistItems().list_next(req, resp)

rows = []
for i in range(0, len(ids), 50):                          # videos.list takes up to 50 IDs
    resp = yt.videos().list(part="snippet,statistics,contentDetails",
                            id=",".join(ids[i:i + 50]),
                            fields="items(id,snippet(title,publishedAt),"
                                   "statistics(viewCount,likeCount,commentCount),"
                                   "contentDetails(duration))").execute()
    for v in resp["items"]:
        s = v.get("statistics", {})
        rows.append({
            "id": v["id"], "title": v["snippet"]["title"],
            "published": v["snippet"]["publishedAt"],
            "duration": v["contentDetails"]["duration"],         # ISO 8601, e.g. PT4M13S
            "views": int(s.get("viewCount", 0)),
            "likes": int(s["likeCount"]) if "likeCount" in s else None,   # hidden likes
            "comments": int(s["commentCount"]) if "commentCount" in s else None,
        })
  • fields trims the response (less bandwidth; same quota cost).
  • Statistics are strings; convert. Missing keys mean hidden or disabled, not zero.
  • Deleted or private videos stay in the uploads playlist for the owner but return nothing from videos.list for others; compare counts and report the gap.
  • Watch time, audience retention and revenue are not in the Data API. Use the YouTube Analytics API (youtubeAnalytics v2, reports.query) with the owner’s OAuth consent.

Write: upload and metadata (OAuth)

from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.http import MediaFileUpload

SCOPES = ["https://www.googleapis.com/auth/youtube.upload"]
creds = InstalledAppFlow.from_client_secrets_file("client_secret.json", SCOPES).run_local_server(port=0)
yt = build("youtube", "v3", credentials=creds)

body = {
    "snippet": {"title": title, "description": desc, "tags": tags, "categoryId": "28"},
    "status": {"privacyStatus": "private", "publishAt": "2026-11-03T15:00:00Z",
               "selfDeclaredMadeForKids": False},
}
media = MediaFileUpload("render.mp4", mimetype="video/mp4", chunksize=-1, resumable=True)
req = yt.videos().insert(part="snippet,status", body=body, media_body=media)
response = None
while response is None:
    status, response = req.next_chunk()          # retry 5xx here with backoff
    if status:
        print(f"{int(status.progress() * 100)}%")
print("uploaded", response["id"])
  • Uploading, scheduling, changing titles or posting comments is publishing on the user’s channel: show the exact metadata and get explicit confirmation before each run, and default new uploads to private.
  • publishAt only works with privacyStatus: private; YouTube flips it public at that time.
  • Videos uploaded through an unverified API project are locked to private. The project must pass Google’s audit before public uploads work.
  • videos.update replaces every field in the parts you send: fetch the current snippet, modify it, and send it back whole, or you wipe tags and description. categoryId is required in snippet updates.
  • Bulk metadata edits: build a CSV of old and new values, show the diff, confirm, then apply with a log of video IDs so the change can be reverted.

Errors

ErrorMeaningAction
403 quotaExceededDaily quota spentStop; resume after the Pacific midnight reset; checkpoint progress
403 forbidden / insufficientPermissionsWrong scope or not the ownerRe-consent with the right scope
403 commentsDisabled, videoNotFound (404)Per-item conditionSkip the item, log it, continue
400 invalidCategoryId, invalidTitleBad metadata (titles max 100 chars, no < or >)Fix input
401Expired or revoked tokenRefresh; if revoked, the user must authorise again
5xx, backendErrorTransientExponential backoff, then retry

Policy points that change designs

  • The YouTube API Services Terms and Developer Policies limit how long API data may be stored without refreshing and require deleting data when users revoke access. Re-fetch stored statistics on a schedule rather than archiving them forever, and read the current policy before building a long-term dataset.
  • Do not scrape YouTube pages to get around quota; it violates the terms.
  • Show YouTube attribution and link to the video when displaying API data publicly.

Gotchas

  • Handles (@name) resolve with forHandle; legacy usernames with forUsername; channel IDs start with UC. The uploads playlist ID is usually the channel ID with UU prefix, but read it from contentDetails rather than deriving it.
  • search.list results are approximate and capped at roughly 500 per query; it is not a listing API.
  • commentThreads.list returns top-level comments with up to 5 replies; fetch the rest with comments.list(parentId=...).
  • Durations are ISO 8601 (PT1H2M3S); Shorts are not flagged explicitly in the API.
  • Quota is per Cloud project, shared by every script using that key.

Verification

  • Log units spent per run (count calls x cost) and compare with the Cloud console quota graph after the first run.
  • For exports, the number of IDs equals the channel’s video count shown in YouTube Studio (minus private videos when using an API key).
  • For writes, re-fetch the updated videos and diff against the intended values; uploads start private, so check processing status in Studio before announcing anything.

Run report

**Job:** export uploads of @SomeChannel with stats -> data/videos-2026-10-28.csv
**Calls:** 41 playlistItems.list + 41 videos.list = 82 units (of 10,000)
**Result:** 2,031 videos; 4 with hidden like counts; 0 errors
**Writes:** none

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "YouTube Data API"
  short_description: "Channel automation that fits in 10,000 units"
  default_prompt: "$youtube-data-api Export every video on our channel with views, likes and duration to a CSV"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem