sociallinks.dev

Blog / Guía para desarrolladores

Cómo obtener el ID de un canal de YouTube a partir de su handle (y al revés)

Los creadores comparten su canal de YouTube como @handle, pero casi todo en la YouTube Data API se identifica por el ID del canal. Si guardas solo los handles, un cambio de nombre rompe tus registros; si guardas solo los ID, no puedes mostrar a los usuarios el nombre que reconocen. Esta guía te enseña a convertir un handle en un ID de canal con una sola llamada a la API, a pasar de ID a handles de 50 en 50, qué hacer con las URL antiguas /user/ y /c/, y cómo mantenerte dentro de la cuota por defecto.

Handles, ID de canal y nombres antiguos

A un canal de YouTube se puede llegar con hasta cuatro tipos de identificador. Solo uno de ellos tiene garantía de ser estable.

IdentificadorEjemploForma de la URLNotas
ID de canalUC_x5XG1OV2P6uZZ5FSM9Ttwyoutube.com/channel/UC...El ID que usa YouTube para identificar el canal. Distingue mayúsculas y minúsculas. Úsalo como clave principal.
Handle@GoogleDevelopersyoutube.com/@GoogleDevelopersÚnico, no distingue mayúsculas, y el dueño puede cambiarlo en YouTube Studio.
Nombre de usuario antiguoGoogleDevelopersyoutube.com/user/GoogleDevelopersDel antiguo sistema de nombres de usuario. Se resuelve con el filtro forUsername de la API.
URL personalizada antiguaGoogleDevelopersyoutube.com/c/GoogleDevelopersURL personalizadas antiguas, que según YouTube siguen funcionando. Ningún filtro de la API las resuelve.

Guarda el ID de canal como clave y mantén el handle como un valor visible que actualizas. El resto de esta guía trata de cómo llegar a ese ID desde lo que te haya dado el usuario.

Configura una clave de API y entiende la cuota

  1. Step 1Crea un proyecto de Google Cloud

    En la consola de Google Cloud, crea un proyecto o elige uno existente.

  2. Step 2Activa la YouTube Data API v3

    Abre APIs y servicios, busca YouTube Data API v3 en la biblioteca y actívala para el proyecto.

  3. Step 3Crea una clave de API

    En Credenciales, crea una clave de API. Los datos públicos de canales, como channels.list por handle o por ID, solo necesitan una clave, no OAuth. Restringe la clave a la YouTube Data API.

  4. Step 4Mantén la clave fuera de tu código

    Ponla en una variable de entorno, por ejemplo YOUTUBE_API_KEY, y léela en tiempo de ejecución. Los ejemplos de abajo hacen justo eso.

  5. Step 5Revisa tu cuota

    En la consola, la página de Cuotas de la API muestra tu asignación diaria y tu consumo.

Las cifras que importan, según la documentación de Google:

Así que la cuota por defecto cubre unas 10.000 búsquedas de handle al día, y hasta 50 veces más búsquedas por ID, porque una sola llamada puede traer 50 canales por ID por la misma unidad.

De handle a ID de canal con forHandle

channels.list admite exactamente un filtro. El filtro forHandle recibe un handle de YouTube, y la referencia dice que el valor puede llevar delante el símbolo @, así que funcionan tanto GoogleDevelopers como @GoogleDevelopers. Pide part=id cuando solo necesites el ID; la respuesta es más pequeña y el coste es el mismo.

cURL

curl -s "https://www.googleapis.com/youtube/v3/channels?part=id&forHandle=@GoogleDevelopers&key=$YOUTUBE_API_KEY"

El ejemplo de Node de abajo envuelve la petición en una función auxiliar, listChannels, que reutiliza el resto de la guía. Devuelve una lista vacía cuando no hay coincidencias y lanza un error en los fallos reales, como una clave incorrecta o la cuota agotada.

Node.js

const API = "https://www.googleapis.com/youtube/v3/channels";
async function listChannels(params) {
  const key = process.env.YOUTUBE_API_KEY;
  if (!key) throw new Error("Set YOUTUBE_API_KEY");
  const url = new URL(API);
  url.search = new URLSearchParams({ ...params, key }).toString();
  const res = await fetch(url, { signal: AbortSignal.timeout(10_000) });
  const data = await res.json();
  if (!res.ok) {
    const reason = data.error?.errors?.[0]?.reason;
    if (reason === "channelNotFound") return [];
    throw new Error(`YouTube API ${res.status} ${reason ?? ""}: ${data.error?.message}`);
  }
  return data.items ?? []; // no match: items is empty or missing
}

/** "@GoogleDevelopers" or "GoogleDevelopers" -> "UC_x5XG1OV2P6uZZ5FSM9Ttw" or null */
export async function channelIdFromHandle(handle) {
  const items = await listChannels({ part: "id", forHandle: handle.trim() });
  return items[0]?.id ?? null;
}

/** Legacy username from a youtube.com/user/<name> URL -> channel ID or null */
export async function channelIdFromUsername(username) {
  const items = await listChannels({ part: "id", forUsername: username.trim() });
  return items[0]?.id ?? null;
}

