The PO Token Manager is a component that manages YouTube PO (proof-of-origin) tokens required for Player, GVS (GetVideoStream), and Subs (Subtitles) API calls. It provides a flexible abstraction for obtaining, caching, and providing tokens through multiple provider strategies.
- POTokenManager: Main coordinator that manages providers and caching
- POTokenProvider: Protocol/interface for token providers
- POTokenCache: Cache with TTL and cooldown logic
- Token Providers:
ManualTokenProvider: Manual token injection from settings/environmentHTTPTokenProvider: External HTTP service for token generation
The system supports three YouTube token types:
- PLAYER: Player API token for video playback
- GVS: GetVideoStream API token for stream access
- SUBS: Subtitles API token for caption access
Add these settings to your .env file:
# Manual token injection (highest priority)
PO_TOKEN_PLAYER=your_player_token_here
PO_TOKEN_GVS=your_gvs_token_here
PO_TOKEN_SUBS=your_subs_token_here
# External provider configuration
PO_TOKEN_PROVIDER_ENABLED=false
PO_TOKEN_PROVIDER_URL=http://localhost:8080
PO_TOKEN_PROVIDER_TIMEOUT=5.0
# Cache and cooldown settings
PO_TOKEN_CACHE_TTL=3600 # 1 hour
PO_TOKEN_COOLDOWN_SECONDS=60 # 1 minute| Setting | Default | Description |
|---|---|---|
PO_TOKEN_PLAYER |
"" |
Manual player token |
PO_TOKEN_GVS |
"" |
Manual GVS token |
PO_TOKEN_SUBS |
"" |
Manual subs token |
PO_TOKEN_PROVIDER_ENABLED |
False |
Enable external provider |
PO_TOKEN_PROVIDER_URL |
"" |
Provider service URL |
PO_TOKEN_PROVIDER_TIMEOUT |
5.0 |
Provider request timeout (seconds) |
PO_TOKEN_CACHE_TTL |
3600 |
Token cache TTL (seconds) |
PO_TOKEN_COOLDOWN_SECONDS |
60 |
Cooldown after failure (seconds) |
The token manager is automatically initialized in the worker loop:
from worker.po_token_manager import get_token_manager, TokenType
# Get the global token manager instance
token_manager = get_token_manager()
# Request a token
player_token = token_manager.get_token(TokenType.PLAYER)
if player_token:
# Use token in yt-dlp or other YouTube API calls
passTokens can be requested with context for more specific caching:
context = {
"region": "us",
"session_id": "abc123",
"client": "web_safari"
}
token = token_manager.get_token(TokenType.PLAYER, context=context)When a token fails (e.g., 403 Forbidden), mark it as invalid to trigger cooldown:
token_manager.mark_token_invalid(
TokenType.PLAYER,
reason="forbidden_error"
)Monitor token manager performance:
stats = token_manager.get_stats()
print(f"Cache hit rate: {stats['cache']['hit_rate']:.2%}")
print(f"Success rate: {stats['retrievals']['success_rate']:.2%}")The simplest provider reads tokens from settings:
from worker.po_token_providers import ManualTokenProvider
provider = ManualTokenProvider(
player_token="manual_token_123",
gvs_token="gvs_token_456",
)External service for dynamic token generation:
from worker.po_token_providers import HTTPTokenProvider
provider = HTTPTokenProvider(
base_url="http://localhost:8080",
timeout=5.0,
)Your external service should implement:
Endpoint: GET /token
Query Parameters:
type: Token type (player,gvs, orsubs)context(optional): JSON-encoded context object
Response Format:
{
"token": "generated_token_value"
}Error Response:
{
"error": "error_message"
}Implement the POTokenProvider protocol:
from typing import Optional
from worker.po_token_manager import POTokenProvider, TokenType
class CustomProvider:
"""Custom token provider example."""
def get_token(self, token_type: TokenType, context: Optional[dict] = None) -> Optional[str]:
# Your token generation logic here
return "custom_token"
def is_available(self) -> bool:
# Check if provider is ready
return True
# Register with manager
token_manager = get_token_manager()
token_manager.add_provider(CustomProvider())Tokens are cached for PO_TOKEN_CACHE_TTL seconds (default: 1 hour). After expiration:
- Cache returns
None - Manager tries providers again
- Fresh token is cached
When a token is marked invalid:
- Token enters cooldown period (
PO_TOKEN_COOLDOWN_SECONDS, default: 60s) - Cache won't return the token during cooldown
- Prevents hammering providers with bad tokens
- After cooldown, providers are retried
Cache keys include:
- Token type (player, gvs, subs)
- Context values (if provided)
Example keys:
player
player:region=us:session_id=abc123
gvs:client=web_safari
The token manager is integrated into the worker's audio download process:
# In worker/audio.py
def _yt_dlp_cmd(base_out: Path, url: str, strategy: Optional[ClientStrategy] = None) -> List[str]:
cmd = ["yt-dlp", "-v", "-f", "bestaudio", ...]
# Add PO tokens if available
po_tokens = _get_po_tokens()
if po_tokens:
# Format: --extractor-args "youtube:po_token=player:TOKEN1;po_token=gvs:TOKEN2"
token_args = []
for token_type, token_value in po_tokens.items():
token_args.append(f"po_token={token_type}:{token_value}")
extractor_arg = "youtube:" + ";".join(token_args)
cmd.extend(["--extractor-args", extractor_arg])
return cmdThe following Prometheus metrics are exported:
-
po_token_retrievals_total{token_type, result}: Total token retrievalsresult:success,failed,cached
-
po_token_provider_attempts_total{provider, token_type}: Provider attempts -
po_token_cache_hits_total{token_type}: Cache hits -
po_token_cache_misses_total{token_type}: Cache misses -
po_token_retrieval_latency_seconds{provider, token_type}: Retrieval latency histogram -
po_token_failures_total{token_type, reason}: Token failures/invalidations
from worker.metrics import (
po_token_retrievals_total,
po_token_cache_hits_total,
)
# Increment counters
po_token_retrievals_total.labels(
token_type="player",
result="success"
).inc()
# Get manager stats programmatically
stats = token_manager.get_stats()YouTube PO tokens have different scopes:
- Player tokens: Required for video playback API calls
- GVS tokens: Required for GetVideoStream API (downloading)
- Subs tokens: Required for subtitle/caption API calls
Most yt-dlp operations need player and GVS tokens. Subtitle operations need subs tokens.
- Acquisition: Manager tries providers in order until one succeeds
- Caching: Valid token cached with TTL
- Usage: Token used in yt-dlp extractor args
- Expiration: After TTL expires, token is refreshed
- Failure: On 403/token errors, token is marked invalid and enters cooldown
Token failures are detected via:
- HTTP 403 status codes
- "token" keyword in error messages
- "forbidden" keyword in error messages (only when "token" is also mentioned)
When detected:
- Only PLAYER and GVS token types are marked invalid on download failures
- Cooldown prevents immediate retry
- Providers are consulted after cooldown
- Manual tokens for testing: Use manual tokens during development
- Provider for production: Implement HTTP provider for dynamic token generation
- Monitor metrics: Track cache hit rates and failure reasons
- Tune TTL: Adjust based on token validity period
- Tune cooldown: Balance between retry frequency and provider load
This section provides comprehensive troubleshooting for PO token issues.
Symptoms:
ERROR: HTTP Error 403: Forbidden
ERROR: Video unavailable
Root causes:
- Invalid or expired PO tokens
- Missing required tokens (PLAYER/GVS)
- YouTube rate limiting or IP blocking
- Token type mismatch (wrong token for operation)
Diagnosis:
Check logs for token-related errors:
docker compose logs worker | grep -E "token|403|forbidden"Check metrics:
# Token usage
curl -s http://localhost:8001/metrics | grep ytdlp_token_usage_total
# Token failures
curl -s http://localhost:8001/metrics | grep po_token_failures_totalSolutions:
-
Configure tokens (if not set):
PO_TOKEN_PLAYER=your_player_token PO_TOKEN_GVS=your_gvs_token
-
Verify tokens are being used:
docker compose logs worker | grep "PO tokens added"
-
Check token expiration: Tokens typically expire after 1-24 hours
- Re-generate tokens from provider
- Or update manual tokens in
.env
-
Enable provider for automatic refresh:
PO_TOKEN_PROVIDER_ENABLED=true PO_TOKEN_PROVIDER_URL=http://your-token-service:8080
-
Check cooldown status:
docker compose logs worker | grep "cooldown"
Symptoms:
WARNING: Circuit breaker is open, rejecting request
WARNING: Circuit breaker opened after 5 consecutive failures
Root causes:
- Multiple consecutive token failures (≥5 by default)
- YouTube API outage or rate limiting
- Network connectivity issues
- Invalid token configuration
Diagnosis:
Check circuit breaker state:
# Logs
docker compose logs worker | grep "circuit breaker"
# Metrics
curl -s http://localhost:8001/metrics | grep ytdlp_circuit_breakerCircuit breaker states:
0= Closed (normal)1= Open (blocking requests)2= Half-open (testing recovery)
Solutions:
-
Wait for cooldown: Circuit breaker will test recovery after cooldown period
- Default: 60 seconds
- Check:
YTDLP_CIRCUIT_BREAKER_COOLDOWNin.env
-
Verify YouTube is accessible:
docker compose exec worker curl -I https://www.youtube.com/ -
Check token validity: Invalid tokens cause repeated failures
- Regenerate tokens
- Test tokens manually with yt-dlp
-
Adjust threshold if too sensitive:
YTDLP_CIRCUIT_BREAKER_THRESHOLD=10 # Increase from default 5 -
Manual reset (restart worker):
docker compose restart worker
Symptoms:
WARNING: Failed to retrieve token from any provider
DEBUG: No token available for type: player
Root causes:
- No providers configured
- All providers failed
- Provider service unreachable
Diagnosis:
Check token manager initialization:
docker compose logs worker | grep -E "token provider|token manager"Check provider configuration:
grep PO_TOKEN .envSolutions:
-
Configure manual tokens (simplest):
PO_TOKEN_PLAYER=your_token_here PO_TOKEN_GVS=your_gvs_token_here
-
Enable and test provider:
PO_TOKEN_PROVIDER_ENABLED=true PO_TOKEN_PROVIDER_URL=http://token-service:8080 # Test provider endpoint curl http://token-service:8080/token?type=player
-
Check provider logs for errors:
docker compose logs token-service # If using external service -
Verify provider timeout isn't too short:
PO_TOKEN_PROVIDER_TIMEOUT=10.0 # Increase if needed
Symptoms:
DEBUG: Token in cooldown period
INFO: Token marked as invalid, entering cooldown
Root causes:
- Token was recently marked invalid due to 403 error
- Token used during cooldown period (≤60s by default)
- Provider returned invalid token
Diagnosis:
Check cooldown events:
docker compose logs worker | grep "cooldown"Check last invalidation reason:
docker compose logs worker | grep "marked as invalid"Solutions:
-
Wait for cooldown to expire: Default is 60 seconds
PO_TOKEN_COOLDOWN_SECONDS=60
-
Investigate why tokens fail:
- Check token expiration
- Verify token format is correct
- Test tokens manually:
yt-dlp --extractor-args "youtube:po_token=player:YOUR_TOKEN" \ --get-url "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
-
Increase cooldown if tokens are getting rate limited:
PO_TOKEN_COOLDOWN_SECONDS=300 # 5 minutes -
Decrease cooldown if tokens are valid but marked incorrectly:
PO_TOKEN_COOLDOWN_SECONDS=30 # 30 seconds
Symptoms:
WARNING: HTTP provider timeout after 5.0 seconds
ERROR: Failed to retrieve token from HTTP provider
Root causes:
- Provider service is slow or overloaded
- Network latency between worker and provider
- Provider is down or unreachable
Diagnosis:
Test provider manually:
time curl -s http://your-provider:8080/token?type=playerCheck provider logs:
docker compose logs token-provider # If using docker composeSolutions:
-
Increase timeout:
PO_TOKEN_PROVIDER_TIMEOUT=15.0 # Increase from default 5.0 -
Check network connectivity:
docker compose exec worker ping -c 3 token-provider docker compose exec worker curl -v http://token-provider:8080/health
-
Add manual tokens as fallback:
PO_TOKEN_PLAYER=fallback_token PO_TOKEN_PROVIDER_ENABLED=true
-
Scale provider if it's overloaded
docker compose up -d --scale token-provider=3
Symptoms:
- Metrics show low
po_token_cache_hits_totalvspo_token_cache_misses_total - Frequent provider calls
- High latency for token retrieval
Diagnosis:
Check cache metrics:
curl -s http://localhost:8001/metrics | grep po_token_cacheCalculate hit rate:
# Should be >80% for good performance
hits=$(curl -s http://localhost:8001/metrics | grep po_token_cache_hits_total | awk '{print $2}')
misses=$(curl -s http://localhost:8001/metrics | grep po_token_cache_misses_total | awk '{print $2}')
if [ $((hits + misses)) -eq 0 ]; then
echo "Hit rate: No data yet"
else
echo "Hit rate: $(echo "scale=2; $hits / ($hits + $misses) * 100" | bc)%"
fiRoot causes:
- TTL too short
- Tokens invalidated frequently (due to failures)
- Context varies too much (creates separate cache entries)
- Cache not enabled or configured
Solutions:
-
Increase TTL:
PO_TOKEN_CACHE_TTL=7200 # 2 hours (from default 1 hour) -
Reduce context variation: Use fewer context fields
# Instead of: context = {"region": "us", "session": uuid4(), "client": "web"} # Use: context = {"client": "web"} # Fewer keys = better cache hit rate
-
Reduce token invalidation: Fix underlying token issues
- Use valid tokens that don't expire quickly
- Configure provider for automatic refresh
-
Monitor invalidation reasons:
docker compose logs worker | grep "mark.*invalid"
When PO tokens fail, the worker uses client fallback to try alternative YouTube clients.
Fallback sequence:
- Try web_safari with PO tokens
- If 403/token error, mark PLAYER/GVS tokens invalid
- Cooldown prevents immediate token retry
- Fall back to ios client (next in order)
- Fall back to android client
- Fall back to tv client (most reliable)
Observing fallback in logs:
docker compose logs worker | grep -E "Trying client|fallback|client failed"Example log sequence:
INFO: Trying client: web_safari with PO tokens
ERROR: HTTP 403 Forbidden with web_safari
INFO: Marking PLAYER and GVS tokens as invalid
DEBUG: Token entering cooldown for 60 seconds
INFO: Client web_safari failed, trying next...
INFO: Trying client: ios without PO tokens
INFO: Client ios succeeded
Configuration:
# Client order (tried in sequence)
YTDLP_CLIENT_ORDER=web_safari,ios,android,tv
# Retries per client
YTDLP_TRIES_PER_CLIENT=2
# Sleep between retries
YTDLP_RETRY_SLEEP=1.0Metrics:
# Track which clients succeed/fail
curl -s http://localhost:8001/metrics | grep ytdlp_operation_attempts_totalKey log patterns to watch:
-
Token retrieval:
DEBUG: Retrieving PO token for type: player DEBUG: Token retrieved from ManualTokenProvider DEBUG: Token cache hit for player -
Token usage:
INFO: PO tokens added to yt-dlp command DEBUG: Using PLAYER token: abc*** DEBUG: Using GVS token: def*** -
Token failures:
ERROR: HTTP 403 Forbidden WARNING: Detected PO token failure INFO: Marking token as invalid: player (reason: forbidden_error) DEBUG: Token entering cooldown for 60 seconds -
Circuit breaker:
WARNING: Circuit breaker opened after 5 consecutive failures INFO: Circuit breaker entering half-open state INFO: Circuit breaker closed after 2 successes -
Provider errors:
WARNING: HTTP provider request failed: Connection timeout ERROR: Failed to retrieve token from HTTP provider DEBUG: Falling back to next provider
Log levels guide:
DEBUG: Normal operations, cache hits, token retrievalsINFO: State changes, provider switches, token additionsWARNING: Failures, timeouts, circuit breaker eventsERROR: Critical failures, provider errors, download failures
Essential metrics to monitor:
-
Token usage:
ytdlp_token_usage_total{has_token="true"} # Operations with tokens ytdlp_token_usage_total{has_token="false"} # Operations without tokens- Goal: Most operations should have
has_token="true" - Alert if ratio drops below 80%
- Goal: Most operations should have
-
Token retrieval success rate:
po_token_retrievals_total{result="success"} po_token_retrievals_total{result="failed"} po_token_retrievals_total{result="cached"}- Goal: Success + cached > 95%
- Alert if failed > 5%
-
Cache performance:
po_token_cache_hits_total po_token_cache_misses_total- Goal: Hit rate > 80%
- Calculate:
hits / (hits + misses)
-
Token failures:
po_token_failures_total{token_type="player", reason="forbidden_error"} po_token_failures_total{token_type="gvs", reason="timeout"}- Track failure reasons to identify patterns
- High
forbidden_error= token validity issue - High
timeout= provider performance issue
-
Circuit breaker:
ytdlp_circuit_breaker_state # 0=closed, 1=open, 2=half-open ytdlp_circuit_breaker_opens_total ytdlp_circuit_breaker_closes_total- Alert if state = 1 (open) for >5 minutes
- Track open/close ratio (should trend toward more closes)
-
Provider performance:
po_token_retrieval_latency_seconds{provider="manual"} po_token_retrieval_latency_seconds{provider="http"}- Goal: p95 < 1s for HTTP provider
- Alert if p95 > 5s
Grafana query examples:
# Token success rate (last 1h)
sum(rate(po_token_retrievals_total{result="success"}[1h])) /
sum(rate(po_token_retrievals_total[1h]))
# Cache hit rate (check both metrics exist before calculating)
sum(po_token_cache_hits_total) /
(sum(po_token_cache_hits_total) + sum(po_token_cache_misses_total))
# Circuit breaker open duration
sum(increase(ytdlp_circuit_breaker_opens_total[1h])) by (component)
# Token failure rate by reason
sum(rate(po_token_failures_total[5m])) by (reason)
Use feature flags to enable/disable token usage for specific operations:
# Use PO tokens for audio downloads (Player/GVS)
PO_TOKEN_USE_FOR_AUDIO=true
# Use PO tokens for caption fetching (Subs)
PO_TOKEN_USE_FOR_CAPTIONS=trueUse cases:
- Gradual rollout: Enable for captions first, then audio
- A/B testing: Compare success rates with/without tokens
- Emergency disable: Turn off if tokens cause issues
- Selective usage: Use tokens only for operations that need them
Metrics by feature flag:
# Audio downloads with/without tokens
curl -s http://localhost:8001/metrics | \
grep 'ytdlp_token_usage_total{operation="audio"}'
# Caption fetches with/without tokens
curl -s http://localhost:8001/metrics | \
grep 'ytdlp_token_usage_total{operation="captions"}'Understanding cooldown mechanics:
- Trigger: Token marked invalid due to 403 or token-related error
- Duration:
PO_TOKEN_COOLDOWN_SECONDS(default: 60s) - Behavior:
- Token won't be returned from cache during cooldown
- Provider will be consulted after cooldown expires
- Other token types not affected (PLAYER cooldown doesn't affect GVS)
Cooldown tuning:
-
Short cooldown (30s): Fast recovery, higher provider load
PO_TOKEN_COOLDOWN_SECONDS=30
Use when: Tokens are valid but occasionally marked incorrectly
-
Medium cooldown (60s): Balanced (default)
PO_TOKEN_COOLDOWN_SECONDS=60
Use when: Standard operation
-
Long cooldown (300s): Slow recovery, lower provider load
PO_TOKEN_COOLDOWN_SECONDS=300
Use when: Provider is expensive or rate-limited
Monitor cooldown events:
# Count cooldown events per hour
docker compose logs worker --since 1h | grep "entering cooldown" | wc -l
# Average cooldown duration
docker compose logs worker | \
grep -E "entering cooldown|exiting cooldown" | \
# Parse timestamps and calculate durations
awk '{print $1, $2, $NF}'Video download fails with 403
│
├─> Check: Are PO tokens configured?
│ ├─> NO → Configure PO_TOKEN_PLAYER and PO_TOKEN_GVS
│ └─> YES → Continue
│
├─> Check: Are tokens being used?
│ │ (Look for "PO tokens added" in logs)
│ ├─> NO → Check PO_TOKEN_USE_FOR_AUDIO=true
│ └─> YES → Continue
│
├─> Check: Are tokens in cooldown?
│ │ (Look for "cooldown" in logs)
│ ├─> YES → Wait for cooldown or investigate why tokens fail
│ └─> NO → Continue
│
├─> Check: Is circuit breaker open?
│ │ (Look for "circuit breaker open")
│ ├─> YES → Wait for cooldown or fix underlying issue
│ └─> NO → Continue
│
├─> Check: Are tokens expired?
│ ├─> YES → Regenerate tokens (manual or provider)
│ └─> NO → Continue
│
└─> Check: Does client fallback work?
├─> YES → Success with fallback client
└─> NO → Check cookies, circuit breaker threshold,
network connectivity, YouTube status
## Example Configurations
### Development (Manual Tokens)
```bash
PO_TOKEN_PLAYER=dev_player_token_abc123
PO_TOKEN_GVS=dev_gvs_token_def456
PO_TOKEN_SUBS=dev_subs_token_ghi789
PO_TOKEN_PROVIDER_ENABLED=false
PO_TOKEN_PLAYER=
PO_TOKEN_GVS=
PO_TOKEN_SUBS=
PO_TOKEN_PROVIDER_ENABLED=true
PO_TOKEN_PROVIDER_URL=http://token-service:8080
PO_TOKEN_PROVIDER_TIMEOUT=10.0
PO_TOKEN_CACHE_TTL=7200
PO_TOKEN_COOLDOWN_SECONDS=120PO_TOKEN_PLAYER=fallback_player_token
PO_TOKEN_GVS=
PO_TOKEN_SUBS=
PO_TOKEN_PROVIDER_ENABLED=true
PO_TOKEN_PROVIDER_URL=http://token-service:8080Manual tokens are tried first, provider is used as fallback.
Run the test suite:
# Run token manager tests
pytest tests/worker/test_po_token_manager.py -v
# Run provider tests
pytest tests/worker/test_po_token_providers.py -v
# Run all token-related tests
pytest tests/worker/test_po_token*.py -vPotential improvements:
- JavaScript runtime provider: Execute JS code to generate tokens
- Token rotation: Proactive token refresh before expiration
- Regional tokens: Different tokens per geographic region
- Token validation: Pre-check token validity before use
- Distributed cache: Redis-backed cache for multi-worker deployments
- Rate limiting: Limit provider call frequency
- Token pooling: Maintain multiple valid tokens per type
- YouTube PO Token Documentation
- yt-dlp Extractor Arguments
- Worker implementation:
worker/po_token_manager.py - Providers:
worker/po_token_providers.py - Integration:
worker/audio.py