Blog / Guide développeur
Comment obtenir l’ID d’une chaîne YouTube à partir d’un pseudo (et inversement)
Les créateurs partagent leur chaîne YouTube sous la forme @handle, mais presque tout dans l’API YouTube Data repose sur l’ID de chaîne. Si vous ne stockez que les pseudos, un changement de nom casse vos données ; si vous ne stockez que les ID, vous ne pouvez pas montrer aux utilisateurs le nom qu’ils reconnaissent. Ce guide montre comment convertir un pseudo en ID de chaîne en un seul appel d’API, comment repasser des ID aux pseudos par lots de 50, que faire des anciennes URL /user/ et /c/, et comment rester dans le quota par défaut.
Pseudos, ID de chaîne et anciens noms
Une chaîne YouTube peut être atteinte par jusqu’à quatre types d’identifiants. Un seul est garanti stable.
| Identifiant | Exemple | Forme d’URL | Remarques |
|---|---|---|---|
| ID de chaîne | UC_x5XG1OV2P6uZZ5FSM9Ttw | youtube.com/channel/UC... | L’ID que YouTube utilise pour identifier la chaîne. Sensible à la casse. Utilisez-le comme clé primaire. |
| Pseudo (handle) | @GoogleDevelopers | youtube.com/@GoogleDevelopers | Unique, insensible à la casse, et le titulaire peut le changer dans YouTube Studio. |
| Ancien nom d’utilisateur | GoogleDevelopers | youtube.com/user/GoogleDevelopers | Issu de l’ancien système de noms d’utilisateur. Se résout avec le filtre forUsername de l’API. |
| Ancienne URL personnalisée | GoogleDevelopers | youtube.com/c/GoogleDevelopers | Anciennes URL personnalisées, qui selon YouTube continuent de fonctionner. Aucun filtre de l’API ne les résout. |
Stockez l’ID de chaîne comme clé et gardez le pseudo comme valeur d’affichage que vous mettez à jour. La suite de ce guide explique comment passer de ce que l’utilisateur vous a donné à cet ID.
Créer une clé API et comprendre le quota
- Step 1Créez un projet Google Cloud
Dans la console Google Cloud, créez un projet ou choisissez-en un existant.
- Step 2Activez l’API YouTube Data v3
Ouvrez API et services, trouvez YouTube Data API v3 dans la bibliothèque et activez-la pour le projet.
- Step 3Créez une clé API
Dans Identifiants, créez une clé API. Les données publiques de chaîne, comme
channels.listpar pseudo ou par ID, ne demandent qu’une clé, pas OAuth. Limitez la clé à l’API YouTube Data. - Step 4Gardez la clé hors de votre code
Placez-la dans une variable d’environnement, par exemple
YOUTUBE_API_KEY, et lisez-la à l’exécution. C’est exactement ce que font les exemples ci-dessous. - Step 5Vérifiez votre quota
Dans la console, la page Quotas de l’API affiche votre allocation quotidienne et votre consommation.
Les chiffres qui comptent, d’après la documentation de Google :
- Un appel
channels.listcoûte 1 unité, quel que soit le filtre utilisé. - Les projets reçoivent par défaut 10 000 unités par jour pour tous les points d’accès autres que
search.listetvideos.insert, qui ont désormais leurs propres limites de 100 appels par jour chacun. - Chaque requête coûte au moins 1 unité, y compris les requêtes invalides.
- Les quotas quotidiens sont réinitialisés à minuit, heure du Pacifique.
Le quota par défaut couvre donc environ 10 000 recherches de pseudos par jour, et jusqu’à 50 fois plus de recherches par ID, car un appel peut récupérer 50 chaînes par ID pour la même unité.
Du pseudo à l’ID de chaîne avec forHandle
channels.list accepte exactement un filtre. Le filtre forHandle prend un pseudo YouTube, et la référence indique que la valeur peut être précédée d’un symbole @ : GoogleDevelopers et @GoogleDevelopers fonctionnent tous les deux. Demandez part=id si vous n’avez besoin que de l’ID ; la réponse est plus petite pour le même coût.
curl -s "https://www.googleapis.com/youtube/v3/channels?part=id&forHandle=@GoogleDevelopers&key=$YOUTUBE_API_KEY"L’exemple Node ci-dessous encapsule la requête dans une fonction utilitaire, listChannels, que le reste du guide réutilise. Elle renvoie une liste vide quand rien ne correspond et lève une exception sur les vraies erreurs, comme une clé invalide ou un quota épuisé.
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 foundTransmettez les pseudos tels que l’utilisateur les a écrits, après avoir décodé l’éventuel encodage en pourcentage de l’URL. Les pseudos peuvent utiliser des lettres de nombreux alphabets, pas seulement l’ASCII, et URLSearchParams comme requests encodent le paramètre pour vous.
Anciens noms d’utilisateur et URL personnalisées /c/
Les anciens liens dans les bios des créateurs et dans les tableurs utilisent souvent l’une des formes antérieures aux pseudos.
- `youtube.com/user/<name>` : passez le nom à
forUsernameavecchannelIdFromUsernameci-dessus. Cela coûte 1 unité, comme tout autre appelchannels.list. - `youtube.com/c/<name>` : il n’existe aucun filtre
channels.listpour les URL personnalisées. Beaucoup de chaînes ont choisi un pseudo identique à leur ancien nom personnalisé, donc essayerforHandleavec la même chaîne peut fonctionner, mais une correspondance ne prouve pas qu’il s’agit de la même chaîne. Confirmez-le autrement avant de vous y fier, par exemple en vérifiant le titre ou en lisant le lien canonique de la page (décrit plus bas). - `search.list` renvoie des résultats classés pour une requête, pas une recherche exacte, et sa limite par défaut est de 100 appels par jour. Évitez-le pour résoudre des identifiants.
De l’ID de chaîne au pseudo, 50 à la fois
Dans l’autre sens, on utilise le filtre id, qui prend une liste d’ID de chaîne séparés par des virgules. Demandez part=snippet et lisez snippet.customUrl. channelsById ci-dessus dédoublonne les ID, les envoie par lots de 50 et renvoie une table indexée par ID. Les ID absents de la table n’ont pas été trouvés, ont été supprimés ou ne sont pas visibles par l’API.
- Taille des lots. La référence décrit
idcomme une liste séparée par des virgules et plafonnemaxResultsà 50. Elle n’indique pas de limite distincte pour le nombre d’ID : le code en envoie donc 50 par appel et fixemaxResults=50pour que la valeur par défaut de 5 ne tronque jamais un lot. - Ordre. Ne supposez pas que les résultats reviennent dans l’ordre d’envoi. Indexez la réponse par
id. - `customUrl` est facultatif. La référence le décrit seulement comme l’URL personnalisée de la chaîne, et une note de révision de 2016 indique que toutes les chaînes n’en ont pas. Traitez-le comme pouvant être nul, retirez un
@initial s’il y en a un, et vérifiez son format sur quelques chaînes que vous connaissez avant de bâtir dessus. - Les titres ne sont pas des pseudos.
snippet.titleest le nom affiché. Il n’est pas unique et peut contenir des espaces et des emojis.
Introuvables, erreurs et cache
Une recherche sans correspondance n’est pas une erreur. Pour forHandle et forUsername, la réponse ne contient simplement aucun élément : vérifiez donc si le tableau items est vide ou absent. Voici les erreurs documentées pour channels.list :
| Raison de l’erreur | HTTP | Quand elle survient | Que faire |
|---|---|---|---|
Pas de items dans une réponse 200 | 200 | Le pseudo ou le nom d’utilisateur n’existe pas | Renvoyer null ; mettre l’échec en cache pour une courte durée |
channelNotFound | 404 | Une chaîne du paramètre id est introuvable | La traiter comme introuvable, comme le font les fonctions ci-dessus |
invalidCriteria | 400 | Plus d’un filtre dans la requête | Envoyer un seul filtre parmi forHandle, forUsername ou id |
channelForbidden | 403 | La chaîne ne prend pas en charge la requête, ou celle-ci n’est pas correctement autorisée | La journaliser et ignorer la chaîne |
quotaExceeded | 403 | Votre projet a épuisé son quota quotidien | Arrêter et réessayer après minuit, heure du Pacifique, ou demander plus de quota |
badRequest (clé API invalide) | 400 | Clé manquante ou erronée | Corriger la configuration ; ne pas réessayer |
Mettez les résultats en cache pour économiser du quota et de la latence, mais respectez les règles de YouTube sur le stockage des données. Les YouTube API Services Developer Policies (section III.E.4) indiquent que vous pouvez stocker les données d’API non autorisées pendant 30 jours calendaires au maximum, après quoi vous devez les supprimer ou les mettre à jour. Un cache avec une durée de vie bien inférieure à 30 jours, indexé par le pseudo en minuscules, apporte l’essentiel du gain :
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 production, placez la même logique dans Redis ou dans une table de base de données avec une colonne expires_at, et donnez aux échecs une durée de vie plus courte qu’aux réussites pour qu’un pseudo nouvellement créé soit vite pris en compte.
La solution sans clé API : lire la page de la chaîne
Les pages publiques des chaînes contiennent l’ID de chaîne dans leur HTML. Une requête vers https://www.youtube.com/@GoogleDevelopers renvoie actuellement une page dont le <link rel="canonical"> pointe vers https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw, et c’est aussi le cas des pages /c/ et /user/, ce qui en fait le seul moyen de résoudre un nom /c/ sans deviner.
// 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 NoneUtilisez-la avec parcimonie, et seulement là où l’API ne peut pas aider. Elle est non officielle et fragile :
- Le balisage n’est pas une interface documentée et peut changer à tout moment.
- YouTube peut renvoyer des pages de consentement, des limites de débit ou un CAPTCHA aux requêtes automatisées, surtout depuis des plages d’IP cloud. Lors de nos tests, suivre un lien
youtu.bedepuis un serveur cloud a abouti à une page CAPTCHA de Google. - L’accès automatisé au site peut contrevenir aux conditions d’utilisation de YouTube. Lisez-les et préférez l’API officielle.
Comparaison des approches
| Approche | Entrée | Coût | Avantages | Inconvénients |
|---|---|---|---|---|
channels.list avec forHandle | Pseudo, avec ou sans @ | 1 unité par pseudo | Officielle, correspondance exacte, fonctionne pour tout pseudo | Un pseudo par appel |
channels.list avec forUsername | Ancien nom /user/ | 1 unité par nom | Officielle, correspondance exacte | Uniquement pour les anciens noms d’utilisateur |
channels.list avec id | Jusqu’à 50 ID de chaîne | 1 unité pour 50 ID | Officielle, par lots, renvoie le titre et customUrl | customUrl peut manquer |
search.list | Texte libre | Limite distincte de 100 appels par jour par défaut | Trouve les chaînes par nom | Suppositions classées, pas exactes ; faible allocation |
| Lien canonique de la page de la chaîne | Toute URL de chaîne, y compris /c/ | Aucun quota d’API | Fonctionne sans clé ; résout les noms /c/ | Non officielle, fragile, peut être bloquée, peut contrevenir aux conditions |
Pour la plupart des produits, la bonne configuration est simple : résolvez les pseudos avec forHandle, stockez l’ID de chaîne, mettez à jour les pseudos par lots de 50 avec le filtre id, et gardez la lecture de page derrière une option pour les rares liens /c/. S’il vous faut aussi les comptes du créateur sur d’autres plateformes, sociallinks.dev est une API qui prend une URL de profil Instagram, TikTok, YouTube ou X et renvoie les comptes du créateur sur les autres plateformes, avec un score de confiance et la preuve de chaque correspondance.
FAQ
Faut-il le @ quand je passe un pseudo à forHandle ?
Non. La référence de channels.list indique que la valeur de forHandle peut être précédée d’un symbole @, et donne en exemple à la fois GoogleDevelopers et @GoogleDevelopers.
Combien de quota consomme une recherche de pseudo ?
Une unité. Chaque appel channels.list coûte 1 unité, et l’allocation par défaut est de 10 000 unités par jour, réinitialisée à minuit, heure du Pacifique. Les requêtes invalides coûtent aussi au moins 1 unité.
Puis-je rechercher plusieurs pseudos en un seul appel ?
Non. forHandle prend un seul pseudo. Seul le filtre id accepte une liste séparée par des virgules : regroupez donc le sens inverse (des ID aux pseudos) en lots et mettez en cache le sens direct.
Un ID de chaîne change-t-il un jour ?
L’ID de chaîne est l’identifiant que YouTube utilise pour identifier la chaîne de façon unique. Changer le pseudo ou le titre ne le modifie pas. C’est pourquoi c’est la bonne clé primaire. Les pseudos, les titres et les URL personnalisées peuvent tous changer.
Pourquoi snippet.customUrl revient-il vide ?
Toutes les chaînes n’ont pas d’URL personnalisée, comme le note l’historique des révisions de YouTube. Traitez le champ comme facultatif et affichez à défaut l’URL /channel/UC....
Combien de temps puis-je garder les résultats en cache ?
Les Developer Policies de YouTube limitent le stockage des données d’API à 30 jours calendaires avant suppression ou mise à jour. Une durée de vie d’une semaine ou moins pour les réussites, et plus courte pour les échecs, vous garde largement dans cette limite.