14 min de lectureFonctionnalités

API du raccourcisseur d'URL : un démarrage rapide de 30 minutes en cinq langages

De zéro à une automatisation de liens courts fonctionnelle en TypeScript, Python, Go, Ruby et PHP - authentification, idempotence, gestion des erreurs et les pièges de production.

Marius Voß
DevRel · edge infra
Diagramme de démarrage rapide en cinq langages avec des panneaux de code pour TypeScript, Python, Go, Ruby et PHP pointant tous vers un point d'entrée API Elido central

Une API de raccourcisseur d'URL est l'une des plus petites intégrations dans le backlog typique d'une équipe d'ingénierie. Trois points d'entrée, 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 frappe, la logique de nouvelle tentative 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 passe en revue l'intégration réelle. Authentification, premier appel, les quatre points d'entrée qui couvrent la plupart des cas d'usage, idempotence, gestion des erreurs, limites de débit, et les pièges de production que le 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 de simples clients HTTP.

Prérequis

Connectez-vous au tableau de bord, naviguez vers /dashboard/api-keys, et créez une clé API (elle commence par elido_). Les jetons sont limités à un espace de travail - un jeton émis dans l'espace de travail A ne peut pas créer de liens dans l'espace de travail B. Les jetons d'utilisateur machine (pour les systèmes CI, l'outillage interne, l'intégration machine-à-machine) sont créés sous /dashboard/machine-users et tournent indépendamment des clés personnelles. Les deux types portent un rôle d'espace de travail préréglé (viewer, editor ou admin) plutôt que des scopes par point d'entrée, donc donnez à une tâche CI le rôle editor si elle ne fait que créer des liens. Le guide des permissions des clés API indique ce que chaque rôle peut atteindre, notamment pourquoi les modifications de webhooks nécessitent le rôle admin.

L'URL de base est https://api.elido.app/v1. Les domaines de redirection (f.elido.me, s.elido.me, b.elido.me) sont séparés de la surface API. Vos liens courts se résolvent au domaine de redirection ; l'API sert à les créer, les modifier et les lire.

La spécification OpenAPI est publiée à https://elido.app/openapi.json et se conforme à OpenAPI 3.1. Les SDK officiels sont générés depuis cette spécification et republiés à chaque version de l'API ; vous pouvez aussi générer votre propre client dans n'importe quel langage pris en charge par OpenAPI.

Le premier appel

Créez un lien court depuis 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 sauf si vous passez 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 d'entrée que vous utiliserez réellement

L'API a plus de quatre points d'entrée, mais la plupart des intégrations restent dans cet ensemble.

Diagramme en étoile des quatre points d'entrée de lien principaux autour de la ressource /v1/links : POST création, GET lecture, PATCH modification, et DELETE suppression, chacun avec son piège clé.

Créer un lien

POST /v1/links accepte l'URL de destination plus des champs optionnels :

  • slug - un slug que vous choisissez (doit être unique sur le domaine).
  • domain_id - pour les liens sur domaine personnalisé ; sur /v1/links, le domaine court par défaut de votre plan est utilisé si omis. Le chemin associé à l'espace de travail /v1/workspaces/{workspace_id}/links l'exige.
  • title - un libellé affiché dans le tableau de bord.
  • tags - un tableau de chaînes libres pour l'organisation.
  • expires_at - horodatage RFC 3339 après lequel le lien renvoie 410 Gone.
  • redirect_status - 301, 302 (la valeur par défaut) ou 307.
  • password - n'est pas encore accepté à la création ; définissez-le juste après avec un PATCH, et la redirection sert une page de mot de passe avant de transmettre.
  • utm et metadata - prévus. Aujourd'hui, placez les paramètres UTM directement dans destination_url et conservez vos propres clés de jointure dans les tags.

Le slug personnalisé est le champ qui mord les équipes en production. Si vous passez un slug déjà utilisé par un autre lien sur le même domaine, l'API renvoie 409 Conflict. Le gestionnaire de nouvelle tentative naïf qui ajoute un compteur (my-slug-1, my-slug-2) produit le problème de lien en double décrit en introduction. Le bon comportement de nouvelle tentative est décrit dans la section idempotence ci-dessous.

