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 resolves the liveChatId of newly discovered broadcasts, and is also 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
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.

Broadcast Discovery (Quota-Free)​

The InnerTube browse request reads a channel's public Streams tab. Current entries use lockupViewModel: a …_LIVE badge style identifies a live broadcast, while badge text Upcoming identifies a scheduled one. The request pins hl=en so the badge text is stable and sets timeZone=UTC and utcOffsetMinutes=0 so the Scheduled for … time can be parsed as UTC. A legacy videoRenderer parser remains as a fallback. InnerTube returns the video ID, title, and available status metadata, but no liveChatId.

Only public streams appear in this discovery path. Unlisted and private broadcasts are not discovered through the public Streams tab.

liveChatId Resolution (Data API v3)​

AspectDetails
EndpointliveBroadcasts.list?part=snippet,status&id=…&maxResults=50
Cost1 unit per call, with up to 50 video IDs per call
FrequencyOn discovery when a broadcast has no known chat ID; unresolved IDs are retried after at least 10 minutes
Sourcecrates/lo-youtube-api/src/client.rs get_broadcasts_by_id() and apps/api/src/workers/youtube.rs

The worker reuses known chat IDs from tracked streams or channel_status. For an unknown ID, it calls the Data API with the connected channel's OAuth token. snippet.scheduledStartTime, when returned, takes precedence over the time parsed from the Streams tab. An upcoming broadcast without a chat ID still appears on the dashboard, but it has no chat reader task and does not appear in the Multichat broadcast dropdown until the ID resolves. A live broadcast without a chat ID waits for resolution. A failed lookup leaves broadcasts with known IDs available; quota errors pause further lookups for five minutes, and other API errors for one discovery interval (60 seconds).

The live-chat embed page is still used to bootstrap an InnerTube chat continuation token after a chat ID is known. It is not the source of the liveChatId.

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)​

Broadcast discovery through the Data API runs when rest_fallback_enabled = true and InnerTube browse fails. REST chat polling is the last transport after the enabled InnerTube and gRPC tiers fail:

OperationEndpointCostInterval
Broadcast discoveryliveBroadcasts.listAt least 2 (one page each for active and upcoming)Every 60 s; more for additional pages
Chat pollingliveChatMessages.list5~10 s active / 30 s idle
CallerFileDaily Units (per channel, one page per broadcast status)
YouTube Worker (broadcast poll)apps/api/src/workers/youtube.rsAt least 2,880
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 (liveBroadcasts.list?id=…)1 per call1 call for up to 50 new IDs; unresolved IDs retry after at least 10 min1 for one resolved broadcast
Total for one resolved broadcast1

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): 1 unit
─────────
Total: 1 unit / 10,000 limit

This example assumes the chat ID resolves on the first call. A broadcast without a chat ID may cost another unit on each retry after at least 10 minutes. Human actions also consume quota - sending a chat message (200), a ban/timeout (200), or a message delete (200) - as does the one-off channels.list at connection time (1).

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

Idle hours (16h): 16 * 120 = 1,920 units (active + upcoming discovery)
Stream hours (8h): 8 * 1920 = 15,360 units (discovery + chat polling)
─────────
Total: 17,280 units / 10,000 limit (173% -- OVER QUOTA)

This assumes one page for each broadcast status and a chat poll every 10 seconds. The fallback listing supplies the chat ID, so this continuous-fallback example has no separate ID lookup. 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