/** Any number of channel IDs -> Map(id -> { title, customUrl }) */
export async function channelsById(ids) {
  const unique = [...new Set(ids)];
  const found = new Map();
  for (let i = 0; i < unique.length; i += 50) {
    const batch = unique.slice(i, i + 50);
    const items = await listChannels({ part: "snippet", id: batch.join(","), maxResults: "50" });
    for (const ch of items) {
      found.set(ch.id, { title: ch.snippet.title, customUrl: ch.snippet.customUrl ?? null });
    }
  }
  return found; // IDs missing from the map were not found
}

Python

import os

import requests

API = "https://www.googleapis.com/youtube/v3/channels"


def list_channels(**params):
    key = os.environ.get("YOUTUBE_API_KEY")
    if not key:
        raise RuntimeError("Set YOUTUBE_API_KEY")
    response = requests.get(API, params={**params, "key": key}, timeout=10)
    data = response.json()
    if not response.ok:
        errors = data.get("error", {}).get("errors") or [{}]
        reason = errors[0].get("reason")
        if reason == "channelNotFound":
            return []
        raise RuntimeError(f"YouTube API {response.status_code} {reason}: {data.get('error', {}).get('message')}")
    return data.get("items", [])  # no match: items is empty or missing


def channel_id_from_handle(handle):
    """'@GoogleDevelopers' or 'GoogleDevelopers' -> 'UC_x5XG1OV2P6uZZ5FSM9Ttw' or None"""
    items = list_channels(part="id", forHandle=handle.strip())
    return items[0]["id"] if items else None


def channel_id_from_username(username):
    """Legacy username from a youtube.com/user/<name> URL -> channel ID or None"""
    items = list_channels(part="id", forUsername=username.strip())
    return items[0]["id"] if items else None


def channels_by_id(ids):
    """Any number of channel IDs -> {id: {"title", "customUrl"}}; missing IDs were not found"""
    unique = list(dict.fromkeys(ids))
    found = {}
    for i in range(0, len(unique), 50):
        batch = unique[i:i + 50]
        for ch in list_channels(part="snippet", id=",".join(batch), maxResults=50):
            found[ch["id"]] = {
                "title": ch["snippet"]["title"],
                "customUrl": ch["snippet"].get("customUrl"),
            }
    return found

Pasa los handles tal como los escribió el usuario, después de decodificar cualquier codificación porcentual de la URL. Los handles pueden usar letras de muchos alfabetos, no solo ASCII, y tanto URLSearchParams como requests codifican el parámetro por ti.

Nombres de usuario antiguos y URL personalizadas /c/

Los enlaces antiguos de las bios de los creadores y de las hojas de cálculo suelen usar alguna de las formas anteriores a los handles.

De ID de canal a handle, de 50 en 50

Para ir en sentido contrario se usa el filtro id, que recibe una lista de ID de canal separados por comas. Pide part=snippet y lee snippet.customUrl. La función channelsById de arriba elimina los ID duplicados, los envía en bloques de 50 y devuelve un mapa indexado por ID. Los ID que no aparecen en el mapa no se encontraron, se eliminaron o no son visibles para la API.

No encontrado, errores y caché

Una búsqueda sin coincidencias no es un error. Con forHandle y forUsername, la respuesta simplemente no tiene elementos, así que comprueba si el array items está vacío o falta. Los errores documentados de channels.list son estos:

Motivo del errorHTTPCuándo ocurreQué hacer
Sin items en una respuesta 200200El handle o el nombre de usuario no existeDevuelve null; guarda el fallo en caché poco tiempo
channelNotFound404No se encuentra un canal del parámetro idTrátalo como no encontrado, como hacen las funciones de arriba
invalidCriteria400Más de un filtro en la peticiónEnvía exactamente uno de forHandle, forUsername o id
channelForbidden403El canal no admite la petición o no está bien autorizadaRegístralo y salta el canal
quotaExceeded403Tu proyecto ha agotado su cuota diariaPara y reintenta después de medianoche, hora del Pacífico, o solicita más cuota
badRequest (clave de API no válida)400Clave ausente o incorrectaCorrige la configuración; no reintentes

Guarda los resultados en caché para ahorrar cuota y latencia, pero respeta las normas de YouTube sobre almacenamiento de datos. Las YouTube API Services Developer Policies (sección III.E.4) dicen que puedes guardar datos de la API no autorizados durante un máximo de 30 días naturales, tras los cuales debes borrarlos o actualizarlos. Una caché con un TTL muy por debajo de 30 días, indexada por el handle en minúsculas, cubre la mayor parte del beneficio:

Caché en Node.js

import { channelIdFromHandle } from "./youtube.mjs";

const TTL_MS = 7 * 24 * 60 * 60 * 1000; // refresh well inside YouTube's 30-day limit
const cache = new Map(); // lowercase handle -> { id, at }

export async function cachedChannelId(handle) {
  const key = handle.trim().replace(/^@/, "").toLowerCase();
  const hit = cache.get(key);
  if (hit && Date.now() - hit.at < TTL_MS) return hit.id;
  const id = await channelIdFromHandle(handle);
  cache.set(key, { id, at: Date.now() }); // caches misses (null) too
  return id;
}