Lire un lien

GET /v1/links/{id} renvoie l'enregistrement complet du lien, y compris short_url et toute la configuration. Les comptes de clics ne figurent pas dans l'enregistrement du lien ; ils proviennent des points d'entrée d'analyse ci-dessous. L'ID du lien est l'identifiant canonique - les slugs peuvent changer, les ID ne changent pas.

GET /v1/links?host=…&tags=…&limit=… liste les liens de l'espace de travail avec des filtres. La pagination est basée sur curseur ; next_cursor dans la réponse est opaque et revient comme paramètre de requête cursor sur la requête suivante.

Modifier un lien

PATCH /v1/links/{id} accepte les mêmes champs que la création. Les mises à jour les plus courantes : changer l'URL de destination (utile pour la rotation de campagne sans réimprimer les codes QR), changer les tags, prolonger expires_at. Le slug se change avec le même PATCH en envoyant un nouveau slug. L'ancien slug cesse immédiatement de fonctionner ; un point d'entrée de renommage dédié qui conserve un 301 depuis l'ancien slug pendant une période de rétention est prévu, mais n'est pas encore construit.

Supprimer un lien

DELETE /v1/links/{id} effectue une suppression douce et renvoie 204 No Content. Le lien cesse de rediriger et disparaît des appels de liste et de lecture. Une vue corbeille avec un point d'entrée de restauration et une fenêtre de 90 jours avant la suppression définitive est prévue ; aujourd'hui, aucun appel API ne permet de restaurer un lien supprimé.

Clés d'idempotence

Chaque requête de modification - POST, PATCH, DELETE - accepte un en-tête Idempotency-Key. La valeur de l'en-tête est une chaîne opaque allant jusqu'à 255 caractères ; le serveur stocke le corps de réponse et le code de statut pendant 24 heures, indexés sur (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 des clés d'idempotence automatiquement quand elles ne sont pas fournies. Vous pouvez les remplacer :

const link = await elido.links.create(
  { destinationUrl: "https://shop.example.com/spring-sale" },
  { idempotencyKey: "order-12345-link" },
);

Le cas d'usage est une boucle de nouvelle tentative. 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 depuis l'ID de commande. Une nouvelle tentative de la même tâche voit la même clé, touche le cache d'idempotence, et renvoie le lien créé à l'origine plutôt que d'en produire un second.

Pipeline où un hook de campagne au moins une fois déclenche deux appels de création portant la même clé d'idempotence ; le cache de 24 heures déduplique le second pour qu'exactement un lien soit créé.

Le piège clé : le cache d'idempotence vit pendant 24 heures, pas pour toujours. Une nouvelle tentative au jour trois d'une tâche bloquée créera un nouveau lien. Si l'intégration tourne sur des lots multi-jours, stockez l'ID de lien renvoyé par la première création réussie et recherchez-le avant de réémettre.

Un second piège : l'idempotence est 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-espace de travail, mais cela peut surprendre les équipes qui supposent que la clé est globalement unique.

Gestion des erreurs

L'API renvoie des codes de statut HTTP standards plus 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 verrez le plus souvent :

  • 400 invalid_request - échec de validation de la charge utile. Le champ message liste les champs spécifiques. Ne retentez pas ; corrigez la charge utile.
  • 401 unauthorized - jeton manquant ou invalide. Ne retentez pas sans faire tourner le jeton.
  • 403 forbidden - le rôle du jeton n'autorise pas l'action (une clé viewer ne peut pas créer de liens). Vérifiez le rôle de la clé sur /dashboard/api-keys.
  • 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 révéler l'existence d'une ressource à des appelants non autorisés).
  • 409 conflict - slug déjà utilisé, ou modification simultanée détectée (PATCH sur une version périmée). Relisez et retentez.
  • 429 rate_limit_exceeded - reculez selon la valeur retry_after.
  • 500 internal_server_error - défaillance côté serveur. Sûr à retenter avec la même clé d'idempotence.
  • 502 bad_gateway, 503 service_unavailable, 504 gateway_timeout - problèmes d'infrastructure transitoires. Reculez et retentez.

