Skip to main content

youtube-api

Use when wiring code to a real YouTube channel: user OAuth 2.0, resumable videos.insert uploads, editing metadata after publish, pulling views/watch time/retention/traffic from the Analytics API v2 into a dated 02-DOCS/wiki/youtube/ feedback log. NOT what to publish or how to title and thumbnail it (that is youtube-strategy / youtube-packaging).

معلومات المصدر

المستودع
ericrisco/rsc-harness
آخر نشاط في المصدر
٢٩ يوليو ٢٠٢٦ في ٢٢:٥١
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
١٤٢
التفرعات
١١

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

مستكشف الملفات
7 ملفات

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
youtube-api
description
Use when wiring code to a real YouTube channel: user OAuth 2.0, resumable videos.insert uploads, editing metadata after publish, pulling views/watch time/retention/traffic from the Analytics API v2 into a dated 02-DOCS/wiki/youtube/ feedback log. NOT what to publish or how to title and thumbnail it (that is youtube-strategy / youtube-packaging).
tags
["youtube","youtube-data-api","youtube-analytics-api","oauth2","resumable-upload","video-metadata","audience-retention","channel-feedback-log"]
recommends
["social-publisher","api-connector-builder","automation-flows","knowledge-ops"]
origin
risco
# YouTube API — Transport + Ingestion for a Real Channel *You own the wire: authenticate to a YouTube channel, upload and edit videos, pull the numbers, and write those numbers into the wiki as a durable feedback log. You do not decide what to make or how to title it — that is the strategy/packaging family. Deliver clean transport and a queryable log; let the siblings interpret.* YouTube has **two separate APIs** and you will touch both: - **Data API v3** — `https://www.googleapis.com/youtube/v3` — the *write* side: `videos.insert` (upload), `videos.update` (edit metadata), `thumbnails.set`. Default quota: a shared 10,000 units/day pool *plus* separate per-day caps of ~100 uploads and ~100 `search.list` calls (see §7). - **Analytics API v2** — `https://youtubeanalytics.googleapis.com/v2/reports` — the *read* side: views, watch time, retention, traffic sources. Separate scopes, separate quota. Auth is **user OAuth 2.0, not a service account.** A human owns the channel; you act on their behalf with a refresh token. A service account cannot own a YouTube channel — if you reach for one, stop. (Service-account Google auth for Gmail/Drive/Sheets is `google-workspace`, a different model.) Route elsewhere when the job is not transport: | You actually want | Go to | | --- | --- | | What videos to make / niche / cadence / growth plan | `../youtube-strategy/SKILL.md` | | Video ideas, hooks, a topic backlog | `../youtube-ideation/SKILL.md` | | Title + thumbnail packaging, CTR copy, A/B framing | `../youtube-packaging/SKILL.md` | | The thumbnail image itself | `../youtube-thumbnails/SKILL.md` | | Render/produce the actual video file in code | `../remotion-video/SKILL.md` | | Post one asset across many networks at once | `../social-publisher/SKILL.md` | | Wrap an arbitrary REST provider with OAuth + retries | `../api-connector-builder/SKILL.md` | | Chain upload → Notion row → Slack across tools | `../automation-flows/SKILL.md` | | Service-account auth to Gmail/Drive/Sheets | `../google-workspace/SKILL.md` | | Generic wiki structure and conventions | `../knowledge-ops/SKILL.md` | ## 1. One-time setup (do this before any code) A checklist, because each missing step produces a distinct, confusing error later: 1. Create (or pick) a **GCP project**. 2. **Enable both APIs** in that project: "YouTube Data API v3" *and* "YouTube Analytics API". Enabling one is the most common cause of a `403 ... has not been used in project` on the other. 3. Configure the **OAuth consent screen** and **publish it to "In production."** Why: an app left in "Testing" status issues refresh tokens that **expire in 7 days** — your cron silently dies the following week. This is the single most common YouTube-ingestion breakage. 4. Create an **OAuth client**: *Desktop app* for a personal/local script, *Web application* (with an exact redirect URI) for a hosted app. 5. Request **least-privilege scopes** — not the full management scope. Scope table — request only what the job needs: | Scope | Grants | Use for | | --- | --- | --- | | `https://www.googleapis.com/auth/youtube.upload` | Upload videos | `videos.insert` | | `https://www.googleapis.com/auth/youtube.force-ssl` | Manage/edit/delete | `videos.update`, `thumbnails.set` | | `https://www.googleapis.com/auth/yt-analytics.readonly` | Read performance | Analytics reports | | `https://www.googleapis.com/auth/yt-analytics-monetary.readonly` | Read revenue metrics | only if you pull `estimatedRevenue` etc. | Bad → Good on scopes: ```text Bad: scopes = ["https://www.googleapis.com/auth/youtube"] # full account control Good: scopes = ["https://www.googleapis.com/auth/youtube.upload", "https://www.googleapis.com/auth/youtube.force-ssl", "https://www.googleapis.com/auth/yt-analytics.readonly"] ``` Full GCP + consent-screen walkthrough, Desktop-vs-Web choice, and `unauthorized_client` / `access_denied` / redirect-URI troubleshooting live in `references/oauth-setup.md`. ## 2. Get an authed client and keep the token alive Build both service clients from **one** set of OAuth credentials. ```python # python: google-api-python-client + google-auth-oauthlib from google_auth_oauthlib.flow import InstalledAppFlow from google.auth.transport.requests import Request from googleapiclient.discovery import build import json, os from google.oauth2.credentials import Credentials SCOPES = ["https://www.googleapis.com/auth/youtube.upload", "https://www.googleapis.com/auth/youtube.force-ssl", "https://www.googleapis.com/auth/yt-analytics.readonly"] TOKEN = "token.json" # gitignored — holds the refresh_token def creds(): c = Credentials.from_authorized_user_file(TOKEN, SCOPES) if os.path.exists(TOKEN) else None if not c or not c.valid: if c and c.expired and c.refresh_token: c.refresh(Request()) # silent renewal — needs "In production" app else: c = InstalledAppFlow.from_client_secrets_file( "client_secret.json", SCOPES).run_local_server(port=0) with open(TOKEN, "w") as f: f.write(c.to_json()) return c c = creds() data = build("youtube", "v3", credentials=c) yta = build("youtubeAnalytics", "v2", credentials=c) ``` ```javascript // node: googleapis + google-auth-library import { google } from "googleapis"; import fs from "node:fs"; const oauth2 = new google.auth.OAuth2(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI); oauth2.setCredentials(JSON.parse(fs.readFileSync("token.json", "utf8"))); // { refresh_token, ... } oauth2.on("tokens", (t) => { // persist the refreshed token if (t.refresh_token) fs.writeFileSync("token.json", JSON.stringify(t)); }); const data = google.youtube({ version: "v3", auth: oauth2 }); const yta = google.youtubeAnalytics({ version: "v2", auth: oauth2 }); ``` Rule: persist the **refresh token**, never a bare `access_token`. Access tokens expire in ~1 hour; the credential object refreshes itself. A hardcoded `access_token=...` literal is a guaranteed 401 within the hour — and is exactly what `verify.sh` flags. ## 3. Upload a video (resumable, always) Decision: any file over a few MB → use the **resumable** protocol. A plain multipart POST drops the whole upload on one network hiccup; resumable survives it. The protocol is two steps: 1. **Init the session** — POST to the upload endpoint with `uploadType=resumable` and the metadata body. The response `Location` header is your session URL. 2. **PUT the bytes** to that session URL — one shot or in chunks. On a dropped connection, query with `Content-Range: bytes */*`; the server returns the byte offset already received, and you resume from there. The client libraries do this for you via a media-upload helper: ```python from googleapiclient.http import MediaFileUpload body = { "snippet": {"title": "Ep. 14", "description": "...", "tags": ["x", "y"], "categoryId": "27"}, "status": {"privacyStatus": "private", "publishAt": "2026-06-10T15:00:00Z", "selfDeclaredMadeForKids": False}, } media = MediaFileUpload("ep14.mp4", chunksize=8 * 1024 * 1024, resumable=True) req = data.videos().insert(part="snippet,status", body=body, media_body=media) resp = None while resp is None: status, resp = req.next_chunk() # resumes automatically on a transient drop video_id = resp["id"] data.thumbnails().set(videoId=video_id, media_body=MediaFileUpload("ep14.jpg")).execute() ``` Throttle the upload **count**, not the unit spend: `videos.insert` has its own cap of ~100 uploads/day, so `uploadLimitExceeded` (429) hits while the 10k unit pool still looks untouched. Space automated uploads out and back off on 429 — figures and citation in §7. ## 4. Edit metadata after publish `videos.update` does **read-modify-write** semantics on whole parts. The trap: any field you omit inside a part you send gets **cleared**. ```python # Bad: wipes description and tags because they aren't in the body data.videos().update(part="snippet", body={"id": vid, "snippet": {"title": "New title", "categoryId": "27"}}).execute() # Good: fetch the current snippet, change one field, send it back whole cur = data.videos().list(part="snippet", id=vid).execute()["items"][0]["snippet"] cur["title"] = "New title" data.videos().update(part="snippet", body={"id": vid, "snippet": cur}).execute() ``` `categoryId` is required when you send a `snippet` part — another reason to read-modify-write. An update costs **~50 units**. ## 5. Pull performance (Analytics API v2) Every report is `GET /v2/reports?ids=channel==MINE&startDate=&endDate=&metrics=...&dimensions=...&filters=...`. Four recipes cover the loop: ```python # (a) Channel KPIs over a date range yta.reports().query(ids="channel==MINE", startDate="2026-05-01", endDate="2026-05-31", metrics="views,estimatedMinutesWatched,averageViewDuration,averageViewPercentage" ).execute() # (b) Per-video KPIs yta.reports().query(ids="channel==MINE", startDate="2026-05-01", endDate="2026-05-31", metrics="views,estimatedMinutesWatched,averageViewPercentage", filters="video==VIDEO_ID").execute() # (c) Audience-retention curve for one video yta.reports().query(ids="channel==MINE", startDate="2026-05-01", endDate="2026-05-31", metrics="audienceWatchRatio,relativeRetentionPerformance", dimensions="elapsedVideoTimeRatio", filters="video==VIDEO_ID").execute() # (d) Traffic sources yta.reports().query(ids="channel==MINE", startDate="2026-05-01", endDate="2026-05-31", metrics="views,estimatedMinutesWatched", dimensions="insightTrafficSourceType").execute() ``` Notes that save an afternoon: - `elapsedVideoTimeRatio` runs 0.0–1.0 (playback position); `audienceWatchRatio` is the absolute retention at that point. Plot one against the other to find the drop-off. - `insightTrafficSourceType` values include `YT_SEARCH`, `SUGGESTED`, `BROWSE`, `EXT_URL`, `NOTIFICATION`, `PLAYLIST`, `END_SCREEN`, `NO_LINK_EMBEDDED`. - **Impressions / CTR** (`impressions`, `impressionClickThroughRate`) are **content-owner-report metrics**, not reliably available on a plain channel query. If the API returns nothing for them, fall back to the value Studio shows — do not block the pull. Full metric+dimension catalog and copy-paste bodies for geography, device, and subscribed-status reports are in `references/analytics-queries.md`. ## 6. Ingest into the wiki — the actual deliverable A pull that prints to stdout and vanishes is wasted. **Every pull appends a dated entry under `02-DOCS/wiki/youtube/`** so the channel's numbers become queryable history that the strategy/packaging siblings can read. ```text 02-DOCS/wiki/youtube/ index.md # rolling pointer to latest snapshot + open questions channel-2026-05-31.md # dated channel snapshot (one per pull) videos/<VIDEO_ID>.md # per-video running log, newest entry on top ``` Per-pull entry template (OKF v0.1: non-empty `type` + the domain keys siblings parse): ```markdown --- type: youtube-metrics title: Channel snapshot 2026-05-31 description: Channel KPIs, traffic and retention for 2026-05-01..2026-05-31. tags: [youtube, channel-snapshot] timestamp: 2026-05-31T00:00:00Z date: 2026-05-31 range: 2026-05-01..2026-05-31 channel: MINE source: youtube-analytics-api-v2 --- ## KPIs views: 41,233 | watch_min: 88,140 | avg_view_pct: 38.2% | avg_view_dur: 0:04:11 ## Traffic (top 3) BROWSE 44% · SUGGESTED 31% · YT_SEARCH 12% ## Retention Sharp drop 0.00→0.06 (intro), recovers, second dip ~0.55. ## What changed since last pull avg_view_pct +2.1pts; SUGGESTED share up 6pts after the Ep.13 packaging change. ``` Rule: **append, never overwrite.** The feedback log *is* the value — overwriting yesterday's snapshot destroys the trend the siblings need. `02-DOCS/wiki/` is an OKF v0.1 bundle: keep the domain keys (`date`, `range`, `channel`, `source`) the siblings parse, add `type`/`timestamp` alongside them, use standard markdown links (never `[[wikilinks]]`), and leave the reserved `index.md` frontmatter-free. Exact file tree, naming, per-video log shape, and how siblings read the log: `references/wiki-schema.md`. ## 7. Quota & failure math | Call | Cost / allocation | | --- | --- | | `videos.insert` (upload) | own daily cap: **~100 uploads/day**, not 10k-pool units | | `search.list` | own daily cap: ~100 calls/day | | write: `videos.update`, `thumbnails.set` | ~50 units (from the 10k pool) | | `*.list` read | 1–5 units (from the 10k pool) | | Analytics `reports.query` | (separate Analytics quota) | Per the official docs (Last updated 2026-06-01 UTC, `https://developers.google.com/youtube/v3/getting-started`; per-method costs at `https://developers.google.com/youtube/v3/determine_quota_cost`), a project's default allocation is *"100 search.list calls, 100 videos.insert calls, and 10,000 units per day combined for all other endpoints."* All quotas reset at midnight Pacific. This is a change from the older single-pool model where `videos.insert` cost ~1,600 units — confirm the live figure on the quota-cost page before relying on it, since Google notes the allocation "is subject to change."
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub