sociallinks.dev

Blog / Guia para desenvolvedores

Como obter o ID de um canal do YouTube a partir do handle (e o contrário)

Os criadores divulgam o canal do YouTube como @handle, mas quase tudo na YouTube Data API usa o ID do canal como chave. Se você guarda só handles, uma mudança de nome quebra seus registros; se guarda só IDs, não consegue mostrar aos usuários o nome que eles reconhecem. Este guia mostra como converter um handle em ID de canal com uma única chamada à API, como voltar de IDs para handles de 50 em 50, o que fazer com URLs antigas /user/ e /c/ e como ficar dentro da cota padrão.

Handles, IDs de canal e nomes antigos

Um canal do YouTube pode ser acessado por até quatro tipos de identificador. Só um deles tem estabilidade garantida.

IdentificadorExemploFormato da URLObservações
ID do canalUC_x5XG1OV2P6uZZ5FSM9Ttwyoutube.com/channel/UC...O ID que o YouTube usa para identificar o canal. Diferencia maiúsculas de minúsculas. Use-o como chave primária.
Handle@GoogleDevelopersyoutube.com/@GoogleDevelopersÚnico, não diferencia maiúsculas de minúsculas, e o dono pode mudá-lo no YouTube Studio.
Nome de usuário antigoGoogleDevelopersyoutube.com/user/GoogleDevelopersDo sistema antigo de nomes de usuário. Pode ser convertido com o filtro forUsername da API.
URL personalizada antigaGoogleDevelopersyoutube.com/c/GoogleDevelopersURLs personalizadas antigas, que segundo o YouTube continuam funcionando. Nenhum filtro da API as converte.

Guarde o ID do canal como chave e mantenha o handle como valor de exibição que você atualiza. O resto deste guia mostra como chegar a esse ID a partir do que o usuário informou.

Configure uma chave de API e entenda a cota

  1. Step 1Crie um projeto no Google Cloud

    No console do Google Cloud, crie um projeto ou escolha um existente.

  2. Step 2Ative a YouTube Data API v3

    Abra APIs e serviços, encontre a YouTube Data API v3 na biblioteca e ative-a no projeto.

  3. Step 3Crie uma chave de API

    Em Credenciais, crie uma chave de API. Dados públicos de canais, como channels.list por handle ou ID, só precisam de uma chave, não de OAuth. Restrinja a chave à YouTube Data API.

  4. Step 4Deixe a chave fora do código

    Coloque-a numa variável de ambiente, por exemplo YOUTUBE_API_KEY, e leia-a em tempo de execução. Os exemplos abaixo fazem exatamente isso.

  5. Step 5Confira sua cota

    No console, a página de Cotas da API mostra sua alocação diária e o uso.

Os números que importam, segundo a documentação do Google:

Ou seja, a cota padrão cobre cerca de 10.000 consultas de handle por dia, e até 50 vezes mais consultas por ID, porque uma chamada busca 50 canais por ID pela mesma unidade.

De handle para ID de canal com forHandle

O channels.list aceita exatamente um filtro. O filtro forHandle recebe um handle do YouTube, e a referência diz que o valor pode vir precedido de @, então GoogleDevelopers e @GoogleDevelopers funcionam. Peça part=id quando só precisar do ID; a resposta é menor e o custo é o mesmo.

cURL

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

O exemplo em Node abaixo coloca a requisição numa função auxiliar, listChannels, que o resto do guia reutiliza. Ela retorna uma lista vazia quando nada corresponde e lança um erro nos problemas reais, como chave inválida ou cota esgotada.

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

Passe os handles como o usuário escreveu, depois de decodificar qualquer percent-encoding da URL. Handles podem usar letras de vários alfabetos, não só ASCII, e tanto o URLSearchParams quanto o requests codificam o parâmetro para você.

Nomes de usuário antigos e URLs personalizadas /c/

Links antigos em bios de criadores e planilhas costumam usar um dos formatos anteriores aos handles.

De ID de canal para handle, de 50 em 50

O caminho inverso usa o filtro id, que recebe uma lista de IDs de canal separados por vírgula. Peça part=snippet e leia snippet.customUrl. A função channelsById acima remove IDs repetidos, envia os IDs em blocos de 50 e retorna um mapa indexado por ID. IDs que não aparecem no mapa não foram encontrados, foram apagados ou não estão visíveis para a API.

Não encontrado, erros e cache

Uma consulta sem correspondência não é um erro. Para forHandle e forUsername, a resposta simplesmente não tem itens, então verifique se o array items está vazio ou ausente. Os erros documentados do channels.list são estes:

Motivo do erroHTTPQuando aconteceO que fazer
Nenhum items numa resposta 200200O handle ou nome de usuário não existeRetorne null; guarde a ausência no cache por pouco tempo
channelNotFound404Um canal do parâmetro id não foi encontradoTrate como não encontrado, como as funções acima fazem
invalidCriteria400Mais de um filtro na requisiçãoEnvie exatamente um entre forHandle, forUsername ou id
channelForbidden403O canal não aceita a requisição ou ela não está devidamente autorizadaRegistre no log e pule o canal
quotaExceeded403Seu projeto usou toda a cota diáriaPare e tente de novo depois da meia-noite no horário do Pacífico, ou peça mais cota
badRequest (chave de API inválida)400Chave ausente ou erradaCorrija a configuração; não tente de novo

Guarde os resultados em cache para economizar cota e latência, mas respeite as regras do YouTube sobre armazenamento de dados. As Políticas para desenvolvedores dos serviços de API do YouTube (seção III.E.4) dizem que você pode guardar dados de API não autorizados por no máximo 30 dias corridos, depois dos quais precisa apagá-los ou atualizá-los. Um cache com TTL bem abaixo de 30 dias, indexado pelo handle em minúsculas, já traz quase todo o benefício:

Cache em 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;
}

Em produção, coloque a mesma lógica no Redis ou numa tabela de banco de dados com uma coluna expires_at, e dê às ausências um TTL menor que o dos acertos, para que um handle recém-criado seja detectado rápido.

Alternativa sem chave de API: ler a página do canal

As páginas públicas dos canais trazem o ID do canal no HTML. Hoje, uma requisição para https://www.youtube.com/@GoogleDevelopers retorna uma página cujo <link rel="canonical"> aponta para https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw, e o mesmo vale para as páginas /c/ e /user/, o que torna este o único jeito de converter um nome /c/ sem chutar.

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

Use com moderação e só onde a API não resolve. É um método não oficial e frágil:

Comparando as abordagens

AbordagemEntradaCustoPrósContras
channels.list com forHandleHandle, com ou sem @1 unidade por handleOficial, correspondência exata, funciona com qualquer handleUm handle por chamada
channels.list com forUsernameNome antigo /user/1 unidade por nomeOficial, correspondência exataSó para nomes de usuário antigos
channels.list com idAté 50 IDs de canal1 unidade a cada 50 IDsOficial, em lote, retorna título e customUrlcustomUrl pode faltar
search.listTexto livreLimite próprio de 100 chamadas por dia, por padrãoEncontra canais pelo nomePalpites ordenados, não exatos; franquia pequena
Link canônico da página do canalQualquer URL de canal, inclusive /c/Sem cota de APIFunciona sem chave; converte nomes /c/Não oficial, frágil, pode ser bloqueado, pode conflitar com os termos

Para a maioria dos produtos, a configuração certa é simples: converta handles com forHandle, guarde o ID do canal, atualize os handles em lotes de 50 com o filtro id e deixe a alternativa pela página atrás de uma flag para o raro link /c/. Se você também precisa das contas do criador em outras plataformas, o sociallinks.dev é uma API que recebe a URL de um perfil do Instagram, TikTok, YouTube ou X e retorna as contas do criador em outras plataformas, com uma pontuação de confiança e a evidência de cada correspondência.

Perguntas frequentes

Preciso do @ ao passar um handle para o forHandle?

Não. A referência do channels.list diz que o valor de forHandle pode vir precedido de @ e dá como exemplos tanto GoogleDevelopers quanto @GoogleDevelopers.

Quanta cota uma consulta de handle usa?

Uma unidade. Toda chamada channels.list custa 1 unidade, e a alocação padrão é de 10.000 unidades por dia, zerada à meia-noite no horário do Pacífico. Requisições inválidas também custam pelo menos 1 unidade.

Posso consultar vários handles numa chamada?

Não. O forHandle recebe um único handle. Só o filtro id aceita uma lista separada por vírgulas, então faça em lote o caminho inverso (de IDs para handles) e use cache no caminho direto.

O ID de um canal muda alguma vez?

O ID do canal é o identificador que o YouTube usa para identificar o canal de forma única. Mudar o handle ou o título não muda o ID. Por isso ele é a chave primária certa. Handles, títulos e URLs personalizadas podem mudar.

Por que o snippet.customUrl volta vazio?

Nem todo canal tem URL personalizada, como o próprio histórico de revisões do YouTube registra. Trate o campo como opcional e, na falta dele, mostre a URL /channel/UC....

Por quanto tempo posso guardar os resultados em cache?

As Políticas para desenvolvedores do YouTube limitam o armazenamento de dados da API a 30 dias corridos antes de apagá-los ou atualizá-los. Um TTL de uma semana ou menos para acertos, e menor para ausências, mantém você bem dentro disso.

Encontre as contas de um criador