Les SDK officiels implémentent un recul exponentiel avec gigue pour 429, 500, 502, 503 et 504. Ils ne retentent pas 400, 401, 403, 404 ou 409 - ce sont des erreurs de programmation ou des conflits de logique métier, pas des défaillances transitoires. Les clients HTTP personnalisés devraient suivre le même schéma ; retenter une erreur 400 avec la même charge utile ne produira pas un résultat différent.

Répartition en décision triant les codes de statut API en une colonne à retenter avec recul (429, 500, 502, 503, 504) et une colonne à ne pas retenter (400, 401, 403, 404, 409) pour les erreurs de programmation et de conflit.

Le request_id dans le corps d'erreur est le champ à inclure dans les tickets de support. Nous pouvons tracer n'importe quelle requête depuis cet ID à travers le journal d'audit, le journal d'application, et les métriques de 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 de 10 req/s.

L'état de limite de débit est exposé dans trois en-têtes de réponse sur chaque réponse API :

  • X-RateLimit-Limit - la limite par seconde actuelle.
  • X-RateLimit-Remaining - requêtes restantes dans la seconde en cours.
  • X-RateLimit-Reset - horodatage Unix de la réinitialisation du seau.

La limite de 100/s est une implémentation en seau de jetons avec une capacité de rafale de 200 - ce qui signifie que vous pouvez émettre 200 requêtes d'un coup si le seau est plein, puis vous stabiliser sur le taux soutenu de 100/s. La plupart des tâches de création de liens courts tiennent confortablement dans la rafale ; les intégrations analytiques lourdes qui paginent à travers l'historique de clics bénéficient de la marge du niveau Pro.

Pour les opérations en masse, le point d'entrée POST /v1/links/bulk accepte jusqu'à 100 liens par requête et compte comme une seule unité de limite de débit. C'est le bon point d'entrée pour toute tâche qui crée plus d'une centaine de liens à la fois. Pour le traitement plus approfondi du rythme contre le seau de jetons, du choix des codes de statut à retenter, et de la façon dont les clés d'idempotence empêchent les nouvelles tentatives de dupliquer des liens, voir limites de débit, nouvelles tentatives et idempotence en production.

Ce que font les SDK que le HTTP brut ne fait pas

Les SDK officiels livrent quatre choses qui se rentabilisent rapidement :

  • Nouvelle tentative automatique avec recul pour les codes de statut à retenter.
  • Génération de clé d'idempotence quand non fournie explicitement.
  • Erreurs typées pour que vous puissiez faire catch (err) { if (err instanceof ElidoRateLimitError) { … } } plutôt que d'analyser du JSON dans les blocs catch.
  • Itérateurs de pagination pour que les points d'entrée de liste exposent des itérateurs ou générateurs asynchrones plutôt que d'exiger une gestion manuelle du curseur.

Le SDK Go expose en plus le client HTTP sous-jacent pour l'instrumentation - utile si vous voulez le câbler dans votre configuration de traçage existante. La page fonctionnalité API + SDK du dépôt couvre la surface complète ; la référence API est publiée à /docs/api-reference.

Accès aux analyses

Les points d'entrée d'analyse sont en lecture seule et vivent sous /v1/workspaces/{id}/analytics/. Le guide de l'API d'analyse des liens liste tous les rapports, leurs paramètres et la forme de leurs réponses. Les requêtes les plus courantes :

  • GET .../clicks/recent?from=…&to=… - clics individuels, du plus récent au plus ancien, paginés avec next_cursor. Utile pour les pipelines d'export.
  • GET .../timeseries?from=…&to=…&interval=day - comptes de clics par bloc pour une plage temporelle ; interval vaut hour ou day, et tz définit le fuseau horaire des blocs.
  • GET .../breakdown/country?from=…&to=… - ventilation géographique.
  • GET .../breakdown/referrer?from=…&to=… - ventilation par referrer.

Les autres rapports sont summary, links/top, les autres ventilations (host, device, browser, destination) et les listes principales (top-countries, top-regions, top-cities, top-referrers, top-destinations). from et to sont des dates au format YYYY-MM-DD et to est exclusif ; ajoutez link_id pour restreindre n'importe quel rapport à un seul lien, et limit pour définir la taille des ventilations et des listes principales.

