Une API de réducteur d'URL est l'une des intégrations les plus modestes dans le backlog d'une équipe d'ingénierie typique. Trois points de terminaison (endpoints), un en-tête d'authentification, une charge utile JSON. La page de documentation promet le premier appel en cinq minutes. Puis le trafic de production arrive, la logique de réessai crée des liens en double, le tableau de bord se remplit de variantes /foo-1, /foo-2, /foo-3 de la même destination, et quelqu'un ouvre un ticket.
Cet article détaille l'intégration réelle. L'authentification, le premier appel, les quatre points de terminaison qui couvrent la plupart des cas d'utilisation, l'idempotence, la gestion des erreurs, les limites de débit et les pièges de production que le guide de démarrage rapide de cinq minutes ignore. Exemples de code en TypeScript, Python, Go, Ruby et PHP — les trois premiers via les SDK officiels (@elido/sdk, elido-python, github.com/elido/elido-go), les deux derniers via des clients HTTP classiques.
Pré-requis#
Connectez-vous au tableau de bord, accédez à /settings/api et créez un jeton d'accès personnel (personal access token). Les jetons sont limités au périmètre de l'espace de travail (workspace) — un jeton émis dans l'espace de travail A ne peut pas créer de liens dans l'espace de travail B. Les jetons de compte de service (pour les systèmes de CI, l'outillage interne, l'intégration machine-à-machine) sont créés sur le même écran pour les forfaits Pro et supérieurs ; ils ont des portées (scopes) explicites (links:write, analytics:read, domains:write) et tournent indépendamment des jetons personnels.
L'URL de base est https://api.elido.app/v1. Les domaines de redirection (f.elido.me, s.elido.me, b.elido.me) sont distincts de la surface de l'API. Vos liens courts se résolvent sur le domaine de redirection ; l'API sert à les créer, les modifier et les lire.
La spécification OpenAPI est publiée sur https://api.elido.app/v1/openapi.json et respecte OpenAPI 3.1. Les SDK officiels sont générés à partir de cette spécification et republiés à chaque mise à jour de l'API ; vous pouvez également générer votre propre client dans n'importe quel langage supportant OpenAPI.
Le premier appel#
Créez un lien court à partir de l'URL de destination. Cinq lignes en TypeScript :
import { Elido } from "@elido/sdk";
const elido = new Elido({ token: process.env.ELIDO_TOKEN! });
const link = await elido.links.create({
destinationUrl: "https://shop.example.com/spring-sale",
});
console.log(link.shortUrl); // https://s.elido.me/abc123
Python :
from elido import Elido
client = Elido(token=os.environ["ELIDO_TOKEN"])
link = client.links.create(
destination_url="https://shop.example.com/spring-sale",
)
print(link.short_url) # https://s.elido.me/abc123
Go :
import "github.com/elido/elido-go/v2/elido"
client := elido.NewClient(elido.WithToken(os.Getenv("ELIDO_TOKEN")))
link, err := client.Links.Create(ctx, &elido.LinkCreateInput{
DestinationURL: "https://shop.example.com/spring-sale",
})
if err != nil {
return fmt.Errorf("create link: %w", err)
}
fmt.Println(link.ShortURL)
Ruby (pas de SDK officiel — utilisation de net/http) :
require "net/http"
require "json"
uri = URI("https://api.elido.app/v1/links")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{ENV['ELIDO_TOKEN']}"
req["Content-Type"] = "application/json"
req.body = { destination_url: "https://shop.example.com/spring-sale" }.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
link = JSON.parse(res.body)
puts link["short_url"]
PHP (Guzzle) :
$client = new GuzzleHttp\Client(['base_uri' => 'https://api.elido.app/v1/']);
$res = $client->post('links', [
'headers' => ['Authorization' => 'Bearer ' . getenv('ELIDO_TOKEN')],
'json' => ['destination_url' => 'https://shop.example.com/spring-sale'],
]);
$link = json_decode((string) $res->getBody(), true);
echo $link['short_url'];
Les cinq produisent le même résultat. Le corps de la réponse contient l'URL courte, l'ID canonique du lien, l'ID de l'espace de travail et l'horodatage de création. Le slug — abc123 dans l'exemple ci-dessus — est généré par le serveur à moins que vous ne passiez custom_slug dans la requête. L'alphabet du slug est base62 ([0-9A-Za-z]) ; la longueur par défaut est de six caractères.
Les quatre points de terminaison que vous utiliserez réellement#
L'API possède plus de quatre points de terminaison, mais la plupart des intégrations se limitent à cet ensemble.
Créer un lien#
POST /v1/links accepte l'URL de destination ainsi que des champs optionnels :
custom_slug— un slug de votre choix (doit être unique dans l'espace de travail).domain_id— pour les liens avec domaine personnalisé ; le domaine principal de l'espace de travail est utilisé s'il est omis.tags— un tableau de chaînes libres pour l'organisation.utm— paramètres de campagne à ajouter à la destination au moment de la redirection.expires_at— horodatage ISO 8601 après lequel le lien renvoie 410 Gone.password— si défini, la redirection affiche une page de mot de passe avant le transfert.metadata— objet JSON opaque que la redirection n'interprète pas ; utile pour vos propres clés de jointure.
Le slug personnalisé est le champ qui pose problème aux équipes en production. Si vous passez un slug déjà utilisé par un autre lien dans le même espace de travail, l'API renvoie une erreur 409 Conflict. Le gestionnaire de réessai naïf qui ajoute un compteur (my-slug-1, my-slug-2) produit le problème de liens en double décrit en introduction. Le comportement de réessai correct est décrit dans la section sur l'idempotence ci-dessous.
Lire un lien#
GET /v1/links/{id} renvoie l'enregistrement complet du lien, y compris le nombre actuel de clics, l'horodatage du clic le plus récent et toute la configuration. L'ID du lien est l'identifiant canonique — les slugs peuvent changer (Pro+ supporte le renommage des slugs), pas les ID.
GET /v1/links?domain_id=…&tag=…&limit=… liste les liens dans l'espace de travail avec des filtres. La pagination est basée sur un curseur ; next_cursor dans la réponse est opaque et doit être renvoyé comme paramètre de requête cursor lors de la requête suivante.
Mettre à jour un lien#
PATCH /v1/links/{id} accepte les mêmes champs que la création. Les mises à jour les plus courantes : modification de l'URL de destination (utile pour la rotation de campagne sans réimprimer les codes QR), modification des tags, extension de expires_at. La mise à jour du slug est un point de terminaison séparé POST /v1/links/{id}/rename qui gère la redirection 301 de l'ancien slug pour une période de rétention configurable (30 jours par défaut).
Supprimer un lien#
DELETE /v1/links/{id} effectue une suppression réversible (soft-delete). Le lien renvoie 410 Gone pendant les 90 jours suivants, puis est supprimé définitivement. La vue corbeille du tableau de bord affiche les liens supprimés ; vous pouvez les restaurer via le tableau de bord ou via POST /v1/links/{id}/restore dans le délai de 90 jours.
Clés d'idempotence#
Chaque requête de mutation — POST, PATCH, DELETE — accepte un en-tête Idempotency-Key. La valeur de l'en-tête est une chaîne opaque de 255 caractères maximum ; le serveur stocke le corps de la réponse et le code d'état pendant 24 heures avec la clé (workspace_id, idempotency_key) et renvoie la réponse stockée si la même clé est présentée à nouveau.
Les SDK officiels génèrent automatiquement des clés d'idempotence lorsqu'elles ne sont pas fournies. Vous pouvez passer outre :
const link = await elido.links.create(
{ destinationUrl: "https://shop.example.com/spring-sale" },
{ idempotencyKey: "order-12345-link" },
);
Le cas d'utilisation type est une boucle de réessai. Si votre tâche crée un lien dans le cadre du traitement d'une commande en amont, générez la clé d'idempotence à partir de l'ID de la commande. Un réessai de la même tâche verra la même clé, touchera le cache d'idempotence et renverra le lien créé initialement plutôt que d'en produire un second.
Le piège : le cache d'idempotence dure 24 heures, pas indéfiniment. Un réessai au troisième jour d'une tâche bloquée créera un nouveau lien. Si l'intégration s'exécute sur des lots de plusieurs jours, stockez l'ID du lien renvoyé par la première création réussie et vérifiez-le avant de relancer.
Un second piège : l'idempotence s'applique par espace de travail. La même clé dans deux espaces de travail crée deux liens. C'est la bonne sémantique pour une API multi-espaces, mais cela peut surprendre les équipes qui supposent que la clé est globalement unique.
Gestion des erreurs#
L'API renvoie des codes d'état HTTP standard ainsi qu'un corps d'erreur structuré :
{
"error": {
"code": "rate_limit_exceeded",
"message": "Workspace rate limit of 100 req/s exceeded. Retry after 1 second.",
"request_id": "req_01HXYZAB123",
"retry_after": 1
}
}
Les codes que vous rencontrerez le plus souvent :
400 invalid_request— échec de validation de la charge utile. Le champmessageliste les champs spécifiques. Ne réessayez pas ; corrigez la charge utile.401 unauthorized— jeton manquant ou invalide. Ne réessayez pas sans renouveler le jeton.403 forbidden— le jeton n'a pas la portée requise. Vérifiez la liste des portées du jeton dans/settings/api.404 not_found— la ressource n'existe pas ou le jeton n'y a pas accès (nous renvoyons 404 plutôt que 403 pour éviter de divulguer l'existence de ressources aux appelants non autorisés).409 conflict— slug déjà utilisé, ou détection d'une modification simultanée (PATCH sur une version obsolète). Récupérez à nouveau la ressource et réessayez.429 rate_limit_exceeded— attendez selon la valeur deretry_after.500 internal_server_error— erreur côté serveur. Sûr à réessayer avec la même clé d'idempotence.502 bad_gateway,503 service_unavailable,504 gateway_timeout— problèmes d'infrastructure temporaires. Attendez et réessayez.
Les SDK officiels implémentent un repli exponentiel (exponential backoff) avec gigue (jitter) pour les erreurs 429, 500, 502, 503 et 504. Ils ne réessaient pas les erreurs 400, 401, 403, 404 ou 409 — ce sont des erreurs de programmation ou des conflits de logique métier, pas des pannes passagères. Les clients HTTP personnalisés doivent suivre le même modèle ; réessayer un 400 avec la même charge utile ne produira pas de résultat différent.
Le request_id dans le corps de l'erreur est le champ à inclure dans les tickets de support. Nous pouvons tracer n'importe quelle requête à partir de cet ID via le journal d'audit, le journal d'application et les métriques de la plateforme — et nous ne pouvons pas tracer une requête sans lui.
Limites de débit#
Les limites de débit publiées sont de 100 requêtes par seconde par espace de travail sur Pro, 500 sur Business, et une limite négociée sur Enterprise. Le niveau Gratuit est limité à 10 req/s.
L'état de la limite de débit est exposé dans trois en-tête de réponse sur chaque réponse de l'API :
X-RateLimit-Limit— la limite actuelle par seconde.X-RateLimit-Remaining— requêtes restantes pour la seconde en cours.X-RateLimit-Reset— horodatage Unix du moment où le quota est réinitialisé.
La limite de 100/s est une implémentation par seau à jetons (token-bucket) avec une capacité de rafale (burst) de 200 — ce qui signifie que vous pouvez émettre 200 requêtes d'un coup si le seau est plein, puis vous stabiliser au taux soutenu de 100/s. La plupart des tâches de création de liens courts s'insèrent confortablement dans la rafale ; les intégrations riches en analyses qui parcourent l'historique des événements de clics bénéficient de la marge de manœuvre du forfait Pro.
Pour les opérations en masse sur Business+, le point de terminaison POST /v1/links/bulk accepte jusqu'à 1000 liens par requête et compte pour une seule unité de limite de débit. C'est le point de terminaison idéal pour toute tâche qui crée plus d'une centaine de liens à la fois.
Ce que les SDK font et que le HTTP classique ne fait pas#
Les SDK officiels proposent quatre fonctionnalités qui se rentabilisent rapidement :
- Réessai automatique avec repli pour les codes d'état concernés.
- Génération de clé d'idempotence lorsqu'elle n'est pas explicitement fournie.
- Erreurs typées pour pouvoir faire un
catch (err) { if (err instanceof ElidoRateLimitError) { … } }plutôt que d'analyser du JSON dans des blocs catch. - Itérateurs de pagination pour que les points de terminaison de liste exposent des itérateurs ou générateurs asynchrones plutôt que de nécessiter une gestion manuelle des curseurs.
Le SDK Go expose en plus le client HTTP sous-jacent pour l'instrumentation — utile si vous souhaitez le relier à votre configuration de traçage existante. La page des fonctionnalités API + SDK couvre toute la surface ; la référence de l'API est publiée sur /docs/api-reference.
Accès aux analyses#
Les points de terminaison d'analyse sont en lecture seule et se trouvent sous /v1/workspaces/{id}/analytics/. Les requêtes les plus courantes :
GET .../links/{id}/clicks?from=…&to=…— événements de clics bruts avec pagination. Utile pour les pipelines d'exportation.GET .../timeseries?from=…&to=…&bucket=day— nombres de clics regroupés par jour pour une période donnée.GET .../breakdown/country?from=…&to=…— ventilation géographique.GET .../breakdown/referrer?from=…&to=…— ventilation par référent (referrer).
Le flux des événements de clics bruts est le plus volumineux. Un espace de travail avec 10 millions de clics par mois produit environ 600 Mo de données JSON brutes par mois. Pour les exportations à cette échelle, le guide d'exportation ClickHouse présente le mécanisme d'exportation en masse qui contourne l'enveloppe JSON et diffuse les données directement depuis l'entrepôt d'analyse.
Webhooks pour les événements de clics#
Les webhooks sont l'inverse de l'interrogation (polling) — au lieu que vous demandiez à l'API de nouveaux clics, l'API les livre à votre point de terminaison. Configurez-les dans /settings/webhooks :
await elido.webhooks.create({
url: "https://your-app.example/webhooks/elido",
events: ["link.click", "link.created", "link.expired"],
secret: process.env.WEBHOOK_SIGNING_SECRET,
});
Chaque livraison inclut un en-tête Elido-Signature contenant un HMAC-SHA256 du corps de la requête avec votre secret partagé. Vérifiez la signature avant le traitement — sans elle, n'importe quel appelant peut envoyer des données à votre point de terminaison de webhook et usurper l'identité d'Elido.
La sémantique de livraison est "au moins une fois" (at-least-once) avec un repli exponentiel jusqu'à une rétention maximale de 72 heures. Pour le format détaillé et le comportement de réessai, l'article webhooks vs interrogation compare les deux modèles d'intégration.
Exemple concret : automatisation de campagne#
L'intégration qui motive la plupart des adoptions d'API ressemble à ceci. Votre automatisation marketing crée une campagne dans Customer.io ou HubSpot. Un hook se déclenche lorsque la campagne est publiée. Votre gestionnaire crée le lien court, l'attache à l'enregistrement de la campagne et le renvoie à l'outil de gestion de campagne pour l'insérer dans le modèle d'e-mail.
En TypeScript :
import { Elido } from "@elido/sdk";
const elido = new Elido({ token: process.env.ELIDO_TOKEN! });
export async function onCampaignPublished(campaign: Campaign) {
const link = await elido.links.create(
{
destinationUrl: campaign.destinationUrl,
tags: ["campaign", `campaign:${campaign.id}`, campaign.channel],
utm: {
source: campaign.channel,
medium: "email",
campaign: campaign.slug,
},
metadata: { campaign_id: campaign.id, batch: campaign.batchId },
},
{
idempotencyKey: `campaign-${campaign.id}-link`,
},
);
await campaignStore.update(campaign.id, { shortUrl: link.shortUrl });
return link;
}
La clé d'idempotence est dérivée de l'ID de la campagne. Si le hook de publication de campagne se déclenche deux fois (cela arrive — les livraisons de webhooks sont "au moins une fois"), le second appel renvoie le même lien sans créer de doublon. Le champ metadata contient vos propres clés de jointure afin que vous puissiez corréler les événements de clics d'Elido à la campagne sans analyser les tags.
Pour une attribution de campagne de bout en bout avec des modèles UTM et le transfert de conversions, le guide du suivi UTM détaille l'ensemble du pipeline.
Ce qui n'est pas encore dans l'API#
Deux choses souvent demandées, mais actuellement non disponibles :
- Un GET d'analyse de lien unique renvoyant toutes les ventilations en un seul appel. Le modèle actuel nécessite des appels séparés pour les clics, le pays, le référent, l'appareil et les séries temporelles. L'agrégation est prévue dans la feuille de route ; pour l'instant, les SDK parallélisent les requêtes avec une seule méthode utilitaire.
- Relecture (replay) de webhooks depuis l'API. Le tableau de bord expose l'historique des livraisons de webhooks et supporte la relecture ; l'API ne le permet pas encore. C'est également prévu dans la feuille de route.
Si une fonctionnalité figure dans la spécification OpenAPI, elle est supportée. S'il en est question dans cet article mais pas dans la spécification, considérez-la comme prévue plutôt que garantie.
Lectures suggérées#
- Les liens intelligents expliqués — l'article de référence pour les fonctionnalités de liens ; couvre la manière dont le moteur de redirection résout un lien à la périphérie (edge).
- Webhooks vs interrogation pour le suivi des clics — quand utiliser quel modèle d'intégration.
- Suivi des conversions côté serveur via les liens courts — étendre l'API au flux de transfert de conversion.
- Importation en masse de campagnes depuis Google Sheets — un exemple concret d'utilisation du point de terminaison en masse.
- Guide opérationnel : le guide du serveur MCP pour connecter la surface de l'API d'Elido à Claude, Cursor et d'autres clients compatibles MCP.
- Présentation produit :
/features/api-sdkset/solutions/developers.
Lire la suite
- FonctionnalitésRaccourcisseurs d'URL en marque blanche : ce que la marque blanche signifie vraiment
- FonctionnalitésWebhooks pour les événements de liens : tous les formats, toutes les tentatives
- FonctionnalitésRaccourcisseur de liens avec un générateur de paramètres UTM — pourquoi le flux de travail s'intègre enfin