Atlas survey

YouTube Auth, Cookies & Watch Later Mechanics (2026)

How YouTube's cookie-based auth works in 2026: the SAPISID/SAPISIDHASH flow, PO token requirements, Chrome's app-bound encryption breaking external extraction, and Watch Later's API quirks.

16 sources ~4 min read #194 youtube · authentication · cookies · watch-later · yt-dlp · innertube

TL;DR Use Firefox cookies for YouTube auth in 2026 — Chrome 127+ app-bound encryption breaks all external extraction tools. [1] The core auth chain is SAPISID → SAPISIDHASH header for InnerTube API calls, now augmented by mandatory PO tokens that must be generated by BotGuard/DroidGuard. [5] Watch Later (list=WL) works with yt-dlp ⭐ 169k + cookies, but is read-mostly via the official Data API: list and insert work; update, delete, and reorder do not. [3]

YouTube auth state lives in a cluster of .youtube.com / .google.com cookies. The critical ones for InnerTube API access:

Cookie Lifetime Role
SAPISID 2 yr Source for SAPISIDHASH; required for authenticated InnerTube requests [4]
__Secure-3PAPISID 2 yr Secure-context APISID variant; used when available, preferred over SAPISID by InnerTube clients [4]
SID 2 yr Session identifier; paired with HSID for replay-attack defence [12]
HSID 2 yr HTTP-only session token; contains signed Google Account ID + last-login time [12]
SSID 2 yr Secure (HTTPS-only) variant of SID [12]
VISITOR_INFO1_LIVE 6 mo Encodes the Visitor ID; maps to visitor_data in InnerTube requests; anchors PO tokens for unauthenticated sessions [9]
YSC session Per-tab session token; anti-CSRF
LOGIN_INFO 2 yr Account-chooser state; not involved in request authentication
PREF 2 yr User preferences (language, autoplay, etc.); not auth-critical

The maintainer of YoutubeExplode ⭐ 3.7k confirmed that APISID/SAPISID (or the __Secure-3PAPISID variant) are the required cookies; the others appear in real browser sessions but are not strictly required for playback. [4]

Authentication Flow: SAPISIDHASH

YouTube’s InnerTube API — the private JSON API used by yt-dlp, YoutubeExplode, and third-party clients — authenticates via a derived Authorization header, not OAuth. The formula: [6][13]

Authorization: SAPISIDHASH {ts}_{SHA1("{ts} {SAPISID} https://www.youtube.com")}

Where {ts} is a fresh Unix epoch timestamp per request. Required alongside:

  • Cookie header with the full session cookie set
  • X-Origin: https://www.youtube.com

OAuth is dead for third-party tools. --username/--password and device-code login no longer work — YouTube deprecated them for non-Google clients. Browser cookies are now the only authentication path. [2]

PO Token System

Since 2024–2025, YouTube enforces Proof of Origin (PO) tokens — attestation tokens verifying that requests originate from a genuine browser or device. Missing or invalid tokens → HTTP 403. [5]

Context Client PO token required?
GVS (video stream) web ✓ (waived for YouTube Premium)
GVS mweb
Player (format URL) web
Subs (subtitles) web
Player android ✓ (DroidGuard)
Player ios ✓ (iOSGuard)

Tokens bind to: [8][9]

  • DATASYNC_ID — authenticated GVS requests (account session)
  • VISITOR_DATA — unauthenticated GVS requests (sourced from VISITOR_INFO1_LIVE)
  • VIDEO_ID — Player and Subs contexts regardless of auth status

Using yt-dlp ⭐ 169k: [14]

# Manual token injection
yt-dlp --extractor-args "youtube:player-client=web,default;po_token=web+TOKEN_VALUE" URL

# Plugin-based automatic generation (recommended)
pip install bgutil-ytdlp-pot-provider
yt-dlp URL   # plugin hooks in automatically

