YouTube API Quota
The YouTube Data API v3 enforces a daily quota of 10,000 units per project (resets at midnight Pacific Time). Lumio's backend makes several types of YouTube API calls through the channel connection credentials (app_credentials table). This page documents every call, its cost, and the estimated daily consumption.
Quota Cost Reference
Official costs per YouTube Data API v3 operation:
| Operation | Endpoint | Cost (units) |
|---|---|---|
| List broadcasts | liveBroadcasts.list | 1 |
| List chat messages | liveChatMessages.list | 5 |
| Send chat message | liveChatMessages.insert | 200 |
| List channels | channels.list | 1 |
| List videos | videos.list | 1 |
| Search | search.list | 100 |
| List subscriptions | subscriptions.list | 1 |
| Transition broadcast | liveBroadcasts.transition | 50 |
The default daily limit is 10,000 units. Higher limits require passing Google's Quota and Compliance Audit.
Lumio API Calls
Quota-Consuming Calls (YouTube Data API v3)
These calls count against the daily quota:
Broadcast Discovery
| Aspect | Details |
|---|---|
| Endpoint | liveBroadcasts.list |
| Cost | 1 unit |
| Source | crates/lo-youtube-api/src/client.rs get_live_broadcasts() |
Only the YouTube Polling Worker makes this API call. The Copyright Worker and YouTube Bot read from the shared Redis cache instead (zero quota).
| Caller | File | Interval | Daily Units (per channel) |
|---|---|---|---|
| YouTube Polling Worker | apps/api/src/workers/youtube.rs | 60s | 1,440 |
| Copyright Detection Worker | reads from Redis cache | -- | 0 |
| YouTube Bot (channel sync) | reads from Redis cache | -- | 0 |
The YouTube Worker stores active broadcasts in Redis at lumio:youtube:active_streams:\{account_id\} (TTL 120s). Both the Copyright Worker and YouTube Bot consume this cache instead of making independent API calls.
Chat Message Polling (REST Fallback)
| Aspect | Details |
|---|---|
| Endpoint | liveChatMessages.list |
| Cost | 5 units |
| Source | crates/lo-youtube-api/src/client.rs get_live_chat_messages() |
Only used when gRPC streaming is unavailable (after 3+ consecutive failures). Can be disabled entirely via configuration (see below).
| Caller | File | Interval | Daily Units (per channel) |
|---|---|---|---|
| YouTube Worker (REST fallback) | apps/api/src/workers/youtube.rs | 10s active / 30s idle | up to 43,200 (worst case) |
| YouTube Bot (chat poll) | apps/youtube-bot/src/bot.rs | 10s (configurable) | up to 43,200 (worst case) |
In practice the worker uses gRPC streaming (zero quota), so REST fallback rarely fires.
Channel Info Lookup
| Aspect | Details |
|---|---|
| Endpoint | channels.list |
| Cost | 1 unit |
| Source | apps/api/src/routes/connections.rs fetch_channel_info() |
| Trigger | Once per YouTube channel connection (OAuth callback) |
| Daily Units | Negligible (< 10) |
Quota-Free Calls
These do not count against the YouTube Data API quota:
| Component | API | Purpose |
|---|---|---|
| gRPC Chat Streaming | liveChatMessages.streamList (gRPC) | Primary chat ingestion mode. Persistent server-push connection, zero quota. |
| InnerTube Embed Scrape | youtube.com/live_chat (HTTP) | Extracts InnerTube API key and continuation token for badge harvesting. |
| InnerTube Chat Poll | youtubei/v1/live_chat/get_live_chat (HTTP) | Undocumented internal API for member badge image URLs. |
Configuration
REST Fallback Toggle
The REST polling fallback can be disabled to save quota when gRPC is the only desired chat ingestion mode:
[youtube]
# Set to false for gRPC-only mode. Chat ingestion stops when gRPC is unavailable.
# ENV: LUMIO__YOUTUBE__REST_FALLBACK_ENABLED
rest_fallback_enabled = true # default
When disabled:
- gRPC failures do not trigger REST polling fallback
- The worker retries gRPC instead of switching to REST
- Quota from
liveChatMessages.listdrops to zero
Daily Quota Calculator
Use this table to estimate daily quota consumption per account.
Per active channel (stream is live):
| Component | Quota per call | Calls/hour | Quota/hour | Quota/day (8h stream) |
|---|---|---|---|---|
| YouTube Worker (broadcast poll) | 1 | 60 | 60 | 480 |
| Copyright Worker | 0 (Redis) | -- | 0 | 0 |
| Bot (channel sync) | 0 (Redis) | -- | 0 | 0 |
| Chat (gRPC -- primary) | 0 | -- | 0 | 0 |
| Chat (REST -- fallback only) | 5 | 360 | 1,800 | 14,400 |
| Subtotal (gRPC mode) | 60 | 480 | ||
| Subtotal (REST fallback) | 1,860 | 14,880 |
Per idle channel (no stream):
| Component | Quota per call | Calls/hour | Quota/hour | Quota/day |
|---|---|---|---|---|
| YouTube Worker (broadcast poll) | 1 | 60 | 60 | 1,440 |
| Copyright Worker | 0 (Redis) | -- | 0 | 0 |
| Bot (channel sync) | 0 (Redis) | -- | 0 | 0 |
| Subtotal | 60 | 1,440 |
Example: 1 Channel, 8h Stream (gRPC mode)
Idle hours (16h): 16 * 60 = 960 units
Stream hours (8h): 8 * 60 = 480 units
─────────
Total: 1,440 units / 10,000 limit (14%)
Example: 1 Channel, 8h Stream (REST fallback)
Idle hours (16h): 16 * 60 = 960 units
Stream hours (8h): 8 * 1860 = 14,880 units
─────────
Total: 15,840 units / 10,000 limit (158% -- OVER QUOTA)
Quota Safeguards
The backend handles quota exhaustion gracefully:
| Mechanism | Details |
|---|---|
| Detection | HTTP 403 (REST) or RESOURCE_EXHAUSTED (gRPC) |
| Backoff | 5-minute pause on quota exhaustion (QUOTA_BACKOFF_SECS = 300) |
| gRPC priority | gRPC streaming is preferred over REST polling (zero quota) |
| Adaptive polling | REST fallback respects polling_interval_millis from YouTube's response |
| Fallback chain | gRPC fails 3x in 60s -> REST polling -> re-attempt gRPC after recovery |
| REST toggle | rest_fallback_enabled = false disables REST fallback entirely |
| Shared cache | Copyright Worker and Bot read broadcast status from Redis, not YouTube API |
Requesting Higher Quota
To request a quota increase from Google:
- Pass the Quota and Compliance Audit
- Demonstrate compliance with YouTube API Terms of Service
- If previously audited within 12 months, use the Audited Developer Requests Form
- There is no published maximum -- increases are granted case-by-case