Skip to main content

YouTube API Quota

Lumio uses InnerTube — YouTube's own internal API — as the primary transport for chat reception and broadcast discovery. InnerTube costs 0 Data API quota. The YouTube Data API v3 is used for chat sending, moderation, channel-info lookup at connection time, and the disabled-by-default REST polling fallback.

The YouTube Data API v3 enforces a daily quota of 10,000 units per project (resets at midnight Pacific Time).

Transport Overview

TransportPurposeQuota Cost
InnerTube (primary)Chat polling, broadcast discovery, badge/emote enrichment, statistics (likes, views)0
gRPC streamList (fallback 1)Chat reception via persistent server-push connection0
REST Data API v3 (fallback 2)Chat polling when InnerTube and gRPC both fail5 units per poll

Fallback cascade: InnerTube → gRPC (if grpc_fallback_enabled) → REST (if rest_fallback_enabled). Each transition triggers after 3 consecutive failures within 60 seconds. Both fallbacks are disabled by default.

Quota Cost Reference

Official costs per YouTube Data API v3 operation:

OperationEndpointCost (units)
List broadcastsliveBroadcasts.list1
List chat messagesliveChatMessages.list5
Send chat messageliveChatMessages.insert200
List channelschannels.list1
List videosvideos.list1
Searchsearch.list100
List subscriptionssubscriptions.list1
Transition broadcastliveBroadcasts.transition50

The default daily limit is 10,000 units. Higher limits require passing Google's Quota and Compliance Audit.

Quota-Free Operations (InnerTube + gRPC)

These operations cost 0 Data API quota:

OperationTransportSource
Chat pollingInnerTube get_live_chatcrates/lo-youtube-api/src/innertube/mod.rs
Broadcast discoveryInnerTube browsecrates/lo-youtube-api/src/innertube/browse.rs
liveChatId resolutionLive-chat embed page scrapecrates/lo-youtube-api/src/innertube/scraper.rs
Badge + emote enrichmentInnerTube (inline in chat responses)crates/lo-youtube-api/src/innertube/parser.rs
Like count + view countInnerTube updated_metadata / playercrates/lo-youtube-api/src/innertube/browse.rs
Like count + view count (fallback)Public channel Atom feed videos.xml (media:community)crates/lo-youtube-api/src/webhook.rs
Chat streaming (fallback)gRPC liveChatMessages.streamListcrates/lo-youtube-api/src/streaming.rs

InnerTube is unauthenticated — no OAuth token required for chat reception. The channel Atom feed is likewise unauthenticated, but is edge-cached for 15 minutes, so it is fetched at most once per channel per 15 minutes and only for values InnerTube did not deliver. See Broadcast Statistics.

liveChatId Resolution (Quota-Free)

AspectDetails
TransportLive-chat embed page scrape (fetch_live_chat_embed_page)
Cost0 — no Data API call
FrequencyOnce per newly discovered broadcast, cached in channel_status
Sourcecrates/lo-youtube-api/src/innertube/browse.rs resolve_broadcast_chat_ids()

InnerTube browse returns broadcast IDs but not liveChatId. resolve_broadcast_chat_ids() fetches each broadcast's live-chat embed page and extracts the id with extract_live_chat_id_from_html() — no liveBroadcasts.list call. Broadcasts whose chat id cannot be resolved (for example an upcoming stream with no chat room yet) are skipped rather than failing the poll.

Quota-Consuming Operations (Data API v3)

Chat Sending + Moderation

OperationEndpointCostFrequencySource
Send chat messagePOST /liveChat/messages200Per message sentapps/api/src/services/chat_send.rs
Ban / timeout userPOST /liveChat/bans200Per actionapps/api/src/services/moderation.rs
Delete messageDELETE /liveChat/messages200Per actionapps/api/src/services/moderation.rs

These use the account's Google login OAuth connection (db::auth::get_account_provider_token), not the channel connection.

Channel Info Lookup

AspectDetails
Endpointchannels.list
Cost1 unit
Sourceapps/api/src/routes/connections.rs fetch_channel_info()
TriggerOnce per YouTube channel connection (OAuth callback)
Daily UnitsNegligible (< 10)

REST Fallback (Disabled by Default)

Only active when both rest_fallback_enabled = true and InnerTube + gRPC have failed:

OperationEndpointCostInterval
Broadcast discoveryliveBroadcasts.list1Every 60 s
Chat pollingliveChatMessages.list5~10 s active / 30 s idle
CallerFileDaily Units (per channel, worst case)
YouTube Worker (broadcast poll)apps/api/src/workers/youtube.rs1,440
YouTube Worker (chat poll)apps/api/src/workers/youtube.rsup to 43,200

Configuration

[youtube]
# Enable gRPC streamList as first fallback when InnerTube fails
# ENV: LUMIO__YOUTUBE__GRPC_FALLBACK_ENABLED
grpc_fallback_enabled = false # default

# Enable REST polling as last resort when InnerTube + gRPC both fail
# Also gates Data API broadcast discovery fallback
# ENV: LUMIO__YOUTUBE__REST_FALLBACK_ENABLED
rest_fallback_enabled = false # default

Daily Quota Calculator

Default Mode (InnerTube Only)

With both fallbacks disabled (default), quota consumption is minimal:

Per active channel (stream is live):

ComponentQuota per callCallsQuota
InnerTube chat polling0~28,800/day (3 s interval)0
InnerTube broadcast discovery01,440/day (60 s interval)0
InnerTube statistics01,440/day (60 s interval)0
liveChatId resolution (embed-page scrape)01 per broadcast (cached)0
Total0

Per idle channel (no stream):

ComponentQuota per callCallsQuota
InnerTube broadcast discovery01,440/day0
Total0

Example: 1 Channel, 8h Stream (Default InnerTube Mode)

Idle hours (16h): 0 units
Stream hours (8h): 0 units
liveChatId resolution (once): 0 units
─────────
Total: 0 units / 10,000 limit

The only Data API spend in default mode comes from actions a human takes — sending a chat message (200), a ban/timeout (200), a message delete (200) — plus the one-off channels.list at connection time (1).

Example: 1 Channel, 8h Stream (REST Fallback Active)

Idle hours (16h): 16 * 60 = 960 units (broadcast poll)
Stream hours (8h): 8 * 1860 = 14,880 units (broadcast + chat poll)
─────────
Total: 15,840 units / 10,000 limit (158% -- OVER QUOTA)

REST fallback is disabled by default for this reason.

Quota Safeguards

MechanismDetails
InnerTube primary0 quota for all chat reception and broadcast discovery
DetectionHTTP 403 (REST) or RESOURCE_EXHAUSTED (gRPC)
Backoff5-minute pause on quota exhaustion (QUOTA_BACKOFF_SECS = 300)
Fallback chainInnerTube → gRPC → REST (each after 3 failures in 60 s)
REST togglerest_fallback_enabled = false disables REST fallback entirely
Shared cacheCopyright Worker and Bot read broadcast status from Redis, not YouTube API
Adaptive pollingREST fallback respects polling_interval_millis from YouTube's response

Requesting Higher Quota

To request a quota increase from Google:

  1. Pass the Quota and Compliance Audit
  2. Demonstrate compliance with YouTube API Terms of Service
  3. If previously audited within 12 months, use the Audited Developer Requests Form
  4. There is no published maximum — increases are granted case-by-case