Why OAuth, not just an API key: reading public data (search, video info)
works with a simple
API key;
uploading to your own channel requires an
OAuth 2.0 client (the key alone cannot write).
Steps (one-time):
- Google Cloud Console → create a project.
- APIs & Services → Enable "YouTube Data API v3".
- Credentials → for read-only OSINT/search: Create API key →
put it in env
YOUTUBE_API_KEY (never hardcode).
- For uploading: Credentials → OAuth client ID (type "Desktop") →
download
client_secrets.json.
- OAuth consent screen → add scope
https://www.googleapis.com/auth/youtube.upload → add your account as a
test user.
- First run opens a browser to authorize; the token is cached
(
token_YourChannelName.pickle) for reuse.
Quota: default 10,000 units/day. An upload
costs ~1,600 units (~6 uploads/day); a search.list costs 100. Request more
in the Console if needed.
Example — upload using the JSON above
(pip install google-api-python-client google-auth-oauthlib):
import json, os
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient.discovery import build
from googleapiclient.http import MediaFileUpload
import pickle
SCOPES = ["https://www.googleapis.com/auth/youtube.upload"]
m = json.load(open("metadata.json")) # the JSON from this tab
# --- OAuth (cache token, refresh when expired) ---
tok = m["video_config"]["token_file"]
creds = pickle.load(open(tok, "rb")) if os.path.exists(tok) else None
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
creds = InstalledAppFlow.from_client_secrets_file(
"client_secrets.json", SCOPES).run_local_server(port=0)
pickle.dump(creds, open(tok, "wb"))
yt = build("youtube", "v3", credentials=creds)
req = yt.videos().insert(
part="snippet,status,recordingDetails",
body={"snippet": m["snippet"], "status": m["status"],
"recordingDetails": m["recordingDetails"]},
media_body=MediaFileUpload(m["video_config"]["video_file"],
chunksize=-1, resumable=True),
)
resp = req.execute()
print("https://youtu.be/" + resp["id"])
Read-only OSINT search (no OAuth):
GET https://www.googleapis.com/youtube/v3/search?part=snippet&type=video&q=QUERY&key=$YOUTUBE_API_KEY
Other useful Google APIs: Cloud Translation (multilingual slang),
Cloud Vision (auto thumbnail / species-in-image),
Custom Search JSON API (web/marketplace OSINT),
Maps Geocoding (location text → coordinates).