Le flux d'événements de clic bruts est le plus volumineux. Un espace de travail avec 10 millions de clics par mois produit environ 600 Mo de JSON par mois de données d'événements brutes. Pour les exports à cette échelle, le guide d'export d'analyse couvre le mécanisme d'export en masse qui contourne l'enveloppe JSON et diffuse directement depuis l'entrepôt d'analyse.

Webhooks pour les événements de lien

Les webhooks sont l'inverse du sondage - au lieu que vous demandiez à l'API ce qui a changé, l'API livre les événements de lien et de domaine à votre point d'entrée. Configurez à /dashboard/webhooks :

await elido.webhooks.create({
  url: "https://your-app.example/webhooks/elido",
  events: ["link.created", "link.updated", "link.expired"],
  secret: process.env.WEBHOOK_SIGNING_SECRET,
});

Un événement click.created par clic est sur la feuille de route mais pas encore disponible, donc aujourd'hui les données de clic viennent des points d'entrée d'analyse. Chaque livraison inclut un en-tête X-Elido-Signature (aussi envoyé comme X-Webhook-Signature) avec la valeur v1=<hex> : un HMAC-SHA256, keyé avec le secret de votre point d'entrée, sur la valeur X-Webhook-Timestamp, un point, et le corps de requête brut. Vérifiez la signature avant de traiter - sans cela, n'importe quel appelant peut publier vers votre point d'entrée de webhook et se faire passer pour Elido.

La sémantique de livraison est au-moins-une-fois : une livraison échouée est retentée avec un recul de quelques minutes, avec trois tentatives au total par défaut. Pour la forme détaillée et le comportement de nouvelle tentative, l'article webhooks contre sondage pour le suivi des clics compare les deux schémas d'intégration.

Un 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 quand la campagne est publiée. Votre gestionnaire crée le lien court, l'attache à l'enregistrement de campagne, et le renvoie à l'outil de gestion de campagne pour le substituer 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}`,
        `batch:${campaign.batchId}`,
        campaign.channel,
      ],
    },
    {
      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 campagne. Si le hook de campagne publiée se déclenche deux fois (cela arrive - les livraisons de webhook sont au-moins-une-fois), le second appel renvoie le même lien sans en créer un doublon. Les tags campaign: et batch: portent vos propres clés de jointure pour que vous puissiez corréler les événements de clic d'Elido avec la campagne ; un champ metadata dédié à cet usage est prévu. Les paramètres UTM doivent être placés dans campaign.destinationUrl elle-même jusqu'à la mise en service du champ utm.

Pour l'attribution de campagne de bout en bout avec des modèles UTM et transmission de conversion, le pilier suivi UTM passe en revue le pipeline complet.

Ce qui n'est pas encore dans l'API

Deux choses fréquemment demandées, actuellement non disponibles :

  • Un GET d'analyse à lien unique qui renvoie toutes les ventilations en un seul appel. Le modèle actuel nécessite des appels séparés pour les clics, le pays, le referrer, l'appareil, et la série temporelle. L'agrégation est sur la feuille de route ; pour l'instant, exécutez les requêtes en parallèle depuis votre propre code.
  • Rejeu de webhook depuis l'API. Le tableau de bord expose l'historique de livraison de webhook et prend en charge le rejeu ; l'API pas encore. C'est aussi sur la feuille de route.

Si une fonctionnalité est dans la spécification OpenAPI, elle est prise en charge. Si elle est dans cet article mais pas dans la spécification, traitez-la comme prévue plutôt que garantie.

Lectures connexes

Essayer Elido

Collez une URL, obtenez un lien court

Sans inscription. Lien actif 30 jours. Inscrivez-vous pour le garder pour toujours.

Gratuit, sans inscription · 2 par jour

Essayer Elido

Raccourcisseur d'URL hébergé en UE : domaines personnalisés, analyses approfondies et API ouverte. Forfait gratuit - sans carte bancaire.

Tags
url shortener api
bitly api alternative
link shortener api
rest api short link
url shortener sdk
openapi 3.1
idempotency keys

Lire la suite