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.
| Identificador | Exemplo | Formato da URL | Observações |
|---|---|---|---|
| ID do canal | UC_x5XG1OV2P6uZZ5FSM9Ttw | youtube.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 | @GoogleDevelopers | youtube.com/@GoogleDevelopers | Único, não diferencia maiúsculas de minúsculas, e o dono pode mudá-lo no YouTube Studio. |
| Nome de usuário antigo | GoogleDevelopers | youtube.com/user/GoogleDevelopers | Do sistema antigo de nomes de usuário. Pode ser convertido com o filtro forUsername da API. |
| URL personalizada antiga | GoogleDevelopers | youtube.com/c/GoogleDevelopers | URLs 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
- Step 1Crie um projeto no Google Cloud
No console do Google Cloud, crie um projeto ou escolha um existente.
- 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.
- Step 3Crie uma chave de API
Em Credenciais, crie uma chave de API. Dados públicos de canais, como
channels.listpor handle ou ID, só precisam de uma chave, não de OAuth. Restrinja a chave à YouTube Data API. - 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. - 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:
- Uma chamada
channels.listcusta 1 unidade, seja qual for o filtro. - Os projetos recebem uma alocação padrão de 10.000 unidades por dia para todos os endpoints, exceto
search.listevideos.insert, que agora têm limites próprios de 100 chamadas por dia cada. - Toda requisição custa pelo menos 1 unidade, inclusive as inválidas.
- As cotas diárias são zeradas à meia-noite no horário do Pacífico.
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 -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.
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
}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 foundPasse 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.
- `youtube.com/user/<name>`: passe o nome para
forUsernamecom a funçãochannelIdFromUsernameacima. Custa 1 unidade, como qualquer outra chamadachannels.list. - `youtube.com/c/<name>`: não existe filtro do
channels.listpara URLs personalizadas. Muitos canais escolheram um handle igual ao antigo nome personalizado, então testarforHandlecom o mesmo texto pode funcionar, mas uma correspondência não prova que é o mesmo canal. Confirme de outro jeito antes de confiar nela, por exemplo conferindo o título ou lendo o link canônico da página (descrito abaixo). - `search.list` retorna resultados ordenados por relevância para uma consulta, não uma busca exata, e o limite padrão é de 100 chamadas por dia. Evite-o para converter identificadores.
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.
- Tamanho do lote. A referência descreve
idcomo uma lista separada por vírgulas e limitamaxResultsa 50. Ela não informa um limite separado para o número de IDs, então o código envia 50 por chamada e definemaxResults=50para que o padrão de 5 nunca corte um lote. - Ordem. Não presuma que os resultados voltam na ordem em que você enviou. Indexe a resposta por
id. - `customUrl` é opcional. A referência o descreve apenas como a URL personalizada do canal, e uma nota de revisão de 2016 diz que nem todos os canais têm uma. Trate-o como anulável, remova o
@do início se houver e confira o formato com alguns canais que você conhece antes de construir em cima disso. - Títulos não são handles.
snippet.titleé o nome de exibição. Não é único e pode ter espaços e emoji.
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 erro | HTTP | Quando acontece | O que fazer |
|---|---|---|---|
Nenhum items numa resposta 200 | 200 | O handle ou nome de usuário não existe | Retorne null; guarde a ausência no cache por pouco tempo |
channelNotFound | 404 | Um canal do parâmetro id não foi encontrado | Trate como não encontrado, como as funções acima fazem |
invalidCriteria | 400 | Mais de um filtro na requisição | Envie exatamente um entre forHandle, forUsername ou id |
channelForbidden | 403 | O canal não aceita a requisição ou ela não está devidamente autorizada | Registre no log e pule o canal |
quotaExceeded | 403 | Seu projeto usou toda a cota diária | Pare 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) | 400 | Chave ausente ou errada | Corrija 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:
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.
// 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;
}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 NoneUse com moderação e só onde a API não resolve. É um método não oficial e frágil:
- O markup não é uma interface documentada e pode mudar a qualquer momento.
- O YouTube pode servir páginas de consentimento, limites de taxa ou um CAPTCHA para requisições automatizadas, principalmente de faixas de IP de nuvem. Nos nossos testes, seguir um link
youtu.bea partir de um servidor na nuvem terminou numa página de CAPTCHA do Google. - O acesso automatizado ao site pode conflitar com os Termos de Serviço do YouTube. Leia os termos e prefira a API oficial.
Comparando as abordagens
| Abordagem | Entrada | Custo | Prós | Contras |
|---|---|---|---|---|
channels.list com forHandle | Handle, com ou sem @ | 1 unidade por handle | Oficial, correspondência exata, funciona com qualquer handle | Um handle por chamada |
channels.list com forUsername | Nome antigo /user/ | 1 unidade por nome | Oficial, correspondência exata | Só para nomes de usuário antigos |
channels.list com id | Até 50 IDs de canal | 1 unidade a cada 50 IDs | Oficial, em lote, retorna título e customUrl | customUrl pode faltar |
search.list | Texto livre | Limite próprio de 100 chamadas por dia, por padrão | Encontra canais pelo nome | Palpites ordenados, não exatos; franquia pequena |
| Link canônico da página do canal | Qualquer URL de canal, inclusive /c/ | Sem cota de API | Funciona 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.