La YouTube Comments API permite a los desarrolladores crear potentes herramientas de engagement que obtienen, publican y moderan comentarios de forma programática. Ya sea que estés construyendo un panel de redes sociales, una herramienta de gestión de comunidad o un sistema de respuesta automatizado, comprender los recursos de comentarios de la YouTube Data API es esencial para crear integraciones efectivas.
Esta guía cubre todo lo que necesitas para implementar una gestión robusta de comentarios de YouTube en tus aplicaciones, desde la configuración de autenticación hasta estrategias de optimización de cuota que mantienen tu integración funcionando sin problemas.

Introducción a YouTube Data API v3
La YouTube Data API v3 proporciona acceso programático a las funciones principales de YouTube, incluyendo subida de videos, gestión de listas de reproducción y operaciones de comentarios. Para la gestión de comentarios específicamente, la API expone dos recursos principales:
- CommentThreads: Comentarios de nivel superior en videos, incluyendo metadatos sobre el conteo de respuestas
- Comments: Comentarios individuales, incluyendo respuestas a comentarios de nivel superior
La API sigue convenciones RESTful y devuelve respuestas JSON. Todas las operaciones de comentarios requieren autenticación OAuth 2.0 ya que involucran datos y acciones específicas del usuario.
// 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 Autenticación OAuth 2.0
Las operaciones de comentarios de YouTube requieren diferentes niveles de autenticación dependiendo de la acción:
| Operación | API Key | OAuth 2.0 |
|---|---|---|
| Leer comentarios públicos | ✅ | ✅ |
| Leer comentarios en videos propios | ❌ | ✅ |
| Publicar comentarios | ❌ | ✅ |
| Responder a comentarios | ❌ | ✅ |
| Eliminar comentarios | ❌ | ✅ |
| Moderar comentarios | ❌ | ✅ |
Para cualquier operación de escritura o acceso a datos privados, debes usar OAuth 2.0. Aquí te mostramos cómo configurar el flujo 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: El scope
youtube.force-ssles requerido para operaciones de comentarios. Sin él, las solicitudes de escritura fallarán con un error 403.
Entendiendo la Cuota de YouTube API (10,000 Unidades/Día)
El sistema de cuota de YouTube API es uno de los conceptos más importantes a entender antes de construir cualquier integración. Cada solicitud a la API consume unidades de cuota, y estás limitado a 10,000 unidades por día por defecto.
Aquí está el desglose del costo de cuota para operaciones relacionadas con comentarios:
| Operación | Costo de Cuota |
|---|---|
| commentThreads.list | 1 unidad |
| comments.list | 1 unidad |
| commentThreads.insert | 50 unidades |
| comments.insert (respuesta) | 50 unidades |
| comments.delete | 50 unidades |
| comments.update | 50 unidades |
| comments.setModerationStatus | 50 unidades |
Con 10,000 unidades por día, puedes realizar aproximadamente:
- 10,000 operaciones de lectura, O
- 200 operaciones de escritura, O
- Una combinación 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: La cuota se reinicia a medianoche hora del Pacífico (PT). Si alcanzas los límites de cuota, tus solicitudes fallarán hasta el reinicio. Planifica tus operaciones en consecuencia.
Recursos CommentThreads vs Comments
Entender la diferencia entre estos dos recursos es crucial para una gestión eficiente de comentarios de YouTube.
CommentThreads representan comentarios de nivel superior en un video. Cada hilo contiene:
- El comentario original (topLevelComment)
- Conteo de respuestas
- Opcionalmente, un subconjunto de respuestas
Comments representan comentarios individuales, que pueden ser:
- Comentarios de nivel superior (accedidos vía CommentThreads)
- Respuestas a comentarios de nivel superior
Usa CommentThreads cuando quieras:
- Obtener todos los comentarios de nivel superior en un video
- Obtener conteos de comentarios e información básica de respuestas
- Publicar un nuevo comentario de nivel superior
Usa Comments cuando quieras:
- Obtener todas las respuestas a un comentario específico
- Publicar una respuesta a un comentario existente
- Actualizar o eliminar un comentario 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[];
};
}
Obteniendo Comentarios de Video con YouTube Comments API
Recuperar comentarios es la operación más común. Aquí hay una implementación completa que maneja paginación y decodificación 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 Respuestas a Comentarios
Responder a comentarios requiere el endpoint comments.insert con el ID del comentario padre:
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 };
}
Operaciones de Moderación de Comentarios
YouTube proporciona capacidades de moderación para que los propietarios de canales gestionen los comentarios en sus videos:
type ModerationStatus = 'heldForReview' | 'published' | 'rejected';
interface ModerationResult {
succes