Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/faraasaaay/innertube-v1/llms.txt

Use this file to discover all available pages before exploring further.

Every InnerTube request is sent on behalf of a specific YouTube client identity. The server uses the client identity to decide which stream formats to provide, whether the request is allowed, and what DRM or age-restriction rules to apply. InnerTube models this with the YouTubeClient data class and resolves the best client for a given piece of content using ContentAwareFallbackStrategy.

YouTubeClient Data Class

YouTubeClient is a serializable data class in com.metrolist.innertube.models. Each instance represents a specific YouTube app/platform combination, and is sent inside the context.client field of every InnerTube request body.
@Serializable
data class YouTubeClient(
    val clientName: String,        // e.g. "WEB_REMIX", "ANDROID_VR"
    val clientVersion: String,     // e.g. "1.20260114.03.00"
    val clientId: String,          // numeric client ID as a string, e.g. "67"
    val userAgent: String,         // HTTP User-Agent header value
    val osName: String? = null,
    val osVersion: String? = null,
    val deviceMake: String? = null,
    val deviceModel: String? = null,
    val androidSdkVersion: String? = null,
    val buildId: String? = null,
    val cronetVersion: String? = null,
    val packageName: String? = null,
    val friendlyName: String? = null,
    // --- Capability flags ---
    val loginSupported: Boolean = false,
    val loginRequired: Boolean = false,
    val useSignatureTimestamp: Boolean = false,
    val isEmbedded: Boolean = false,
    val useWebPoTokens: Boolean = false,
    val requirePoToken: Boolean = false,
    val includeUserAgentInContext: Boolean = false,
)

Capability Flags

loginSupported
Boolean
When true, the library will attach the Cookie and SAPISIDHASH Authorization headers when this client is used with setLogin = true. Clients without this flag will never send authentication headers, even if YouTube.cookie is set.
loginRequired
Boolean
When true, this client explicitly requires a logged-in session to function correctly (e.g. WEB_CREATOR).
useSignatureTimestamp
Boolean
When true, the player request will include a signatureTimestamp in the playbackContext. This is needed to correctly decrypt signed stream URLs.
isEmbedded
Boolean
When true, the request context includes a thirdParty.embedUrl field, spoofing an embedded player context. This can bypass certain age-restriction checks.
useWebPoTokens
Boolean
When true, a web-origin Proof-of-Origin Token (PoToken) is included in the player request’s serviceIntegrityDimensions if one is available.
requirePoToken
Boolean
When true, this client will not successfully return streams without a valid PoToken. Requests without one will typically receive throttled or empty stream URLs.
includeUserAgentInContext
Boolean
When true, the userAgent string is also embedded inside the context.client JSON body (in addition to the HTTP User-Agent header). Required for TV/VR clients.

Predefined Client Constants

All predefined clients are companion object constants on YouTubeClient. Import them with YouTubeClient.WEB_REMIX, YouTubeClient.TVHTML5, etc.

WEB_REMIX

clientId: 67 — The primary YouTube Music web client. Supports login, signature timestamps, and web PoTokens. Used by default for all search, browse, next, and feedback requests in YouTube.

WEB

clientId: 1 — Standard YouTube web client. Used for non-Music endpoints such as getMediaInfo().

WEB_CREATOR

clientId: 62 — YouTube Creator Studio web client. Login is both supported and required. Useful as a fallback for uploaded, live, and kids content.

TVHTML5

clientId: 7 — TV HTML5 client. Supports login and signature timestamps. Works well for uploaded songs, live streams, and kids content. Includes user-agent in context.

TVHTML5_SIMPLY

clientId: 75 — Simplified TV client. Requires a PoToken to return usable streams. Used as a last-resort fallback in default and kids flows.

TVHTML5_SIMPLY_EMBEDDED_PLAYER

clientId: 85 — Embedded player variant of TVHTML5 SIMPLY. The isEmbedded flag causes an embedUrl to be sent in the request context, which can bypass age-restrictions without requiring login.

