Trois endpoints, un en-tête d'authentification, un corps JSON. L'API d'un raccourcisseur d'URL est l'une des intégrations les plus simples de tout backlog, et le guide de démarrage rapide vous donne un lien court fonctionnel en quelques minutes. Ce que le guide passe sous silence, c'est tout ce qui se produit quand l'intégration tourne à plein régime : le limiteur de débit qui repousse, un 503 transitoire au milieu d'un traitement par lots, une file de tâches qui livre le même message deux fois. Ratez ces points et vous vous retrouvez avec des liens en double, du travail perdu et une tempête de 429 qui empire les choses.
Cet article est le complément de durcissement pour la production du guide de démarrage rapide API. Il couvre les trois mécaniques qui séparent une démo d'une intégration fiable : les limites de débit et comment s'y adapter, quelles erreurs relancer et comment reculer, et les clés d'idempotence qui empêchent une relance de créer un second lien. Les exemples utilisent l'API Elido, mais les patterns sont identiques face à toute API de raccourcissement de lien bien construite. Si vous traitez les liens courts comme une infrastructure gérée par du code, l'argument général est développé dans les liens courts comme Terraform.
Limites de débit : un seau à jetons et un en-tête Retry-After
Elido régule l'API avec un seau à jetons, scopé par espace de travail. Les débits soutenus sont de 10 requêtes par seconde en Free, 30 en Pro et 100 en Business, avec des capacités de rafale de 20, 60 et 200. En Pro, par exemple, un seau plein vous laisse envoyer 60 requêtes d'un coup avant que le débit ne retombe aux 30 par seconde soutenues. Un seau distinct par clé d'API s'ajoute par-dessus, si bien qu'une clé bruyante ne peut pas avaler tout l'espace de travail. La plupart des jobs de création de liens tiennent dans la rafale et ne sentent jamais la limite.
Inutile de deviner quand une limite saute. Une requête rejetée porte trois signaux :
Retry-After- le nombre de secondes à attendre avant de réessayer (actuellement1).X-RateLimit-Scope- quel seau a sauté :workspaceouapi_key.- Le corps JSON -
{"error":"workspace rate limit exceeded"}, qui nomme la limite.
Il n'existe pas d'en-tête de jetons restants, donc réglez le rythme côté client : gardez la concurrence au niveau du débit soutenu de votre plan, et au premier 429 arrêtez-vous et attendez Retry-After plutôt que de foncer dans un mur de rejets. Un rythme anticipé garde le débit lisse ; relancer à l'aveugle après chaque rejet gaspille des allers-retours et, si tous les clients relancent au même instant, fabrique un effet de troupeau.
Quand vous devez créer beaucoup de liens, ne bouclez pas sur l'endpoint de création unitaire. POST /v1/workspaces/{workspace_id}/links/bulk accepte jusqu'à 100 liens par requête. Un appel bulk déplace une centaine de liens en un aller-retour, là où cent appels unitaires dépenseraient cent jetons et l'essentiel de votre rafale. C'est ainsi que l'import Google Sheets déplace les liens d'une campagne sans déclencher le limiteur.
Un 429 Too Many Requests - le code que la RFC 6585 réserve exactement à cet usage - revient avec un en-tête Retry-After qui indique combien de secondes attendre. Respectez-le. Ce nombre, c'est le limiteur qui vous dit quand un jeton sera disponible, ce qui vaut mieux que toute estimation de votre backoff.
Relances : quels codes, et comment reculer
Toute erreur ne mérite pas d'être relancée, et relancer la mauvaise, c'est transformer un petit incident en panne. Classez les réponses en deux catégories.
Relancez celles-ci, car elles sont transitoires : 429 (vous étiez trop rapide), et 500, 502, 503, 504 (une défaillance côté serveur ou passerelle qui peut se résoudre seule). Ne relancez pas celles-ci, car la même requête échouera à l'identique : 400 (la charge utile est invalide), 401 (le jeton est absent ou incorrect), 403 (le jeton n'a pas la portée requise), 404 (la ressource n'existe pas ou ne vous appartient pas), et 409 (un conflit de slug ou une modification à version obsolète). La première catégorie, c'est « attendez et réessayez ». La seconde, c'est « corrigez le code ou l'entrée ». Relancer un 400 en boucle serrée transforme simplement un bug en attaque par déni de service contre vous-même.
Pour les codes relançables, l'algorithme qui compte est le backoff exponentiel avec jitter. Le backoff exponentiel seul - doubler le délai à chaque tentative - synchronise quand même les clients, car tous ceux qui ont échoué au même moment relancent aussi aux mêmes instants. Ajouter de l'aléatoire les étale. L'article d'AWS sur le backoff exponentiel et le jitter est la référence canonique et montre pourquoi la version avec jitter réduit drastiquement la contention. Une version compacte en TypeScript :
const RETRYABLE = new Set([429, 500, 502, 503, 504]);
async function withRetry<T>(
call: () => Promise<Response>,
max = 5,
): Promise<Response> {
let attempt = 0;
while (true) {
const res = await call();
if (res.ok || !RETRYABLE.has(res.status) || attempt >= max) return res;
// Honor server guidance first; otherwise back off exponentially with full jitter.
const retryAfter = Number(res.headers.get("retry-after"));
const base =
Number.isFinite(retryAfter) && retryAfter > 0
? retryAfter * 1000
: Math.min(1000 * 2 ** attempt, 20_000);
const wait = Math.random() * base; // full jitter
await new Promise((r) => setTimeout(r, wait));
attempt++;
}
}
Trois choses rendent cela sûr plutôt que dangereux. Le nombre de tentatives est plafonné, donc une défaillance persistante échoue bruyamment au lieu de tourner indéfiniment. L'en-tête Retry-After est respecté quand le serveur l'envoie, et le backoff calculé n'est utilisé qu'en son absence. Et le jitter est appliqué, pour qu'une flotte de workers se remettant du même incident ne charge pas en cadence. Seul le SDK Go est publié à ce jour ; les SDK TypeScript, Python et Ruby ne sont pas encore sur leurs registres de paquets, donc dans ces langages ce petit wrapper au-dessus du HTTP brut est tout le client dont vous avez besoin.
Il y a une règle qui relie les relances à la section suivante : relancer une création n'est sûr que si la création est idempotente. Sinon, chaque relance risque de créer un second lien.
Idempotence : comment ne pas créer de liens en double
L'échec classique ressemble à ceci. Votre worker crée un lien court, le lien est créé, mais le 200 ne revient jamais - la connexion tombe au retour. Le worker voit un timeout, suppose un échec et relance. Vous avez maintenant deux liens pour une campagne. À l'échelle, le tableau de bord se remplit de /foo, /foo-1, /foo-2, et les doublons faussent tous les rapports en aval.
Les clés d'idempotence comblent cette lacune. Envoyez un en-tête Idempotency-Key sur une requête mutante - n'importe quelle chaîne jusqu'à 255 caractères - et le serveur stocke la réponse associée. Présentez la même clé à nouveau et vous obtenez la réponse originale, code de statut et corps inclus, sans que l'opération s'exécute deux fois. Le pattern est le même que celui que Stripe documente pour les requêtes idempotentes, et c'est la façon standard de rendre un réseau non fiable sûr pour les écritures.
Le détail qui fait ou défait tout, c'est l'origine de la clé. Ne générez pas une clé aléatoire par tentative - cela annule l'intérêt, car chaque relance ressemble alors à une nouvelle opération. Dérivez-la d'un identifiant métier stable pour que la même action logique produise toujours la même clé :
const res = await withRetry(() =>
fetch(`https://api.elido.app/v1/workspaces/${WORKSPACE_ID}/links`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ELIDO_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": `order-${order.id}-link`,
},
body: JSON.stringify({ destination_url: order.landingUrl }),
}),
);
const link = await res.json();
Désormais, une relance du même travail porte à nouveau order-12345-link, atteint la réponse stockée et retourne le lien qui existe déjà. Exactement un lien par commande, peu importe combien de fois la file relivère le message. C'est ce qui vous permet de combiner la boucle de backoff ci-dessus avec des créations en toute sécurité : la relance et la clé d'idempotence sont deux moitiés de la même garantie.
Deux limites à garder en tête. La clé est scopée par espace de travail : la même clé dans deux espaces de travail crée deux liens, ce qui est correct pour une API multi-tenant mais surprend les équipes qui supposent que les clés sont globales. Et le cache n'est pas permanent - sur Elido il dure 24 heures, scopé sur (workspace, key). Une relance dans la fenêtre déduplique ; une relance trois jours plus tard, d'un travail bloqué qui a finalement été traité, créera un nouveau lien. Pour les traitements par lots sur plusieurs jours, ne comptez pas uniquement sur la clé. Persistez l'ID du lien retourné au premier succès et consultez-le avant de réémettre la création. L'IETF standardise cet en-tête dans le brouillon Idempotency-Key, et la mise en garde sur la fenêtre de 24 heures y est également mentionnée.
Si vous câblez une intégration API aujourd'hui et voulez qu'elle survive à ses propres relances, commencez avec un espace de travail gratuit, générez un jeton de compte de service, et mettez une clé d'idempotence sur votre toute première création plutôt que d'en ajouter une après l'apparition des doublons.
Tout assembler
Un appel de création de qualité production, c'est les trois mécaniques empilées. Calez le client sur le débit soutenu de votre plan pour atteindre rarement un 429. Encapsulez l'appel dans un backoff jitterisé qui ne relance que les codes transitoires et respecte Retry-After. Portez une clé d'idempotence dérivée d'un ID métier pour que la relance soit sûre. Avec un simple fetch, le wrapper ci-dessus couvre les deux premiers et vous ne fournissez que la clé :
export async function shortenForOrder(order: Order) {
const res = await withRetry(() =>
fetch(`https://api.elido.app/v1/workspaces/${WORKSPACE_ID}/links`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ELIDO_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": `order-${order.id}-link`,
},
body: JSON.stringify({
destination_url: order.landingUrl,
tags: [`order:${order.id}`],
}),
}),
);
if (res.status === 429) {
// withRetry gave up and we are still limited. Defer the job.
throw new RetryableJobError(Number(res.headers.get("retry-after")) || 1);
}
if (!res.ok) throw new Error(`create failed: ${res.status}`); // non-retryable
return res.json();
}
Rien de tout cela n'est exotique. C'est la même discipline que mérite toute API à forte activité d'écriture, appliquée aux liens. La récompense est une intégration qui fait ce qu'il faut sous charge au lieu de corrompre silencieusement votre inventaire de liens. Pour le versant lecture de la même API - récupérer les données de clics sans marteler le limiteur - les compromis sont dans webhooks versus polling pour le suivi des clics, et la surface complète des endpoints se trouve sur la page API et SDK et la vue d'ensemble des solutions développeurs.
Sur le blog
- API de raccourcissement d'URL : démarrage rapide en 30 minutes dans cinq langages
- Liens courts comme Terraform : gérer les liens comme du code
- Webhooks versus polling pour le suivi des clics
- Importer des campagnes en masse depuis Google Sheets
- Connecter l'API à Claude et Cursor via MCP
- API gratuite de raccourcissement d'URL : structure des requêtes, authentification et limites
Questions fréquentes
Quelles sont les limites de débit d'une API de raccourcissement d'URL ?
Sur Elido, les limites s'appliquent par espace de travail et dépendent du plan, avec un seau à jetons : 10 requêtes par seconde en Free (rafale de 20), 30 en Pro (rafale de 60) et 100 en Business (rafale de 200). Un seau distinct par clé d'API s'ajoute par-dessus. Quand une limite saute, l'API renvoie 429 avec les en-têtes Retry-After et X-RateLimit-Scope, qui indique quel seau est atteint, ce qui permet au client de savoir combien de temps patienter. La création en masse accepte jusqu'à 100 liens par requête, donc l'endpoint bulk est l'outil adapté aux gros traitements.
Quelles erreurs API dois-je relancer ?
Ne relancez que les erreurs transitoires : 429 (limite de débit atteinte), 500, 502, 503 et 504. Utilisez un backoff exponentiel avec jitter et un plafond de tentatives. Ne relancez jamais un 400, 401, 403, 404 ou 409 - ce sont des échecs de validation, des problèmes d'authentification ou des conflits, et la même requête échouera à nouveau. Associez les relances à une clé d'idempotence pour éviter qu'une création relancée ne produise un lien en double.
Qu'est-ce qu'une clé d'idempotence et quand en ai-je besoin ?
Une clé d'idempotence est une chaîne opaque que vous envoyez sur une requête mutante pour que le serveur puisse reconnaître une relance et retourner le résultat original au lieu d'agir deux fois. Vous en avez besoin dès qu'une création ou mise à jour peut être relancée - c'est-à-dire toujours, car les réseaux tombent et les files de tâches livrent au minimum une fois. Dérivez la clé d'un identifiant métier stable comme un ID de commande, pour que la même opération logique porte toujours la même clé.
Comment éviter de créer des liens courts en double ?
Envoyez une clé d'idempotence dérivée d'un identifiant métier stable sur chaque création. Si l'appel est relancé, le serveur retourne le lien créé initialement plutôt que d'en générer un second. Pour les tâches qui peuvent être relancées plusieurs jours plus tard - au-delà de la fenêtre de cache d'idempotence - stockez aussi l'ID du lien retourné par le premier succès et consultez-le avant de réémettre la création.
Combien de temps dure une clé d'idempotence ?
Sur Elido, le cache d'idempotence dure 24 heures, scopé sur l'espace de travail plus la clé. Une relance dans cette fenêtre retourne la réponse stockée ; une relance au-delà est traitée comme une nouvelle requête et créera une nouvelle ressource. Pour les traitements par lots sur plusieurs jours, persistez l'ID du lien au premier succès plutôt que de vous fier uniquement à la clé.
Essayer Elido
Collez une URL, obtenez un lien court
Sans inscription. Lien actif 30 jours. Inscrivez-vous pour le garder pour toujours.
Sans inscription. Créez un compte gratuit pour 50 liens par mois.