En producción, pon la misma lógica en Redis o en una tabla de base de datos con una columna expires_at, y da a los fallos un TTL más corto que a los aciertos para que un handle recién creado se detecte rápido.

La alternativa sin clave de API: leer la página del canal

Las páginas públicas de los canales incluyen el ID del canal en su HTML. Una petición a https://www.youtube.com/@GoogleDevelopers devuelve hoy una página cuyo <link rel="canonical"> apunta a https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw, y lo mismo pasa con las páginas /c/ y /user/, lo que convierte esto en la única forma de resolver un nombre /c/ sin adivinar.

Node.js

// Unofficial fallback: reads the public channel page. Fragile, prefer the API.
export async function channelIdFromPage(handle) {
  const url = `https://www.youtube.com/@${encodeURIComponent(handle.replace(/^@/, ""))}`;
  const res = await fetch(url, {
    headers: { "Accept-Language": "en-US" },
    signal: AbortSignal.timeout(10_000),
  });
  if (!res.ok) return null;
  const html = await res.text();
  const match = html.match(/<link rel="canonical" href="https:\/\/www\.youtube\.com\/channel\/(UC[A-Za-z0-9_-]{22})"/);
  return match?.[1] ?? null;
}

Python

import re
from urllib.parse import quote

import requests

CANONICAL = re.compile(r'<link rel="canonical" href="https://www\.youtube\.com/channel/(UC[A-Za-z0-9_-]{22})"')


def channel_id_from_page(handle):
    """Unofficial fallback: reads the public channel page. Fragile, prefer the API."""
    url = "https://www.youtube.com/@" + quote(handle.lstrip("@"), safe="._-")
    response = requests.get(url, headers={"Accept-Language": "en-US"}, timeout=10)
    if not response.ok:
        return None
    match = CANONICAL.search(response.text)
    return match.group(1) if match else None

Úsalo con moderación y solo donde la API no pueda ayudar. Es no oficial y frágil:

Comparación de los métodos

MétodoEntradaCosteVentajasInconvenientes
channels.list con forHandleHandle, con o sin @1 unidad por handleOficial, coincidencia exacta, funciona con cualquier handleUn handle por llamada
channels.list con forUsernameNombre antiguo /user/1 unidad por nombreOficial, coincidencia exactaSolo para nombres de usuario antiguos
channels.list con idHasta 50 ID de canal1 unidad por cada 50 IDOficial, por lotes, devuelve el título y customUrlcustomUrl puede faltar
search.listTexto libreLímite aparte de 100 llamadas al día por defectoEncuentra canales por nombreSuposiciones ordenadas, no exactas; cupo pequeño
Enlace canónico de la página del canalCualquier URL de canal, incluidas las /c/Sin cuota de APIFunciona sin clave; resuelve nombres /c/No oficial, frágil, puede bloquearse, puede chocar con las condiciones

Para la mayoría de los productos, la configuración adecuada es sencilla: resuelve los handles con forHandle, guarda el ID del canal, actualiza los handles en lotes de 50 con el filtro id y deja la alternativa de la página detrás de una opción para el raro enlace /c/. Si además necesitas las cuentas del creador en otras plataformas, sociallinks.dev es una API que toma una URL de perfil de Instagram, TikTok, YouTube o X y devuelve las cuentas del creador en otras plataformas, con una puntuación de confianza y las pruebas de cada coincidencia.

Preguntas frecuentes

¿Necesito la @ cuando paso un handle a forHandle?

No. La referencia de channels.list dice que el valor de forHandle puede llevar delante el símbolo @, y da como ejemplos tanto GoogleDevelopers como @GoogleDevelopers.

¿Cuánta cuota consume una búsqueda de handle?

Una unidad. Cada llamada a channels.list cuesta 1 unidad, y la asignación por defecto es de 10.000 unidades al día, que se reinicia a medianoche, hora del Pacífico. Las peticiones no válidas también cuestan al menos 1 unidad.

¿Puedo buscar varios handles en una sola llamada?

No. forHandle recibe un solo handle. Solo el filtro id acepta una lista separada por comas, así que agrupa en lotes el sentido inverso (de ID a handles) y guarda en caché el sentido directo.

¿Cambia alguna vez el ID de un canal?

El ID de canal es el identificador que usa YouTube para identificar el canal de forma única. Cambiar el handle o el título no lo cambia. Por eso es la clave principal adecuada. Los handles, los títulos y las URL personalizadas pueden cambiar.

¿Por qué snippet.customUrl viene vacío?

No todos los canales tienen una URL personalizada, como indica el propio historial de revisiones de YouTube. Trata el campo como opcional y, si falta, muestra la URL /channel/UC....

¿Cuánto tiempo puedo guardar los resultados en caché?

Las Developer Policies de YouTube limitan los datos de la API almacenados a 30 días naturales antes de borrarlos o actualizarlos. Un TTL de una semana o menos para los aciertos, y más corto para los fallos, te mantiene muy por debajo de ese límite.

Encuentra las cuentas de un creador