IOS

clientId: 5 — iOS YouTube app client. Useful as a lightweight mobile client that typically doesn’t require PoToken.

MOBILE / ANDROID

clientId: 3 — Android YouTube app. Supports login and signature timestamps. General-purpose Android client.

ANDROID_NO_SDK

clientId: 3 — Android client with loginSupported = false and useSignatureTimestamp = false. Use this for paid, private, or age-restricted content where the standard Authorization header would cause the request to be rejected.

ANDROID_VR_1_65_10

clientId: 28 — Oculus Quest VR client version 1.65. Good general-purpose VR client with broad format support. Includes user-agent in context.

ANDROID_VR_1_61_48

clientId: 28 — Oculus Quest VR client version 1.61. Cannot play kids, paid, or age-restricted content. Logged-out only.

ANDROID_VR_1_43_32

clientId: 28 — Oculus Quest VR client version 1.43. Uses non-adaptive bitrate, which fixes audio stuttering with YouTube Music streams. Does not use AV1.

ANDROID_CREATOR

clientId: 14 — Android Creator Studio app. Supports login and signature timestamps. Cannot play live streams or HDR content, but can play videos labeled for children and music-tagged videos.

VISIONOS

clientId: 101 — Internal client for an unreleased Apple visionOS YouTube app. Currently produces very clean stream results without requiring PoToken. May stop working at any time as it is an unreleased, internal client.

IPADOS

clientId: 5 — iPad client using the iOS client name. AV1 hardware decoding is not supported on the target device (iPad 6th Gen). Login not supported.

ContentAwareFallbackStrategy

ContentAwareFallbackStrategy takes a ContentHints object describing the content being played and returns an ordered List<YouTubeClient> to try in sequence. The player logic should attempt each client in order, stopping at the first one that returns a usable stream.
data class ContentHints(
    val isExplicit: Boolean? = null,
    val isKidsContent: Boolean? = null,
    val isLive: Boolean? = null,
    val isUploaded: Boolean? = null,
)
Only one hint is evaluated at a time. The priority order is: isUploadedisLiveisKidsContentisExplicit → default.

Fallback Lists

Used when none of the content hints are true. Optimised for standard YouTube Music tracks.
VISIONOS → ANDROID_VR_1_65_10 → ANDROID_VR_1_43_32 → WEB_REMIX → TVHTML5 → TVHTML5_SIMPLY
PriorityClientReason
1VISIONOSClean streams, no PoToken needed (while available)
2ANDROID_VR_1_65_10Reliable VR client, broad format support
3ANDROID_VR_1_43_32Non-adaptive bitrate fallback, no AV1
4WEB_REMIXOfficial Music web client
5TVHTML5TV client fallback
6TVHTML5_SIMPLYLast resort (requires PoToken)

Using ContentAwareFallbackStrategy

val strategy = ContentAwareFallbackStrategy()

// Determine content hints from your song/episode metadata
val hints = ContentHints(
    isExplicit = song.explicit,
    isLive = false,
    isUploaded = song.uploadEntityId != null,
    isKidsContent = false,
)

val clients = strategy.resolveClients(hints)

var playerResponse: PlayerResponse? = null
for (client in clients) {
    val result = runCatching {
        innerTube.player(
            client = client,
            videoId = song.id,
            playlistId = playlistId,
            signatureTimestamp = if (client.useSignatureTimestamp) currentSigTimestamp else null,
        ).body<PlayerResponse>()
    }
    val response = result.getOrNull() ?: continue
    // Check that the response actually contains usable stream URLs
    if (response.streamingData?.adaptiveFormats?.isNotEmpty() == true) {
        playerResponse = response
        break
    }
}

if (playerResponse == null) {
    error("No client could resolve streams for video ${song.id}")
}
Cache the signatureTimestamp — fetch it once per app session using YouTube.player() with WEB_REMIX and extract it from the player response. It changes infrequently and re-fetching it per-track adds unnecessary latency.

Build docs developers (and LLMs) love