Blog / Guida per sviluppatori
Come ottenere l’ID di un canale YouTube da un handle (e viceversa)
I creator condividono il proprio canale YouTube come @handle, ma quasi tutto nella YouTube Data API usa come chiave l’ID del canale. Se salvi solo gli handle, un cambio di nome rompe i tuoi record; se salvi solo gli ID, non puoi mostrare agli utenti il nome che riconoscono. Questa guida mostra come convertire un handle in ID canale con una sola chiamata API, come tornare dagli ID agli handle 50 alla volta, cosa fare con i vecchi URL /user/ e /c/, e come restare entro la quota predefinita.
Handle, ID canale e nomi legacy
Un canale YouTube può essere raggiunto tramite fino a quattro tipi di identificatore. Solo uno di questi è garantito come stabile.
| Identificatore | Esempio | Forma dell’URL | Note |
|---|---|---|---|
| ID canale | UC_x5XG1OV2P6uZZ5FSM9Ttw | youtube.com/channel/UC... | L’ID che YouTube usa per identificare il canale. Distingue maiuscole e minuscole. Usalo come chiave primaria. |
| Handle | @GoogleDevelopers | youtube.com/@GoogleDevelopers | Unico, non distingue maiuscole e minuscole, e il titolare può cambiarlo in YouTube Studio. |
| Username legacy | GoogleDevelopers | youtube.com/user/GoogleDevelopers | Dal vecchio sistema di username. Si può convertire con il filtro forUsername dell’API. |
| URL personalizzato legacy | GoogleDevelopers | youtube.com/c/GoogleDevelopers | Vecchi URL personalizzati che, secondo YouTube, continuano a funzionare. Nessun filtro dell’API li converte. |
Salva l’ID del canale come chiave e tieni l’handle come valore da mostrare, da aggiornare periodicamente. Il resto di questa guida spiega come arrivare a quell’ID partendo da qualsiasi cosa ti abbia dato l’utente.
Configura una chiave API e capisci la quota
- Step 1Crea un progetto Google Cloud
Nella console Google Cloud, crea un progetto o scegline uno esistente.
- Step 2Attiva la YouTube Data API v3
Apri API e servizi, trova YouTube Data API v3 nella libreria e attivala per il progetto.
- Step 3Crea una chiave API
In Credenziali, crea una chiave API. Per i dati pubblici dei canali, come
channels.listper handle o ID, basta una chiave, non serve OAuth. Limita la chiave alla YouTube Data API. - Step 4Tieni la chiave fuori dal codice
Mettila in una variabile d’ambiente, per esempio
YOUTUBE_API_KEY, e leggila in fase di esecuzione. Gli esempi qui sotto fanno esattamente questo. - Step 5Controlla la quota
Nella console, la pagina Quote dell’API mostra l’allocazione giornaliera e l’utilizzo.
I numeri che contano, secondo la documentazione di Google:
- Una chiamata
channels.listcosta 1 unità, qualunque filtro tu usi. - I progetti ricevono un’allocazione predefinita di 10.000 unità al giorno per tutti gli endpoint tranne
search.listevideos.insert, che ora hanno limiti propri di 100 chiamate al giorno ciascuno. - Ogni richiesta costa almeno 1 unità, anche le richieste non valide.
- Le quote giornaliere si azzerano a mezzanotte, ora del Pacifico.
Quindi la quota predefinita copre circa 10.000 ricerche per handle al giorno, e fino a 50 volte tante ricerche per ID, perché una chiamata può recuperare 50 canali per ID con la stessa singola unità.
Da handle a ID canale con forHandle
channels.list accetta esattamente un filtro. Il filtro forHandle accetta un handle YouTube, e la documentazione dice che il valore può essere preceduto dal simbolo @, quindi funzionano sia GoogleDevelopers sia @GoogleDevelopers. Chiedi part=id quando ti serve solo l’ID: la risposta è più piccola e il costo è lo stesso.
curl -s "https://www.googleapis.com/youtube/v3/channels?part=id&forHandle=@GoogleDevelopers&key=$YOUTUBE_API_KEY"L’esempio Node qui sotto racchiude la richiesta in un’unica funzione di supporto, listChannels, che il resto della guida riutilizza. Restituisce una lista vuota quando non c’è corrispondenza e lancia un’eccezione sugli errori veri, come una chiave errata o una quota esaurita.
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 foundPassa gli handle come li ha scritti l’utente, dopo aver decodificato l’eventuale percent-encoding dell’URL. Gli handle possono usare lettere di molti alfabeti, non solo ASCII, e sia URLSearchParams sia requests codificano il parametro al posto tuo.
Username legacy e URL personalizzati /c/
I vecchi link nelle bio dei creator e nei fogli di calcolo usano spesso una delle forme precedenti agli handle.
- `youtube.com/user/<name>`: passa il nome a
forUsernameconchannelIdFromUsernamevisto sopra. Costa 1 unità come qualsiasi altra chiamatachannels.list. - `youtube.com/c/<name>`: non esiste un filtro
channels.listper gli URL personalizzati. Molti canali hanno scelto un handle uguale al vecchio nome personalizzato, quindi provareforHandlecon la stessa stringa può funzionare, ma una corrispondenza non prova che sia lo stesso canale. Confermala in un altro modo prima di farci affidamento, per esempio controllando il titolo o leggendo il link canonico della pagina (descritto più avanti). - `search.list` restituisce risultati ordinati per una query, non una ricerca esatta, e il suo limite predefinito è di 100 chiamate al giorno. Evitalo per convertire identificatori.
Da ID canale a handle, 50 alla volta
Per la direzione opposta si usa il filtro id, che accetta una lista di ID canale separati da virgole. Richiedi part=snippet e leggi snippet.customUrl. channelsById visto sopra elimina i duplicati, invia gli ID in blocchi da 50 e restituisce una mappa con chiave l’ID. Gli ID che non compaiono nella mappa non sono stati trovati, sono stati eliminati o non sono visibili all’API.
- Dimensione del batch. La documentazione descrive
idcome una lista separata da virgole e limitamaxResultsa 50. Non indica un limite separato per il numero di ID, quindi il codice ne invia 50 per chiamata e impostamaxResults=50, così il valore predefinito di 5 non può mai troncare un batch. - Ordine. Non dare per scontato che i risultati tornino nell’ordine in cui li hai inviati. Indicizza la risposta per
id. - `customUrl` è facoltativo. La documentazione lo descrive solo come l’URL personalizzato del canale, e una nota di revisione del 2016 dice che non tutti i canali ne hanno uno. Consideralo nullable, togli un’eventuale
@iniziale e verifica il formato su alcuni canali che conosci prima di costruirci sopra. - I titoli non sono handle.
snippet.titleè il nome visualizzato. Non è unico e può contenere spazi ed emoji.
Nessun risultato, errori e cache
Una ricerca senza corrispondenze non è un errore. Per forHandle e forUsername la risposta semplicemente non ha elementi, quindi controlla se l’array items è vuoto o assente. Gli errori documentati per channels.list sono questi:
| Motivo dell’errore | HTTP | Quando succede | Cosa fare |
|---|---|---|---|
Nessun items in una risposta 200 | 200 | L’handle o lo username non esiste | Restituisci null; metti in cache il mancato risultato per poco tempo |
channelNotFound | 404 | Un canale nel parametro id non si trova | Trattalo come non trovato, come fanno le funzioni sopra |
invalidCriteria | 400 | Più di un filtro nella richiesta | Invia esattamente uno tra forHandle, forUsername o id |
channelForbidden | 403 | Il canale non supporta la richiesta o la richiesta non è autorizzata correttamente | Registralo nei log e salta il canale |
quotaExceeded | 403 | Il progetto ha esaurito la quota giornaliera | Fermati e riprova dopo mezzanotte, ora del Pacifico, oppure richiedi più quota |
badRequest (API key not valid) | 400 | Chiave mancante o errata | Correggi la configurazione; non riprovare |
Metti in cache i risultati per risparmiare quota e latenza, ma rispetta le regole di YouTube sulla conservazione dei dati. Le YouTube API Services Developer Policies (sezione III.E.4) dicono che puoi conservare i dati API non autorizzati per non più di 30 giorni di calendario, dopo i quali devi eliminarli o aggiornarli. Una cache con un TTL ben sotto i 30 giorni, con chiave l’handle in minuscolo, offre gran parte del vantaggio:
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;
}In produzione, metti la stessa logica in Redis o in una tabella di database con una colonna expires_at, e dai ai mancati risultati un TTL più breve rispetto ai risultati trovati, così un handle appena creato viene rilevato in fretta.
L’alternativa senza chiave API: leggere la pagina del canale
Le pagine pubbliche dei canali includono l’ID del canale nel loro HTML. Una richiesta a https://www.youtube.com/@GoogleDevelopers oggi restituisce una pagina il cui <link rel="canonical"> punta a https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw, e lo stesso vale per le pagine /c/ e /user/: per questo è l’unico modo per convertire un nome /c/ senza tirare a indovinare.
// 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 NoneUsalo con parsimonia e solo dove l’API non può aiutarti. È non ufficiale e fragile:
- Il markup non è un’interfaccia documentata e può cambiare in qualsiasi momento.
- YouTube può mostrare pagine di consenso, limiti di frequenza o un CAPTCHA alle richieste automatiche, soprattutto da intervalli di IP cloud. Nei nostri test, seguire un link
youtu.beda un server cloud finiva su una pagina CAPTCHA di Google. - L’accesso automatico al sito può essere in contrasto con i Termini di servizio di YouTube. Leggili e preferisci l’API ufficiale.
Confronto tra gli approcci
| Approccio | Input | Costo | Pro | Contro |
|---|---|---|---|---|
channels.list con forHandle | Handle, con o senza @ | 1 unità per handle | Ufficiale, corrispondenza esatta, funziona con qualsiasi handle | Un handle per chiamata |
channels.list con forUsername | Nome legacy /user/ | 1 unità per nome | Ufficiale, corrispondenza esatta | Solo per username legacy |
channels.list con id | Fino a 50 ID canale | 1 unità ogni 50 ID | Ufficiale, a batch, restituisce titolo e customUrl | customUrl può mancare |
search.list | Testo libero | Limite separato di 100 chiamate al giorno per impostazione predefinita | Trova canali per nome | Ipotesi ordinate, non esatte; poche chiamate disponibili |
| Link canonico della pagina del canale | Qualsiasi URL di canale, anche /c/ | Nessuna quota API | Funziona senza chiave; converte i nomi /c/ | Non ufficiale, fragile, può essere bloccato, può violare i termini |
Per la maggior parte dei prodotti la configurazione giusta è semplice: converti gli handle con forHandle, salva l’ID del canale, aggiorna gli handle a blocchi di 50 con il filtro id e tieni l’alternativa della pagina dietro un flag per i rari link /c/. Se ti servono anche gli account del creator sulle altre piattaforme, sociallinks.dev è un’API che prende un URL di profilo Instagram, TikTok, YouTube o X e restituisce gli account del creator sulle altre piattaforme, con un punteggio di affidabilità e le prove di ogni collegamento.
Domande frequenti
Serve la @ quando passo un handle a forHandle?
No. La documentazione di channels.list dice che il valore di forHandle può essere preceduto dal simbolo @, e riporta come esempi sia GoogleDevelopers sia @GoogleDevelopers.
Quanta quota consuma una ricerca per handle?
Un’unità. Ogni chiamata channels.list costa 1 unità, e l’allocazione predefinita è di 10.000 unità al giorno, azzerate a mezzanotte, ora del Pacifico. Anche le richieste non valide costano almeno 1 unità.
Posso cercare più handle in una sola chiamata?
No. forHandle accetta un solo handle. Solo il filtro id accetta una lista separata da virgole, quindi usa i batch nella direzione inversa (da ID a handle) e la cache nella direzione diretta.
L’ID di un canale cambia mai?
L’ID del canale è l’identificatore che YouTube usa per identificare il canale in modo univoco. Cambiare l’handle o il titolo non lo modifica. Per questo è la chiave primaria giusta. Handle, titoli e URL personalizzati possono invece cambiare.
Perché snippet.customUrl torna vuoto?
Non tutti i canali hanno un URL personalizzato, come indica la cronologia delle revisioni di YouTube. Considera il campo facoltativo e, in alternativa, mostra l’URL /channel/UC....
Per quanto tempo posso tenere in cache i risultati?
Le Developer Policies di YouTube limitano i dati API conservati a 30 giorni di calendario, dopo i quali devi eliminarli o aggiornarli. Un TTL di una settimana o meno per i risultati trovati, e più breve per i mancati risultati, ti tiene ben dentro questo limite.