Skip to main content

Documentation Index

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

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

Overview

Every request InnerTube sends to YouTube’s API includes a client context — a JSON object that tells YouTube which app is making the request. YouTubeClient is a @Serializable data class that represents this identity and drives the HTTP headers that accompany every call.
@Serializable
data class YouTubeClient(
    val clientName: String,
    val clientVersion: String,
    val clientId: String,
    val userAgent: String,
    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,
    val loginSupported: Boolean = false,
    val loginRequired: Boolean = false,
    val useSignatureTimestamp: Boolean = false,
    val isEmbedded: Boolean = false,
    val useWebPoTokens: Boolean = false,
)

Key Fields

FieldDescription
clientNameThe X-YouTube-Client-Name header and context.client.clientName body field.
clientVersionThe X-YouTube-Client-Version header and context.client.clientVersion body field.
clientIdNumeric ID sent in the X-YouTube-Client-Name header (YouTube uses the numeric ID in that header despite its name).
userAgentThe User-Agent HTTP header, mimicking the real app or browser.
loginSupportedWhen true, authentication cookies and SAPISIDHASH are attached to requests for this client.
loginRequiredWhen true, the client only works when the user is signed in.
useSignatureTimestampWhen true, InnerTube fetches a signature timestamp (via NewPipe) and includes it in the PlayerBody.PlaybackContext. Required for WEB-based stream decryption.
isEmbeddedWhen true, the request body includes a thirdParty.embedUrl — allows bypassing some age restrictions.
useWebPoTokensWhen true, a PoToken must be generated and appended to the stream URL. Required for WEB and WEB_REMIX stream resolution.

toContext() — Building the Request Body

YouTubeClient.toContext() produces the Context object that is embedded in every API request body:
fun toContext(locale: YouTubeLocale, visitorData: String?, dataSyncId: String?): Context
The resulting Context includes:
  • client — clientName, clientVersion, OS info, device info, locale (gl/hl), visitorData
  • user.onBehalfOfUser — set to dataSyncId only when loginSupported = true
  • requestuseSsl: true (always)
InnerTube calls toContext() automatically when building each request body. You do not need to call it manually.

Named Client Constants

All predefined clients are available as constants in YouTubeClient.Companion. Select the right one for your use case:

Client Reference Table

ConstantloginSupporteduseWebPoTokensNotes
WEBStandard YouTube web client. Used for comments(), transcript(), and next().
WEB_REMIXYouTube Music web client. The default for almost all music browse and search calls. Requires signature timestamp for playback.
WEB_CREATOR✅ (loginRequired)YouTube Studio client. Used for age-restricted content when the user is signed in.
TVHTML5✅ (loginRequired)Smart TV client. Supports PoTokens. Used in stream fallback chain.
TVHTML5_SIMPLY_EMBEDDED_PLAYEREmbedded player (isEmbedded = true). Can bypass age restrictions without login. First fallback for age-restricted streams.
IOSiOS YouTube app. No login support.
IPADOSiPadOS YouTube app. iPad 6th Gen device model. No AV1 hardware decoding.
MOBILEAndroid YouTube app (ANDROID client name). Login and signature timestamp supported.
ANDROID_NO_SDKAndroid client without SDK context. No auth, no signature. Cannot play paid, private, or age-restricted content.
ANDROID_VR_NO_AUTHAndroid VR v1.61 without auth headers. No login, no signature.
ANDROID_VR_1_61_48Android VR v1.61 with full device context (Oculus Quest 3). Can play most content. Not usable when logged in.
ANDROID_VR_1_43_32Android VR v1.43. Uses non-adaptive bitrate (fixes audio stuttering). Does not use AV1. Primary client for audio playback.
ANDROID_CREATORYouTube Creator app. Can play videos for children and with music; cannot play live streams or HDR.
VISIONOSInternal visionOS client (clientId = 101). Experimental — may stop working at any time.
ANDROID_MUSICAndroid YouTube Music app. Login and signature timestamp supported.
IOS_MUSICiOS YouTube Music app. Login and signature timestamp supported.

Passing a Client to YouTube.player()

Most high-level YouTube.* methods choose their client internally. The player() method is the primary place where you explicitly pass a YouTubeClient:
suspend fun player(
    videoId: String,
    playlistId: String? = null,
    client: YouTubeClient,
    signatureTimestamp: Int? = null,
    poToken: String? = null,
): Result<PlayerResponse>
Example — resolving a stream with the primary Android VR client:
viewModelScope.launch {
    val result = YouTube.player(
        videoId = "dQw4w9WgXcQ",
        playlistId = null,
        client = YouTubeClient.ANDROID_VR_1_43_32,
        signatureTimestamp = null, // not needed for ANDROID_VR
        poToken = null,            // not needed for ANDROID_VR
    )

    result.onSuccess { response ->
        if (response.playabilityStatus.status == "OK") {
            val streamUrl = response.streamingData?.adaptiveFormats
                ?.filter { it.isAudio }
                ?.maxByOrNull { it.bitrate }
                ?.url
            println("Stream URL: $streamUrl")
        }
    }
}

Multi-Client Fallback Strategy

In practice, no single client can play every video. YTPlayerUtils implements a robust waterfall strategy: Primary client (fast path):
MAIN_CLIENT = ANDROID_VR_1_43_32
ANDROID_VR_1_43_32 is tried first for every video. It does not require a PoToken and resolves streams with minimal latency. Its non-adaptive bitrate also eliminates an audio-stuttering issue present in newer VR client versions. Fallback chain (on failure):
val STREAM_FALLBACK_CLIENTS: Array<YouTubeClient> = arrayOf(
    ANDROID_VR_1_61_48,
    WEB_REMIX,
    TVHTML5_SIMPLY_EMBEDDED_PLAYER,   // age-restricted bypass (no login needed)
    TVHTML5,
    ANDROID_CREATOR,
    IPADOS,
    ANDROID_VR_NO_AUTH,
    MOBILE,
    IOS,
    WEB,
    WEB_CREATOR,                       // age-restricted bypass (login required)
)
The fallback logic:
  1. If the primary client’s playabilityStatus.status is not "OK", the next client in the array is tried.
  2. Clients with loginRequired = true are skipped automatically when the user is not signed in.
  3. PoTokens are generated lazily — only when a web client (with useWebPoTokens = true) is actually reached in the chain.
  4. For age-restricted content, TVHTML5_SIMPLY_EMBEDDED_PLAYER is tried before login-gated clients.
  5. Stream URLs from web clients are processed through an n-transform deobfuscation step before being validated with a lightweight HEAD request.
Client identities depend on specific clientVersion strings. If YouTube rolls out a server-side change that invalidates a client version, that client will start returning non-OK playability statuses or HTTP errors. Monitor the fallback chain in production and update version strings when breakage is detected. The VISIONOS client (clientId = 101) is explicitly marked as experimental and may stop working at any time.

Build docs developers (and LLMs) love