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.

The Comments API fetches YouTube video comments along with their reply threads. All methods are suspend functions on the YouTube singleton and return Result<T>.
Comments use the WEB client (not WEB_REMIX), which enables the nested reply mechanism that YouTube Music’s internal client intentionally disables. This allows commentReplies() to retrieve full reply threads.

YouTube.comments()

Fetches the first page of comment threads for a video.
suspend fun comments(videoId: String): Result<Pair<List<CommentThreadRenderer>, String?>>
videoId
String
required
The YouTube video ID (e.g. "dQw4w9WgXcQ").

How it works

  1. Fires a next request with the WEB client to obtain the initial comment continuation token.
  2. The implementation searches three locations for this token in order of priority:
    • Direct continuationItemRenderer in the twoColumnWatchNextResults content list
    • continuationItemRenderer inside itemSectionRenderer contents
    • Fallback: engagementPanels (YouTube Music-style, no nested replies)
  3. Immediately calls commentContinuation() with that token and returns the result, so the first call returns actual comments rather than just a token.

Returns

Result<Pair<List<CommentThreadRenderer>, String?>> — a pair of:
first
List<CommentThreadRenderer>
First page of comment threads. See CommentThreadRenderer fields below.
second
String?
Continuation token for commentContinuation(). null when there are no more comments.

Example

import com.music.innertube.YouTube

val (comments, nextToken) = YouTube.comments("dQw4w9WgXcQ").getOrThrow()
comments.forEach { thread ->
    val renderer = thread.comment?.commentRenderer
    println("${renderer?.authorText?.runs?.firstOrNull()?.text}: ${renderer?.contentText?.runs?.firstOrNull()?.text}")
    println("  Likes: ${renderer?.voteCount?.runs?.firstOrNull()?.text}  Replies: ${renderer?.replyCount}")
}
println("Next token: $nextToken")

YouTube.commentContinuation()

Fetches the next page of comment threads using a continuation token.
suspend fun commentContinuation(
    continuationToken: String,
): Result<Pair<List<CommentThreadRenderer>, String?>>
continuationToken
String
required
The continuation token from a previous comments() or commentContinuation() call.

Merge strategy

The implementation merges results from two comment models to maximise accuracy:
  1. Legacy model — standard commentThreadRenderer entries from continuationItems. These carry replies (reply thread containers).
  2. Framework modelcommentEntityPayload entries from frameworkUpdates.entityBatchUpdate.mutations. These carry the canonical text content and up-to-date like/vote state that YouTube has migrated away from the legacy model.
The merged result prefers the framework model per entry (since it has the correct text and vote counts) but injects the legacy model’s replies field so that reply tokens are preserved.

Returns

Result<Pair<List<CommentThreadRenderer>, String?>> — same shape as comments().

YouTube.commentReplies()

Fetches replies for a comment thread.
suspend fun commentReplies(
    replyToken: String,
): Result<Pair<List<CommentRenderer>, String?>>
replyToken
String
required
The reply continuation token. Obtain from CommentThreadRenderer.replies.commentRepliesRenderer.contents[].continuationItemRenderer.continuationEndpoint.continuationCommand.token, typically surfaced as the token behind a “View X replies” button.

Returns

Result<Pair<List<CommentRenderer>, String?>> — a pair of:
first
List<CommentRenderer>
Reply CommentRenderer objects. Legacy and framework models are merged identically to commentContinuation(). Framework vote counts override legacy counts; legacy replyCount is preserved when present in both.
second
String?
Continuation token for loading more replies. null on the last page.

CommentThreadRenderer fields

CommentThreadRenderer is the top-level wrapper for a single comment thread.
data class CommentThreadRenderer(
    val comment: Comment?,
    val commentViewModel: CommentViewModelWrapper?,
    val replies: Replies?,
)
comment
Comment?
Legacy comment container.
commentViewModel
CommentViewModelWrapper?
Framework-based alternative comment model.
replies
Replies?
Reply thread container.

CommentRenderer fields

CommentRenderer holds the content for a single comment or reply.
data class CommentRenderer(
    val authorText: Runs?,
    val authorThumbnail: Thumbnails?,
    val contentText: Runs?,
    val publishedTimeText: Runs?,
    val authorEndpoint: NavigationEndpoint?,
    val commentId: String?,
    val voteCount: Runs?,
    val voteStatus: String?,
    val replyCount: Int?,
)
authorText
Runs?
Author display name. Read the first Run.text for the plain string.
authorThumbnail
Thumbnails?
Author avatar. The list of Thumbnail objects contains URLs at ascending resolutions; the last entry is highest quality.
contentText
Runs?
Comment body. Each Run may contain a plain text segment or a linked text segment. Join all Run.text values for the full string.
publishedTimeText
Runs?
Relative publish time string (e.g. "2 days ago", "3 years ago"). Read runs.firstOrNull()?.text.
authorEndpoint
NavigationEndpoint?
Navigation endpoint to the author’s channel.
commentId
String?
Unique comment identifier. Used to deduplicate merged legacy/framework entries.
voteCount
Runs?
Like count as a formatted string (e.g. "1.2K", "0"). Read runs.firstOrNull()?.text.
voteStatus
String?
Current user’s vote state. One of:
ValueMeaning
"UPVOTE"The authenticated user has liked this comment
"INDIFFERENT"Not liked (default)
replyCount
Int?
Number of replies to this comment. null for reply objects themselves.

Fetching all comments with pagination

import com.music.innertube.YouTube

suspend fun fetchAllComments(videoId: String) {
    // First page — comments() calls commentContinuation() internally
    var (comments, nextToken) = YouTube.comments(videoId).getOrThrow()

    // Paginate until there are no more pages
    while (nextToken != null) {
        val (more, token) = YouTube.commentContinuation(nextToken).getOrThrow()
        comments = comments + more
        nextToken = token
    }

    println("Total comments fetched: ${comments.size}")

    // Print comments and fetch replies for any thread that has them
    comments.forEach { thread ->
        val renderer = thread.comment?.commentRenderer ?: return@forEach
        println("${renderer.authorText?.runs?.firstOrNull()?.text}: " +
                "${renderer.contentText?.runs?.joinToString("") { it.text }}")

        // Load replies if available
        val replyToken = thread.replies
            ?.commentRepliesRenderer
            ?.contents
            ?.firstOrNull()
            ?.continuationItemRenderer
            ?.continuationEndpoint
            ?.continuationCommand
            ?.token

        if (replyToken != null) {
            var (replies, nextReplyToken) = YouTube.commentReplies(replyToken).getOrThrow()
            while (nextReplyToken != null) {
                val (moreReplies, t) = YouTube.commentReplies(nextReplyToken).getOrThrow()
                replies = replies + moreReplies
                nextReplyToken = t
            }
            replies.forEach { reply ->
                println("  ↳ ${reply.authorText?.runs?.firstOrNull()?.text}: " +
                        "${reply.contentText?.runs?.joinToString("") { it.text }}")
            }
        }
    }
}

Build docs developers (and LLMs) love