A YouTube Comments API permite que desenvolvedores construam ferramentas poderosas de engajamento que buscam, publicam e moderam comentários programaticamente. Seja construindo um painel de mídia social, uma ferramenta de gerenciamento de comunidade ou um sistema de resposta automatizado, entender os recursos de comentários da YouTube Data API é essencial para criar integrações eficazes.
Este guia cobre tudo o que você precisa para implementar um gerenciamento robusto de comentários do YouTube em suas aplicações, desde a configuração de autenticação até estratégias de otimização de quota que mantêm sua integração funcionando sem problemas.

Introdução à YouTube Data API v3
A YouTube Data API v3 fornece acesso programático aos recursos principais do YouTube, incluindo uploads de vídeos, gerenciamento de playlists e operações de comentários. Para gerenciamento de comentários especificamente, a API expõe dois recursos principais:
- CommentThreads: Comentários de nível superior em vídeos, incluindo metadados sobre contagem de respostas
- Comments: Comentários individuais, incluindo respostas a comentários de nível superior
A API segue convenções RESTful e retorna respostas JSON. Todas as operações de comentários requerem autenticação OAuth 2.0, pois envolvem dados e ações específicas do usuário.
// Base configuration for YouTube API requests
const YOUTUBE_API_BASE = 'https://www.googleapis.com/youtube/v3';
interface YouTubeApiConfig {
accessToken: string;
baseUrl: string;
}
const config: YouTubeApiConfig = {
accessToken: process.env.YOUTUBE_ACCESS_TOKEN || '',
baseUrl: YOUTUBE_API_BASE,
};
API Key vs Autenticação OAuth 2.0
As operações de comentários do YouTube requerem diferentes níveis de autenticação dependendo da ação:
| Operação | API Key | OAuth 2.0 |
|---|---|---|
| Ler comentários públicos | ✅ | ✅ |
| Ler comentários em vídeos próprios | ❌ | ✅ |
| Publicar comentários | ❌ | ✅ |
| Responder a comentários | ❌ | ✅ |
| Excluir comentários | ❌ | ✅ |
| Moderar comentários | ❌ | ✅ |
Para qualquer operação de escrita ou acesso a dados privados, você deve usar OAuth 2.0. Veja como configurar o fluxo OAuth:
interface OAuthConfig {
clientId: string;
clientSecret: string;
redirectUri: string;
scopes: string[];
}
const oauthConfig: OAuthConfig = {
clientId: process.env.YOUTUBE_CLIENT_ID || '',
clientSecret: process.env.YOUTUBE_CLIENT_SECRET || '',
redirectUri: 'https://yourapp.com/auth/youtube/callback',
scopes: [
'https://www.googleapis.com/auth/youtube.force-ssl',
'https://www.googleapis.com/auth/youtube',
],
};
function getAuthUrl(state?: string): string {
const params = new URLSearchParams({
client_id: oauthConfig.clientId,
redirect_uri: oauthConfig.redirectUri,
scope: oauthConfig.scopes.join(' '),
response_type: 'code',
access_type: 'offline',
prompt: 'consent',
});
if (state) {
params.append('state', state);
}
return `https://accounts.google.com/o/oauth2/auth?${params.toString()}`;
}
async function exchangeCodeForToken(code: string): Promise<{
access_token: string;
refresh_token: string;
expires_in: number;
}> {
const response = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
code,
client_id: oauthConfig.clientId,
client_secret: oauthConfig.clientSecret,
redirect_uri: oauthConfig.redirectUri,
grant_type: 'authorization_code',
}),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Token exchange failed: ${response.status} ${errorText}`);
}
return response.json();
}
Nota: O escopo
youtube.force-sslé necessário para operações de comentários. Sem ele, requisições de escrita falharão com um erro 403.
Entendendo a Quota da YouTube API (10.000 Unidades/Dia)
O sistema de quota da YouTube API é um dos conceitos mais importantes para entender antes de construir qualquer integração. Cada requisição à API consome unidades de quota, e você está limitado a 10.000 unidades por dia por padrão.
Aqui está o detalhamento do custo de quota para operações relacionadas a comentários:
| Operação | Custo de Quota |
|---|---|
| commentThreads.list | 1 unidade |
| comments.list | 1 unidade |
| commentThreads.insert | 50 unidades |
| comments.insert (resposta) | 50 unidades |
| comments.delete | 50 unidades |
| comments.update | 50 unidades |
| comments.setModerationStatus | 50 unidades |
Com 10.000 unidades por dia, você pode realizar aproximadamente:
- 10.000 operações de leitura, OU
- 200 operações de escrita, OU
- Uma combinação de ambas
// Quota tracking utility
interface QuotaTracker {
used: number;
limit: number;
operations: Map;
}
const quotaCosts: Record = {
'commentThreads.list': 1,
'comments.list': 1,
'commentThreads.insert': 50,
'comments.insert': 50,
'comments.delete': 50,
'comments.update': 50,
'comments.setModerationStatus': 50,
};
function createQuotaTracker(dailyLimit: number = 10000): QuotaTracker {
return {
used: 0,
limit: dailyLimit,
operations: new Map(),
};
}
function trackQuotaUsage(
tracker: QuotaTracker,
operation: string
): { allowed: boolean; remaining: number } {
const cost = quotaCosts[operation] || 1;
if (tracker.used + cost > tracker.limit) {
return { allowed: false, remaining: tracker.limit - tracker.used };
}
tracker.used += cost;
const currentCount = tracker.operations.get(operation) || 0;
tracker.operations.set(operation, currentCount + 1);
return { allowed: true, remaining: tracker.limit - tracker.used };
}
Nota: A quota é resetada à meia-noite no horário do Pacífico (PT). Se você atingir os limites de quota, suas requisições falharão até o reset. Planeje suas operações adequadamente.
Recursos CommentThreads vs Comments
Entender a diferença entre esses dois recursos é crucial para um gerenciamento eficiente de comentários do YouTube.
CommentThreads representam comentários de nível superior em um vídeo. Cada thread contém:
- O comentário original (topLevelComment)
- Contagem de respostas
- Opcionalmente, um subconjunto de respostas
Comments representam comentários individuais, que podem ser:
- Comentários de nível superior (acessados via CommentThreads)
- Respostas a comentários de nível superior
Use CommentThreads quando você quiser:
- Buscar todos os comentários de nível superior em um vídeo
- Obter contagens de comentários e informações básicas de respostas
- Publicar um novo comentário de nível superior
Use Comments quando você quiser:
- Buscar todas as respostas a um comentário específico
- Publicar uma resposta a um comentário existente
- Atualizar ou excluir um comentário específico
// Type definitions for YouTube comment structures
interface YouTubeCommentSnippet {
authorDisplayName: string;
authorProfileImageUrl: string;
authorChannelId: { value: string };
textDisplay: string;
textOriginal: string;
likeCount: number;
publishedAt: string;
updatedAt: string;
}
interface YouTubeComment {
id: string;
snippet: YouTubeCommentSnippet;
}
interface YouTubeCommentThread {
id: string;
snippet: {
videoId: string;
topLevelComment: YouTubeComment;
totalReplyCount: number;
isPublic: boolean;
};
replies?: {
comments: YouTubeComment[];
};
}
Buscando Comentários de Vídeos com a YouTube Comments API
Recuperar comentários é a operação mais comum. Aqui está uma implementação completa que lida com paginação e decodificação de entidades HTML:
// Helper to decode HTML entities from YouTube API responses
function decodeHtmlEntities(text: string): string {
if (!text) return text;
return text
.replace(/'/g, "'")
.replace(/"/g, '"')
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/'/g, "'")
.replace(///g, '/')
.replace(/ /g, ' ');
}
interface CommentData {
commentId: string;
comment: string;
created: string;
from: {
id: string;
name: string;
picture: string;
};
likeCount: number;
replyCount: number;
platform: 'youtube';
platformPostId: string;
replies: CommentData[];
}
interface GetCommentsOptions {
limit?: number;
pageToken?: string;
}
interface GetCommentsResult {
comments: CommentData[];
pagination: {
hasMore: boolean;
pageToken?: string;
};
}
async function getVideoComments(
accessToken: string,
videoId: string,
options: GetCommentsOptions = {}
): Promise {
const maxResults = Math.min(options.limit || 25, 100);
const params = new URLSearchParams({
part: 'snippet,replies',
videoId: videoId,
maxResults: maxResults.toString(),
});
if (options.pageToken) {
params.append('pageToken', options.pageToken);
}
const response = await fetch(
`${YOUTUBE_API_BASE}/commentThreads?${params.toString()}`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
}
);
if (!response.ok) {
const errorBody = await response.text();
throw new Error(`Failed to fetch comments: ${response.status} ${errorBody}`);
}
const data = await response.json();
const comments: CommentData[] = (data.items || []).map(
(thread: YouTubeCommentThread) => {
const topComment = thread.snippet.topLevelComment.snippet;
const replies: CommentData[] = (thread.replies?.comments || []).map(
(reply: YouTubeComment) => ({
commentId: reply.id,
comment: decodeHtmlEntities(reply.snippet.textDisplay),
created: reply.snippet.publishedAt,
from: {
id: reply.snippet.authorChannelId?.value || '',
name: reply.snippet.authorDisplayName,
picture: reply.snippet.authorProfileImageUrl,
},
likeCount: reply.snippet.likeCount || 0,
replyCount: 0,
platform: 'youtube' as const,
platformPostId: thread.snippet.videoId,
replies: [],
})
);
return {
commentId: thread.snippet.topLevelComment.id,
comment: decodeHtmlEntities(topComment.textDisplay),
created: topComment.publishedAt,
from: {
id: topComment.authorChannelId?.value || '',
name: topComment.authorDisplayName,
picture: topComment.authorProfileImageUrl,
},
likeCount: topComment.likeCount || 0,
replyCount: thread.snippet.totalReplyCount || 0,
platform: 'youtube' as const,
platformPostId: thread.snippet.videoId,
replies,
};
}
);
return {
comments,
pagination: {
hasMore: !!data.nextPageToken,
pageToken: data.nextPageToken,
},
};
}
Publicando Respostas a Comentários
Responder a comentários requer o endpoint comments.insert com o ID do comentário pai:
interface ReplyResult {
replyId: string;
text: string;
publishedAt: string;
}
async function replyToComment(
accessToken: string,
parentCommentId: string,
text: string
): Promise {
const response = await fetch(
`${YOUTUBE_API_BASE}/comments?part=snippet`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({
snippet: {
parentId: parentCommentId,
textOriginal: text,
},
}),
}
);
if (!response.ok) {
const errorBody = await response.text();
// Handle specific error cases
if (response.status === 403) {
if (errorBody.includes('commentsDisabled')) {
throw new Error('Comments are disabled on this video');
}
if (errorBody.includes('forbidden')) {
throw new Error('You do not have permission to comment on this video');
}
}
if (response.status === 400) {
if (errorBody.includes('processingFailure')) {
throw new Error('Comment processing failed. The comment may contain prohibited content.');
}
}
throw new Error(`Failed to post reply: ${response.status} ${errorBody}`);
}
const data = await response.json();
return {
replyId: data.id,
text: data.snippet.textDisplay,
publishedAt: data.snippet.publishedAt,
};
}
// Post a new top-level comment on a video
async function postComment(
accessToken: string,
videoId: string,
text: string
): Promise<{ commentId: string }> {
const response = await fetch(
`${YOUTUBE_API_BASE}/commentThreads?part=snippet`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({
snippet: {
videoId,
topLevelComment: {
snippet: {
textOriginal: text,
},
},
},
}),
}
);
if (!response.ok) {
const errorBody = await response.text();
throw new Error(`Failed to post comment: ${response.status} ${errorBody}`);
}
const data = await response.json();
return { commentId: data.id };
}
Operações de Moderação de Comentários
O YouTube fornece capacidades de moderação para proprietários de canais gerenciarem comentários em seus vídeos:
type ModerationStatus = 'heldForReview' | 'published' | 'rejected';
interface ModerationResult {
succes