Active PO token plugins: bgutil-ytdlp-pot-provider ⭐ 555 (uses LuanRT’s BotGuard library; runs as HTTP server or CLI subprocess) [15] and yt-dlp-get-pot ⭐ 112 (abstract provider framework for custom integrations). [16]

Rate limits: authenticated session → ~2,000 videos/hr; guest/visitor session → ~300 videos/hr. [2]

Chrome is broken. Chrome 127 (July 2024) shipped app-bound encryption — the AES decryption key is bound to the Chrome binary itself. [1] Every external extraction path now fails:

  • DPAPI decryption — fails even with admin rights
  • rookiepy Rust library — decrypt_encrypted_value failed
  • Chrome DevTools Protocol (port 9222) — port-binding issues on Windows

Firefox works. Firefox stores cookies unencrypted in SQLite at %APPDATA%\Mozilla\Firefox\Profiles\*\cookies.sqlite (Linux: ~/.mozilla/firefox/*/cookies.sqlite). [1]

import sqlite3, shutil, glob, os
profiles = os.path.join(os.environ["APPDATA"], "Mozilla", "Firefox", "Profiles")
db = max(glob.glob(f"{profiles}/*/cookies.sqlite"), key=os.path.getsize)
shutil.copy2(db, "tmp.sqlite")   # copy to avoid lock
conn = sqlite3.connect("tmp.sqlite")
rows = conn.execute(
    "SELECT host, name, value, path, expiry, isSecure "
    "FROM moz_cookies WHERE host LIKE '%youtube%' OR host LIKE '%google%'"
).fetchall()
# → 60–70 rows in Netscape format ready for yt-dlp --cookies

Or directly: yt-dlp --cookies-from-browser firefox URL

Rotation caveat: Cookies expire in ~2 weeks. [7] To preserve a working session: log in via a private/incognito window, export cookies immediately, then close the tab — leaving an authenticated tab open triggers background rotation scripts that invalidate the exported cookies. [2]

Watch Later Mechanics

Identity

Watch Later is a system-managed RelatedPlaylist identified by the short token WL. Its full ID is user-specific and dynamic — never hardcode it. Retrieve via the Data API: [3]

svc = build("youtube", "v3", credentials=creds)
resp = svc.channels().list(part="contentDetails", mine=True).execute()
wl_id = resp["items"][0]["contentDetails"]["relatedPlaylists"]["watchLater"]

Browser URL: https://www.youtube.com/playlist?list=WL (resolves via session cookie).

Data API Capabilities

Operation YouTube Data API v3 Notes
List items playlistItems.list Requires OAuth youtube.readonly scope
Insert item playlistItems.insert Requires OAuth youtube scope
Update item 403: “The playlist cannot be modified” [3]
Delete item 403 or 404 [3]
Reorder items Impossible via API
Rename / change settings Impossible via API

YouTube’s backend enforces special auto-management behavior on Watch Later (e.g., removing watched videos via the UI) that would conflict with arbitrary API writes. [3]

Workaround for full CRUD: create a custom playlist. It grants complete insert/update/delete/reorder control unavailable on the native Watch Later. [3]

“Remove Watched” Button

YouTube’s web UI includes a “Remove watched videos” button on the Watch Later page. It is manual, not automatic — it bulk-removes all videos where any playback progress was recorded, including partially watched ones. [11] No programmatic API equivalent exists.

Content restriction: “Made for kids” videos cannot be added to Watch Later at all. [11]

Downloading with yt-dlp

Watch Later is a private playlist; cookies are required: [10]

yt-dlp --cookies-from-browser firefox "https://www.youtube.com/playlist?list=WL"

⚠ Known issue: using yt-dlp as a Python module (not CLI subprocess) can trigger "YouTube said: The playlist does not exist" for WL even with valid cookies. Root cause: deleted/unavailable videos in the queue produce a response the module path mishandles. Workaround: invoke via subprocess.run(["yt-dlp", ...]). [10]

Citations · 16 sources

Click the Citations tab to load…