Pour raccourcir une URL avec une API, envoyez un seul POST authentifié : mettez l'URL longue dans un corps JSON sous destination_url, envoyez-le à https://api.elido.app/v1/links avec Authorization: Bearer <your key>, et lisez short_url dans la réponse 201 Created. Voilà toute l'intégration, dans chaque langage. Les huit langages ci-dessous ne diffèrent que par la façon d'écrire la requête. Et par le détail de production qui mord en premier.
Le premier appel prend cinq minutes. Les ennuis commencent plus tard, quand le trafic réel arrive et qu'un gestionnaire de retry crée des doublons : le tableau de bord se remplit de variantes /foo-1, /foo-2 et /foo-3 d'une même destination, et quelqu'un ouvre un ticket. Ce guide est la présentation de l'API de raccourcisseur d'URL que j'aurais voulu voir lue par cette équipe : authentification, premier appel, les quatre points d'accès des liens, clés d'idempotence, codes d'erreur, limites de débit, puis la même requête de lien court en huit langages, avec la seule habitude dont chaque runtime a besoin.
Vous hésitez encore sur le service ? La comparaison des API de raccourcisseur d'URL gratuites met en regard les formes de requête et les limites des offres gratuites chez plusieurs fournisseurs. Tout ce qui suit vise l'API REST d'Elido.
Allez directement à votre langage :
- Python :
requestspour un lien,httpxavec un sémaphore pour des milliers. - JavaScript et Node.js :
fetch, pourquoi la clé reste hors du navigateur, un pool de workers au lieu dePromise.all. - Go : un
http.Clientpartagé avec timeout,errgroup.SetLimitpour le bulk. - Java : le
HttpClientdu JDK, des threads virtuels derrière unSemaphore. - C# et .NET :
PostAsJsonAsync, puis un client typé issu deIHttpClientFactory. - PHP, Laravel et WordPress : curl,
Http::avec retries,wp_remote_postà la publication. - Ruby :
Net::HTTP.post, puis Faraday avec un middleware de retry qui relance vraiment un POST. - PowerShell :
Invoke-RestMethod, une clé dans un coffre et toute une colonne CSV.
Prérequis : une clé API et une URL de base
Connectez-vous au tableau de bord, ouvrez /dashboard/api-keys et créez une clé API. Les clés commencent par elido_ et ne s'affichent qu'une fois. Chaque clé est liée à un espace de travail : une clé émise dans l'espace A ne peut pas créer de liens dans l'espace B, et l'API déduit de la clé seule l'espace de travail visé. Les jetons d'utilisateur machine (pour les systèmes de CI, les outils internes et les intégrations de machine à machine) se trouvent sous /dashboard/machine-users et tournent indépendamment des clés personnelles.
Les deux types portent un rôle d'espace de travail prédéfini (viewer, editor ou admin) plutôt que des portées par point d'accès : une tâche de CI qui ne fait que créer des liens reçoit donc editor. Le guide des permissions des clés API liste ce que chaque rôle peut atteindre. Les changements de webhooks exigent admin, et il explique pourquoi.
L'URL de base est https://api.elido.app, et les routes de liens se trouvent sous /v1. Les domaines de redirection (f.elido.me en Free, s.elido.me en offre payante, b.elido.me en Business) forment une surface distincte : vos liens courts s'y résolvent, tandis que l'API les crée et les modifie. La spécification OpenAPI 3.1 derrière la référence de l'API fait foi. Si un champ figure dans la spécification, il est supporté.
Un piège attend les nouveaux comptes. Les écritures faites avec une clé dont le propriétaire n'a pas vérifié son e-mail reviennent en 403 (email_not_verified). Les lectures continuent de fonctionner. Les jetons d'utilisateur machine en sont exemptés. Vérifiez l'adresse une fois et l'erreur disparaît.
Le premier appel : créer un lien court
Voici la requête que chaque section de langage ci-dessous reproduit, en curl :
curl -sS https://api.elido.app/v1/links \
-H "Authorization: Bearer $ELIDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"destination_url": "https://shop.example.com/spring-sale"}'
La réponse est un 201 Created avec l'enregistrement du lien. Réduit aux champs utiles :
{
"id": 48213,
"workspace_id": 42,
"domain_id": 3,
"slug": "aB3xK9",
"destination_url": "https://shop.example.com/spring-sale",
"tags": [],
"redirect_status": 302,
"password_set": false,
"short_url": "https://s.elido.me/aB3xK9",
"created_at": "2026-11-11T09:14:03Z"
}
Conservez id si vous comptez modifier, faire expirer ou supprimer le lien : c'est l'identifiant canonique, et les slugs peuvent changer alors que les ID, non. Vous n'avez pas passé de domain_id, donc l'API a utilisé le domaine court par défaut de votre offre. Le slug est généré par le serveur sauf si vous envoyez slug ; les slugs générés sont en base62 ([0-9A-Za-z]) et font six caractères au plus.
Gardez la clé dans une variable d'environnement (ELIDO_API_KEY dans tous les exemples ici), jamais dans le code source : une clé de raccourcisseur est un identifiant qui engage votre quota, quiconque la détient peut créer des liens à vos frais, et c'est en JavaScript que cela compte le plus, comme l'explique la section Node.
Les quatre points d'accès dont vous vous servirez vraiment
L'API compte plus de quatre points d'accès, mais la plupart des intégrations restent dans cet ensemble. Les routes plates /v1/links déduisent l'espace de travail de votre clé ; les chemins canoniques sous /v1/workspaces/{workspace_id}/links se comportent de la même façon et exigent l'ID explicite.
Créer un lien
POST /v1/links prend destination_url plus des champs facultatifs :
slug: une fin de lien de votre choix ; elle doit être unique sur le domaine.domain_id: pour un domaine personnalisé ; omettez-le pour obtenir le domaine court par défaut de votre offre.titleettags: un libellé de tableau de bord et des chaînes libres sur lesquelles filtrer plus tard.expires_at: un horodatage RFC 3339 après lequel le lien cesse de rediriger et affiche une page d'expiration.redirect_status:301,302(par défaut) ou307.
Les mots de passe ne se définissent pas à la création : envoyez un PATCH juste après, et la redirection affichera une page de mot de passe avant de transférer. Les paramètres UTM vont directement dans destination_url, et vos propres clés de jointure dans tags.
Le slug personnalisé est le champ qui mord les équipes en production. Réutilisez un slug sur le même domaine et vous obtenez un 409. Un gestionnaire naïf qui ajoute un compteur (my-slug-1, my-slug-2) est exactement ce qui déclenche le désordre de doublons évoqué au début. Le remède est une clé d'idempotence, traitée deux sections plus bas.
Lire et lister les liens
GET /v1/links/{id} renvoie l'enregistrement complet, short_url compris. Les nombres de clics ne figurent pas dans l'enregistrement du lien. Ils viennent des points d'accès d'analytique décrits dans le guide de l'API d'analytique des clics.
GET /v1/links?limit=…&tags=…&host=… liste les liens avec des filtres (q recherche, status et folder affinent). La pagination se fait par curseur : renvoyez le next_cursor de la réponse sous le nom cursor, et arrêtez-vous quand il vaut null.
Modifier un lien
PATCH /v1/links/{id} accepte les champs de création. Les mises à jour courantes : une nouvelle destination_url (rotation de campagne sans réimprimer un QR code), de nouveaux tags et un expires_at repoussé. Envoyer un nouveau slug renomme le lien. L'ancien slug meurt aussitôt. Ne renommez rien de ce qui est déjà imprimé.
Supprimer et restaurer un lien
DELETE /v1/links/{id} renvoie 204 No Content et place le lien dans la corbeille : il cesse de rediriger et disparaît des appels de liste et de lecture. Il n'est pas encore perdu. POST /v1/links/{id}/restore le ramène et GET /v1/links/trash montre le contenu. Au bout de 30 jours, la corbeille est vidée définitivement.
Clés d'idempotence : des retries sans liens en double
Chaque POST, PUT et PATCH accepte un en-tête Idempotency-Key, une chaîne opaque de votre choix. La première fois qu'une clé arrive avec une réponse réussie, le serveur stocke le code de statut et le corps pendant 24 heures, pour le couple (workspace, key). Une répétition de la même clé avec la même requête reçoit la réponse stockée : un retry renvoie donc le lien déjà existant au lieu d'en créer un second.
Trois détails décident si cela marche pour vous :
- Seules les réponses réussies sont stockées. Un
429ou un500n'est pas mis en cache, si bien que le retry suivant avec la même clé obtient une vraie seconde tentative. - Le serveur prend l'empreinte de la méthode, du chemin et du corps. Réutilisez une clé avec un autre corps et vous obtenez
422avecidempotency_key_mismatch: c'est un bug de l'appelant, pas quelque chose à relancer. DELETEne prend pas de clé ; supprimer deux fois est idempotent par nature.
La clé elle-même doit venir d'un élément stable. Dérivez-la de l'événement métier (order-12345-link) quand il y en a un, ou d'un hash SHA-256 de l'URL de destination dans une tâche par lots. Un UUID aléatoire à chaque tentative annule l'intérêt. Une clé par URL fonctionne jusqu'à ce que le lot soit relancé.
La fenêtre de 24 heures est le piège. Un retry le troisième jour d'une tâche bloquée crée un nouveau lien : pour les lots de plusieurs jours, stockez l'id de la première création réussie et cherchez-le avant de réémettre. Les clés sont aussi propres à chaque espace de travail : la même clé dans deux espaces crée deux liens, ce qui est correct pour une API multi-espaces et surprend pourtant les équipes qui croyaient les clés globales.
Une clé fondée sur le hash de la destination a un effet de bord à connaître. Deux campagnes qui pointent vers la même URL dans les 24 heures partagent un lien. Si vous voulez deux liens, mettez l'identifiant de campagne dans la clé.
Gestion des erreurs et codes de statut
Les erreurs arrivent sous forme d'un petit objet JSON avec un error lisible et, dans certains cas, un code lisible par machine :
{ "error": "destination_url is required" }
Les codes de statut que vous rencontrerez :
400: échec de validation, par exemple undestination_urlmanquant, unexpires_atmal formé, unredirect_statusinvalide. Corrigez la charge utile ; ne relancez pas.401: la clé est absente, révoquée ou mal saisie. Vérifiez que l'en-tête contientBeareravec une espace avant la clé.402: le quota de liens de votre offre est épuisé. Un retry n'y changera rien ; passer à une offre supérieure ou attendre la période suivante, si.403: le rôle de la clé ne permet pas l'action (une cléviewerne peut pas créer de liens), l'e-mail du compte n'est pas vérifié, ou la destination a été bloquée par l'analyse de sécurité ("code": "destination_blocked").404: le lien n'existe pas ou cette clé ne peut pas le voir. Nous renvoyons 404 plutôt que 403 pour qu'un appelant ne puisse pas sonder des ressources qui ne sont pas les siennes.409: le slug est déjà pris sur ce domaine.422: une clé d'idempotence réutilisée avec un autre corps, ou une combinaison d'options non permise.429: vous avez atteint la limite de débit. AttendezRetry-Afteret réessayez avec la même clé d'idempotence.500,502,503,504: une panne côté serveur ou transitoire. Patientez avec un backoff et relancez avec la même clé.
Cette séparation est toute la politique de retry, et chaque section de langage ci-dessous l'applique de la même façon : relancer 429 et 5xx, abandonner pour le reste. Relancer un 400 avec la même charge utile ne peut pas donner une autre réponse, et relancer trois fois un 401 n'est qu'un échec plus lent. Le guide des limites de débit et des retries approfondit le jitter et la raison pour laquelle les boucles de retry aveugles transforment une panne en deux.
Limites de débit et point d'accès bulk
Le trafic API est mesuré par espace de travail avec un token bucket :
| Offre | Débit soutenu | Burst |
|---|---|---|
| Free | 10 req/s | 20 |
| Pro | 30 req/s | 60 |
| Business | 100 req/s | 200 |
Un seau plein vous laisse envoyer le burst d'un coup. Ensuite, vous retombez au débit soutenu. Chaque clé API a aussi son propre seau en plus de celui de l'espace de travail : un seul script emballé ne peut donc pas dépenser à lui seul tout le quota de l'espace.
Une requête rejetée reçoit 429, un en-tête Retry-After: 1 et un en-tête X-RateLimit-Scope qui indique quel seau s'est vidé (workspace ou api_key). Il n'y a pas d'en-têtes de quota restant sur lesquels se caler. L'approche pratique est donc un plafond fixe de concurrence plus un retry sur 429 : huit requêtes en vol sont un bon départ en Pro, et quatre conviennent à Free. Une bizarrerie m'a piégé. Le corps du 429 est un texte de forme JSON envoyé en text/plain, si bien qu'un client qui n'analyse que les types de contenu JSON verra une chaîne simple là où il attendait un objet.
Un gros travail ? POST /v1/links/bulk prend {"links": [...]} avec jusqu'à 100 éléments et compte pour une requête dans la limite. Il répond 200 même quand certains éléments échouent, avec des compteurs ok et failed et un status par élément valant ok ou error : lisez donc chaque élément. Ne sautez pas cette étape. Les liens renvoyés portent un slug mais pas de short_url, ce qui signifie que vous construisez l'URL vous-même à partir de votre domaine et du slug. Le tutoriel d'import en masse depuis Google Sheets déplace ainsi toute une feuille de calcul.
Raccourcir une URL dans votre langage : la forme commune
Chaque section à partir d'ici envoie la requête du premier appel et applique les mêmes quatre habitudes : la clé prise dans l'environnement, un timeout sur chaque requête, le code de statut vérifié avant d'analyser le corps, et une Idempotency-Key stable sur tout ce qui peut s'exécuter deux fois. Je ne les réexpliquerai pas langage par langage. Ce qui change, c'est l'idiome. Chaque runtime a aussi un détail que les gens ratent.
Chaque section s'ouvre sur l'appel minimal qui fonctionne. C'est suffisant pour un script lancé à la main. Vient ensuite la pièce de production que les utilisateurs de ce runtime demandent le plus : la concurrence en Python, Go ou Java ; la durée de vie du client en C# ; l'hébergement en PHP ; le middleware en Ruby ; les secrets et les CSV en PowerShell. Vous voulez les exécuter tels quels ? Créez une clé sur l'offre gratuite, exportez-la en ELIDO_API_KEY, et chaque extrait marche sans changement.
Raccourcir une URL en Python
En Python, raccourcir une URL tient en un requests.post avec la clé dans un en-tête Bearer, la destination dans json= et un timeout :
import os
import requests
resp = requests.post(
"https://api.elido.app/v1/links",
headers={"Authorization": f"Bearer {os.environ['ELIDO_API_KEY']}"},
json={"destination_url": "https://example.com/a-very-long-path?with=params"},
timeout=10,
)
resp.raise_for_status()
print(resp.json()["short_url"]) # https://s.elido.me/aB3xK9
La bibliothèque requests n'a pas de timeout par défaut : sans timeout=10, une connexion bloquée suspend le script pour toujours. raise_for_status() transforme un 401 ou un 500 en exception au lieu de laisser un appel raté passer pour un succès. Si vous ne pouvez rien installer avec pip, urllib.request, dans la bibliothèque standard, envoie le même POST avec plus de cérémonie.
Bulk avec httpx et un sémaphore
Une boucle sur requests.post convient pour quelques centaines de liens. Pour des dizaines de milliers, le temps part à attendre le réseau, et httpx avec asyncio fait chevaucher ces attentes. Le sémaphore compte le plus. Il plafonne les requêtes en vol, ce qui fait la différence entre un lot rapide et un mur de 429 dont la boucle de retry doit ensuite se sortir.
import asyncio
import hashlib
import os
import httpx
API = "https://api.elido.app/v1/links"
async def shorten_all(urls: list[str], concurrency: int = 8) -> dict[str, str]:
gate = asyncio.Semaphore(concurrency)
headers = {"Authorization": f"Bearer {os.environ['ELIDO_API_KEY']}"}
async with httpx.AsyncClient(headers=headers, timeout=10) as client:
async def one(url: str) -> tuple[str, str]:
key = hashlib.sha256(url.encode()).hexdigest() # same key on every retry
async with gate:
for attempt in range(3):
r = await client.post(
API,
json={"destination_url": url},
headers={"Idempotency-Key": key},
)
if r.status_code == 429 or r.status_code >= 500:
await asyncio.sleep(int(r.headers.get("Retry-After", 2**attempt)))
continue
if r.is_success:
return url, r.json()["short_url"]
return url, f"ERROR {r.status_code}: {r.text}"
return url, "ERROR: gave up after 3 attempts"
pairs = await asyncio.gather(*(one(u) for u in urls))
return dict(pairs)
# results = asyncio.run(shorten_all(my_urls))
Les résultats sont indexés par l'URL d'origine. Un échec partiel reste visible. Comme la clé est un hash de la destination, relancer toute la liste après avoir corrigé trois mauvaises lignes ne crée rien en double. Dormir à l'intérieur du sémaphore est voulu : un worker à qui l'on a dit de patienter ne doit pas céder sa place à une requête neuve.
Raccourcir une URL en JavaScript et Node.js
En JavaScript, raccourcir une URL tient en un POST fetch. Node fournit fetch comme global depuis la version 18, donc il n'y a rien à installer :
const res = await fetch("https://api.elido.app/v1/links", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ELIDO_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ destination_url: "https://example.com/spring-sale" }),
signal: AbortSignal.timeout(10_000),
});
if (!res.ok)
throw new Error(`shorten failed: ${res.status} ${await res.text()}`);
const { short_url } = await res.json();
console.log(short_url);
Lancez-le avec node --env-file=.env shorten.mjs. Cette ligne if (!res.ok) n'est pas de la décoration : fetch ne rejette qu'en cas d'échec réseau ou d'abandon, donc un 401 arrive comme une promesse résolue avec ok: false. Les utilisateurs d'Axios se font piéger une fois. Cela se manifeste d'ordinaire par un undefined là où devrait se trouver un lien.
Gardez la clé côté serveur
Collez cet extrait dans un composant React et deux choses cassent. CORS bloque la réponse, car une API authentifiée par clé n'envoie pas Access-Control-Allow-Origin pour des sites quelconques. Le plus grave se cache derrière l'erreur : une clé dans le code front-end est lisible par quiconque ouvre DevTools, et un préfixe NEXT_PUBLIC_ l'affiche en grand. Le navigateur poste donc une simple URL vers votre route. C'est la route qui ajoute la clé.
Validez l'URL que votre route accepte, sinon vous avez construit une redirection ouverte avec des étapes en plus ; l'article sur les vulnérabilités de redirection ouverte montre ce que les attaquants en font.
Pour un lot, résistez à Promise.all(urls.map(shorten)). Il lance toutes les requêtes dans le même tick : 500 URL deviennent 500 connexions simultanées et la plupart reviennent en 429. Un petit pool de workers qui puisent dans une file partagée garde une concurrence plate, quelle que soit la longueur de la liste :
import { createHash } from "node:crypto";
async function shorten(url) {
const key = createHash("sha256").update(url).digest("hex"); // stable across retries
for (let attempt = 0; attempt < 3; attempt++) {
const res = await fetch("https://api.elido.app/v1/links", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ELIDO_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": key,
},
body: JSON.stringify({ destination_url: url }),
signal: AbortSignal.timeout(10_000),
});
if (res.status === 429 || res.status >= 500) {
const wait = Number(res.headers.get("Retry-After") ?? 2 ** attempt);
await new Promise((r) => setTimeout(r, wait * 1000));
continue;
}
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
return (await res.json()).short_url;
}
throw new Error("gave up after 3 attempts");
}
export async function shortenAll(urls, concurrency = 8) {
const queue = [...urls];
const results = new Map();
const worker = async () => {
while (queue.length) {
const url = queue.shift();
try {
results.set(url, await shorten(url));
} catch (err) {
results.set(url, `ERROR: ${err.message}`); // one bad URL must not sink the batch
}
}
};
await Promise.all(Array.from({ length: concurrency }, worker));
return results;
}
Sur les runtimes edge (Vercel Edge, Cloudflare Workers, Deno), node:crypto peut manquer ; faites le hash avec crypto.subtle.digest("SHA-256", …) à la place, le reste ne change pas.
Raccourcir une URL en Go
En Go, raccourcir une URL demande net/http et encoding/json de la bibliothèque standard : sérialiser le corps, poser deux en-têtes, décoder short_url. Écrivez-le dès le départ comme une fonction sur un client partagé, car c'est la version que vous garderez :
var client = &http.Client{Timeout: 10 * time.Second}
func shorten(ctx context.Context, destination string) (string, error) {
body, err := json.Marshal(map[string]string{"destination_url": destination})
if err != nil {
return "", err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
"https://api.elido.app/v1/links", bytes.NewReader(body))
if err != nil {
return "", err
}
sum := sha256.Sum256([]byte(destination))
req.Header.Set("Authorization", "Bearer "+os.Getenv("ELIDO_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", hex.EncodeToString(sum[:]))
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("post: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode >= 300 {
msg, _ := io.ReadAll(io.LimitReader(resp.Body, 512))
return "", fmt.Errorf("shorten %s: %s %s", destination, resp.Status, msg)
}
var link struct {
ShortURL string `json:"short_url"`
}
if err := json.NewDecoder(resp.Body).Decode(&link); err != nil {
return "", fmt.Errorf("decode: %w", err)
}
return link.ShortURL, nil
}
http.DefaultClient et http.Post n'ont aucun timeout. Aucun. Un hôte qui ne répond plus peut retenir une goroutine pendant des minutes. Un client unique au niveau du paquet garde le pool de connexions en vie entre les appels, et NewRequestWithContext fait qu'une CLI annulée ou un appelant déconnecté arrête la requête au lieu de la laisser tourner. Si un lot ouvre bien plus de sockets que prévu, relevez MaxIdleConnsPerHost sur le transport : sa valeur par défaut de 2 passe inaperçue dans un script et coûte cher dès que huit goroutines se partagent un hôte.
Fan-out borné avec errgroup.SetLimit
Une goroutine par URL coûte peu à Go et ruine la limite de débit : 5 000 URL deviennent 5 000 requêtes d'un coup. errgroup avec SetLimit donne un fan-out avec plafond.
func shortenAll(ctx context.Context, urls []string) map[string]string {
var (
g errgroup.Group
mu sync.Mutex
out = make(map[string]string, len(urls))
)
g.SetLimit(8) // at most 8 requests in flight, whatever len(urls) is
for _, u := range urls {
g.Go(func() error {
short, err := shorten(ctx, u)
if err != nil {
short = "ERROR: " + err.Error()
}
mu.Lock()
out[u] = short
mu.Unlock()
return nil
})
}
_ = g.Wait()
return out
}
Ceci utilise volontairement un simple errgroup.Group. errgroup.WithContext annule tout à la première erreur, ce qui convient à une étape de build tout ou rien et se trompe exactement pour un importeur censé raccourcir ce qu'il peut, dépasser les mauvaises lignes et rapporter les échecs à la fin. Depuis Go 1.22, la variable de boucle est propre à chaque itération, donc l'ancienne ligne u := u a disparu, mais le mutex n'est pas facultatif : go test -race signalera une map non protégée. Ajoutez un retry sur 429 et 5xx dans shorten en utilisant select sur ctx.Done() et time.After, pour que le backoff reste interruptible.
Raccourcir une URL en Java
En Java, raccourcir une URL est un POST via java.net.http.HttpClient, présent dans le JDK depuis Java 11 : la première version n'a donc besoin d'aucune dépendance Maven :
private static final HttpClient CLIENT = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build();
public static void main(String[] args) throws Exception {
HttpRequest request = HttpRequest
.newBuilder(URI.create("https://api.elido.app/v1/links"))
.header("Authorization", "Bearer " + System.getenv("ELIDO_API_KEY"))
.header("Content-Type", "application/json")
.timeout(Duration.ofSeconds(10))
.POST(HttpRequest.BodyPublishers.ofString(
"{\"destination_url\":\"https://example.com/spring-sale\"}"))
.build();
HttpResponse<String> response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 300) {
throw new IllegalStateException(response.statusCode() + " " + response.body());
}
System.out.println(response.body()); // JSON with "short_url"
}
Deux timeouts font deux métiers. connectTimeout borne la poignée de main TCP et TLS ; le timeout de la requête borne l'échange entier, et sans lui un serveur qui accepte la connexion puis se tait retient le thread. send ne lève d'exception que pour les échecs de transport et les timeouts, jamais pour une erreur HTTP, ce qui explique pourquoi la vérification du statut est le contrat et non de la programmation défensive. Construisez un seul HttpClient par application : il est thread-safe et possède un pool de connexions, et en créer un par appel refait la poignée de main TLS à chaque fois.
Threads virtuels derrière un sémaphore
Les threads virtuels sont devenus définitifs en Java 21. Ils rendent un thread par URL assez bon marché pour rester raisonnable. La limite de débit n'a pas bougé, en revanche : c'est donc le Semaphore qui fait le vrai travail. Analysez avec Jackson ici. Une chaîne JSON assemblée à la main casse dès la première destination contenant un guillemet.
static final ObjectMapper MAPPER = new ObjectMapper();
static String shorten(String destination) throws Exception {
String key = HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256")
.digest(destination.getBytes(StandardCharsets.UTF_8)));
HttpRequest request = HttpRequest
.newBuilder(URI.create("https://api.elido.app/v1/links"))
.header("Authorization", "Bearer " + System.getenv("ELIDO_API_KEY"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", key)
.timeout(Duration.ofSeconds(10))
.POST(HttpRequest.BodyPublishers.ofString(
MAPPER.writeValueAsString(Map.of("destination_url", destination))))
.build();
HttpResponse<String> response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 300) {
throw new IllegalStateException(response.statusCode() + " " + response.body());
}
return MAPPER.readTree(response.body()).get("short_url").asText();
}
static Map<String, String> shortenAll(List<String> urls) {
Map<String, String> out = new ConcurrentHashMap<>();
Semaphore gate = new Semaphore(8); // 8 in flight, whatever urls.size() is
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (String url : urls) {
executor.submit(() -> {
gate.acquire();
try {
out.put(url, shorten(url));
} catch (Exception e) {
out.put(url, "ERROR: " + e.getMessage());
} finally {
gate.release();
}
return null;
});
}
} // close() waits for every task
return out;
}
Le bloc try-with-resources fait un vrai travail : fermer un exécuteur de threads virtuels attend toutes les tâches soumises, donc pas de danse avec awaitTermination. Sur Java 11 à 17, la même forme fonctionne avec sendAsync et un pool fixe. Ce qui échoue sur toutes les versions, c'est CompletableFuture.allOf sur une liste non bornée, qui démarre tout d'un coup et récolte un tas de 429.
Raccourcir une URL en C# et .NET
En C#, raccourcir une URL tient en un appel PostAsJsonAsync sur un HttpClient qui porte déjà l'en-tête Bearer :
using System.Net.Http.Json;
using System.Text.Json.Serialization;
var http = new HttpClient
{
BaseAddress = new Uri("https://api.elido.app"),
Timeout = TimeSpan.FromSeconds(10),
};
http.DefaultRequestHeaders.Authorization =
new("Bearer", Environment.GetEnvironmentVariable("ELIDO_API_KEY"));
var response = await http.PostAsJsonAsync("/v1/links",
new { destination_url = "https://example.com/spring-sale" });
response.EnsureSuccessStatusCode();
var link = await response.Content.ReadFromJsonAsync<LinkResponse>();
Console.WriteLine(link!.ShortUrl);
record LinkResponse([property: JsonPropertyName("short_url")] string ShortUrl);
EnsureSuccessStatusCode est la ligne qui compte. Sans elle, un 401 se termine tranquillement, ReadFromJsonAsync rend un record plein de nulls, et l'échec apparaît trois couches plus loin sous forme de NullReferenceException. L'objet anonyme garde le nom de champ en snake_case sans politique de nommage ; dans un vrai projet, réglez une fois JsonNamingPolicy.SnakeCaseLower dans les options du sérialiseur.
Un client typé issu d'IHttpClientFactory
Cet extrait va très bien dans un prototype de console et se trompe dans un service. Un HttpClient créé à chaque appel ouvre à chaque fois un nouveau pool de connexions, et le libérer laisse des sockets en TIME_WAIT ; faites-le dans une boucle et vous manquez de ports. IHttpClientFactory met les handlers en pool et les fait tourner pour que les changements de DNS soient pris en compte.
builder.Services.AddHttpClient<ElidoClient>(client =>
{
client.BaseAddress = new Uri("https://api.elido.app");
client.DefaultRequestHeaders.Authorization =
new("Bearer", builder.Configuration["Elido:ApiKey"]);
})
.AddStandardResilienceHandler(); // retries, timeouts, circuit breaker
public sealed class ElidoClient(HttpClient http, ILogger<ElidoClient> logger)
{
public async Task<string> ShortenAsync(string destination, CancellationToken ct = default)
{
using var request = new HttpRequestMessage(HttpMethod.Post, "/v1/links")
{
Content = JsonContent.Create(new { destination_url = destination }),
};
// stable across retries, so the resilience handler can't create duplicates
request.Headers.Add("Idempotency-Key",
Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(destination))));
using var response = await http.SendAsync(request, ct);
if (!response.IsSuccessStatusCode)
{
var body = await response.Content.ReadAsStringAsync(ct);
logger.LogWarning("shorten failed {Status}: {Body}", (int)response.StatusCode, body);
throw new HttpRequestException($"shorten failed: {(int)response.StatusCode} {body}");
}
var link = await response.Content.ReadFromJsonAsync<LinkResponse>(ct);
return link!.ShortUrl;
}
}
AddStandardResilienceHandler vient de Microsoft.Extensions.Http.Resilience et apporte des retries avec jitter sur 429 et 5xx, des timeouts et un disjoncteur. Il relancera un POST sauf si vous le lui interdisez, et c'est l'en-tête Idempotency-Key qui rend cela sûr. Lire le corps de l'erreur avant de lever l'exception est l'autre moitié : EnsureSuccessStatusCode ne garde que le code de statut, ce qui vous laisse deviner à 3 h du matin quel champ le 400 a refusé. Pour le bulk, Parallel.ForEachAsync avec MaxDegreeOfParallelism = 8 plafonne les requêtes en vol et se lit mieux qu'un sémaphore bricolé. Gardez la clé dans les secrets utilisateur en local, et dans des variables d'environnement ou un coffre en production, jamais dans un appsettings.json versionné.
Raccourcir une URL en PHP, Laravel et WordPress
En PHP, raccourcir une URL fonctionne avec l'extension curl livrée avec PHP, donc même sur un hébergement mutualisé où Composer n'est pas une option :
<?php
$ch = curl_init('https://api.elido.app/v1/links');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('ELIDO_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['destination_url' => 'https://example.com/spring-sale']),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($body === false || $status >= 300) {
throw new RuntimeException("shorten failed: HTTP {$status} {$body}");
}
echo json_decode($body, true, 512, JSON_THROW_ON_ERROR)['short_url'];
Deux lignes existent à cause de bugs que tout le monde rencontre une fois. Sans CURLOPT_RETURNTRANSFER, curl affiche le JSON en sortie et renvoie true, ce qui est la vraie histoire derrière la plupart des questions « ma réponse est vide ». Et CURLOPT_POSTFIELDS passe à l'encodage de formulaire multipart dès que vous lui donnez un tableau : c'est donc json_encode qui en fait une requête JSON. Beaucoup de tutoriels PHP appellent encore urlshortener/v1 de Google ou le point d'accès bit.ly v3 ; les deux ont disparu depuis longtemps, et l'article sur l'alternative au raccourcisseur d'URL de Google explique où sont allés les liens goo.gl.
Le client Http de Laravel avec retries
Dans une application Laravel ou Symfony, le client HTTP de Laravel (une enveloppe de Guzzle) déclare la politique de retry une fois pour toutes :
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Http;
$response = Http::withToken(config('services.elido.key'))
->withHeaders(['Idempotency-Key' => hash('sha256', $destination)])
->timeout(10)
->retry(3, 1000, function (Throwable $e) {
return $e instanceof ConnectionException
|| ($e instanceof RequestException
&& in_array($e->response->status(), [429, 500, 502, 503, 504], true));
}, throw: false)
->post('https://api.elido.app/v1/links', ['destination_url' => $destination]);
$short = $response->successful() ? $response->json('short_url') : null;
Le callback when est ce qui évite qu'un 401 soit relancé trois fois. Guzzle simple se comporte autrement que curl : il lève ClientException sur 4xx et ServerException sur 5xx par défaut, donc attrapez-les ou mettez http_errors à false. Pour un import de nuit, le Pool de Guzzle avec 'concurrency' => 8 est la réponse PHP au parallélisme borné. Un piège propre à PHP : sleep() dans une requête web retient un worker php-fpm pendant tout ce temps, donc les retries appartiennent aux scripts CLI, aux jobs de file et à WP-Cron.
WordPress : wp_remote_post à la publication
WordPress a sa propre couche HTTP, wp_remote_post, qui choisit un transport et marche sur les hébergements où curl est désactivé. Accrochez-la à la publication et stockez le résultat en méta de l'article :
add_action('publish_post', function (int $post_id): void {
if (get_post_meta($post_id, '_elido_short_url', true)) {
return; // already shortened
}
$response = wp_remote_post('https://api.elido.app/v1/links', [
'timeout' => 10,
'headers' => [
'Authorization' => 'Bearer ' . ELIDO_API_KEY,
'Content-Type' => 'application/json',
'Idempotency-Key' => 'post-' . $post_id,
],
'body' => wp_json_encode(['destination_url' => get_permalink($post_id)]),
]);
if (is_wp_error($response) || wp_remote_retrieve_response_code($response) >= 300) {
error_log('shorten failed for post ' . $post_id);
return;
}
$link = json_decode(wp_remote_retrieve_body($response), true);
update_post_meta($post_id, '_elido_short_url', $link['short_url']);
});
Définissez ELIDO_API_KEY dans wp-config.php, pas dans un fichier de plugin qui finit dans un dépôt public ni dans une ligne d'options lisible par tous les administrateurs. La clé post-123 est stable. Déclenchez le hook deux fois et le second appel renvoie le lien existant. Si vous préférez ne maintenir aucun hook, le guide du raccourcisseur d'URL WordPress compare cette voie au plugin et au no-code.
Raccourcir une URL en Ruby
En Ruby, raccourcir une URL est un Net::HTTP.post de la bibliothèque standard : un script n'a besoin d'aucun gem :
require "json"
require "net/http"
response = Net::HTTP.post(
URI("https://api.elido.app/v1/links"),
{ destination_url: "https://example.com/spring-sale" }.to_json,
"Authorization" => "Bearer #{ENV.fetch('ELIDO_API_KEY')}",
"Content-Type" => "application/json",
)
raise "shorten failed: #{response.code} #{response.body}" unless response.is_a?(Net::HTTPSuccess)
puts JSON.parse(response.body)["short_url"]
ENV.fetch plutôt que ENV[] échoue sur-le-champ quand la clé manque, au lieu d'envoyer Bearer et de revenir avec un 401 déroutant. La vérification is_a?(Net::HTTPSuccess) est la ligne que les gens sautent : Net::HTTP lève sur les sockets morts et les timeouts, et sur rien d'autre. Net::HTTP.post ne prend pas non plus d'arguments de timeout, donc pour tout ce qui tourne sans surveillance, passez à Net::HTTP.start avec open_timeout et read_timeout.
Faraday avec un middleware de retry
Dans une application Rails, Faraday vous donne un objet de connexion unique avec l'en-tête d'authentification attaché, du JSON dans les deux sens et une politique de retry déclarée. Depuis Faraday 2, le middleware de retry vit dans le gem séparé faraday-retry.
# Gemfile: gem "faraday" and gem "faraday-retry"
require "digest"
require "faraday"
require "faraday/retry"
ELIDO = Faraday.new(url: "https://api.elido.app") do |f|
f.request :json
f.request :retry, max: 3, interval: 0.5, backoff_factor: 2,
methods: %i[post], # POST is not retried by default
retry_statuses: [429, 500, 502, 503, 504]
f.response :json, content_type: /\bjson$/
f.headers["Authorization"] = "Bearer #{ENV.fetch('ELIDO_API_KEY')}"
f.options.timeout = 10
end
def shorten(destination)
res = ELIDO.post("/v1/links", { destination_url: destination },
"Idempotency-Key" => Digest::SHA256.hexdigest(destination))
raise "shorten failed: #{res.status} #{res.body}" unless res.success?
res.body["short_url"]
end
La ligne methods: %i[post] est celle que la plupart des extraits Faraday en ligne omettent. Par défaut, faraday-retry ne relance que les verbes idempotents (GET, PUT, DELETE, HEAD, OPTIONS) : une configuration POST sans elle ne relance donc jamais rien, et personne ne le remarque jusqu'à ce qu'un 503 fasse perdre un lien. Inscrire POST est sans danger ici précisément grâce à l'Idempotency-Key. Le middleware respecte aussi Retry-After sur un 429. Pour une liste, une Queue et huit threads marchent bien même sur CRuby, puisque le verrou global est relâché pendant les E/S ; dans Rails, enveloppez shorten dans un Active Job pour qu'un retry endormi ne retienne pas un thread Puma.
Raccourcir une URL en PowerShell
En PowerShell, raccourcir une URL est un appel à Invoke-RestMethod, intégré à Windows PowerShell 5.1 et à PowerShell 7, qui rend un objet analysé plutôt qu'une chaîne JSON :
$body = @{ destination_url = 'https://example.com/spring-sale' } | ConvertTo-Json
$link = Invoke-RestMethod -Method Post `
-Uri 'https://api.elido.app/v1/links' `
-Headers @{ Authorization = "Bearer $env:ELIDO_API_KEY" } `
-ContentType 'application/json' `
-Body $body `
-TimeoutSec 10
$link.short_url
ConvertTo-Json n'est pas facultatif. Passez un hashtable directement à -Body et PowerShell l'encode comme un formulaire : vous obtenez un 400 qui ressemble à un problème de serveur. La surprise pour ceux qui viennent d'autres langages va dans le sens inverse : un 401 ou un 404 est ici une erreur terminante, pas une valeur de retour, donc vous attrapez une exception au lieu de vérifier un code de statut. PowerShell 7 ajoute -SkipHttpErrorCheck, qui renvoie la réponse à la place.
Une clé dans un coffre et toute une colonne CSV
Une clé tapée dans un fichier .ps1 finit dans le gestionnaire de versions, dans Get-History et dans toute transcription que le parc a activée. Le module SecretManagement la garde dans un coffre : renouveler une clé revient alors à mettre à jour une seule entrée au lieu de fouiller les scripts de quatre serveurs. La vraie tâche courante est une colonne de tableur plutôt qu'une URL :
$env:ELIDO_API_KEY = Get-Secret -Name ElidoApiKey -AsPlainText
function Get-ShortLink([string] $Destination) {
$sha = [System.Security.Cryptography.SHA256]::Create()
$key = [BitConverter]::ToString(
$sha.ComputeHash([Text.Encoding]::UTF8.GetBytes($Destination))).Replace('-', '')
$body = @{ destination_url = $Destination } | ConvertTo-Json
for ($attempt = 0; $attempt -lt 3; $attempt++) {
try {
return (Invoke-RestMethod -Method Post -Uri 'https://api.elido.app/v1/links' `
-Headers @{ Authorization = "Bearer $env:ELIDO_API_KEY"; 'Idempotency-Key' = $key } `
-ContentType 'application/json' -Body $body -TimeoutSec 10).short_url
}
catch {
$status = [int]$_.Exception.Response.StatusCode
if ($status -ne 429 -and $status -lt 500) { throw } # 400, 401, 403: give up
Start-Sleep -Seconds ([math]::Pow(2, $attempt))
}
}
throw "gave up after 3 attempts: $Destination"
}
$funcDef = ${function:Get-ShortLink}.ToString()
Import-Csv .\campaign-urls.csv | ForEach-Object -ThrottleLimit 8 -Parallel {
${function:Get-ShortLink} = $using:funcDef # ship the function into the runspace
$row = $_
$short = try { Get-ShortLink $row.destination } catch { "ERROR: $($_.Exception.Message)" }
[pscustomobject]@{ id = $row.id; destination = $row.destination; short_url = $short }
} | Export-Csv .\campaign-urls-short.csv -NoTypeInformation
-ThrottleLimit 8 garde huit requêtes en vol que le fichier compte cinquante lignes ou cinquante mille. Chaque itération -Parallel s'exécute dans son propre runspace, ce qui explique que la fonction y entre par $using:, et toute la construction exige PowerShell 7 ; en 5.1, un simple foreach est la réponse honnête pour quelques centaines de lignes. Les erreurs atterrissent dans la colonne de sortie, et comme la clé d'idempotence vient de la destination, relancer le fichier après correction de trois lignes ne crée aucun doublon. Vérifiez quelques lignes avec curl.exe -sI avant que quoi que ce soit parte à l'impression. Le CSV est déjà dans un Google Sheet ? L'import en masse sans code fait le même travail.
Ce que font les SDK aujourd'hui
Des bibliothèques clientes officielles existent pour quatre langages (TypeScript sous le nom @elido/sdk, Python, Go, Ruby) et suivent la spécification OpenAPI. Seul le SDK Go est publié pour l'instant, sous la forme du module github.com/elidoapp/elido-go (v0.1.0). Les SDK TypeScript, Python et Ruby n'ont pas encore été publiés sur npm, PyPI ni RubyGems. C'est pourquoi tous les exemples ci-dessus utilisent le client HTTP propre à chaque langage, ce qui est aussi ce que nous supportons pour les intégrations de production aujourd'hui. Quand les autres bibliothèques sortiront, la page API et SDK portera les instructions d'installation.
Une fois publiées, attendez-vous à ce qu'elles enveloppent les routes liées à l'espace de travail, donc vous passerez explicitement l'ID de l'espace et un domain_id, et à ce qu'elles diffèrent par leur comportement de retry. Le client TypeScript ajoute une Idempotency-Key à chaque POST, PUT ou PATCH et relance 502, 503 et 504 avec backoff, en vous laissant 429. Le client Ruby génère aussi des clés. Il lève une erreur typée de limite de débit portant retry_after mais ne relance jamais de lui-même. Les clients Python et Go ne relancent pas du tout. Quel que soit celui que vous utilisez, la politique de retry de la section sur les erreurs s'applique toujours.
Pour un terminal plutôt qu'une base de code, le guide de la CLI de raccourcisseur d'URL enveloppe le même appel de création dans curl et une fonction shell, et le guide du serveur MCP relie l'API à Claude, Cursor ou tout autre client compatible MCP.
Accès à l'analytique
Les points d'accès d'analytique sont en lecture seule et se trouvent sous /v1/workspaces/{id}/analytics/ ; le guide de l'API d'analytique des liens liste chaque rapport, ses paramètres et la forme de sa réponse. Les requêtes les plus courantes :
GET .../clicks/recent?from=…&to=…: les clics individuels, du plus récent au plus ancien, paginés avecnext_cursor. Utile pour les pipelines d'export.GET .../timeseries?from=…&to=…&interval=day: des nombres de clics par intervalle ;intervalvauthourouday, ettzfixe le fuseau horaire des intervalles.GET .../breakdown/country?from=…&to=…: la répartition géographique.GET .../breakdown/referrer?from=…&to=…: la répartition par référent.
Les autres rapports sont summary, links/top, les autres répartitions (host, device, browser, destination) et les classements (top-countries, top-regions, top-cities, top-referrers, top-destinations). from et to sont des dates YYYY-MM-DD et to est exclusif ; ajoutez link_id pour restreindre n'importe quel rapport à un lien, et limit pour dimensionner les répartitions et les classements.
Le flux brut des clics est le plus volumineux. Un espace de travail à 10M de clics par mois produit environ 600 Mo de JSON d'événements bruts par mois : parcourez-le à intervalles réguliers plutôt qu'en une seule séance.
Webhooks pour les événements de lien
Les webhooks sont l'inverse du polling : au lieu de demander à l'API ce qui a changé, vous recevez les événements de liens et de domaines sur votre point d'accès. Configurez-les sur /dashboard/webhooks avec une URL de destination, les événements voulus (link.created, link.updated, link.expired et d'autres) et un secret de signature.
Un événement click.created par clic est prévu sur la feuille de route et pas encore disponible : les données de clics passent donc pour l'instant par les points d'accès d'analytique. Chaque livraison porte un en-tête X-Elido-Signature (envoyé aussi sous le nom X-Webhook-Signature) de valeur v1=<hex> : un HMAC-SHA256, calculé avec le secret de votre point d'accès, sur la valeur de X-Webhook-Timestamp, un point et le corps brut de la requête. Vérifiez-le avant tout traitement, faute de quoi n'importe quel appelant peut poster sur votre point d'accès et se faire passer pour Elido ; le guide pour vérifier les signatures de webhook propose la vérification dans plusieurs langages.
La livraison est au moins une fois. Les livraisons échouées reçoivent trois tentatives au total, espacées de quelques minutes. La comparaison webhooks et polling pèse les deux modèles d'intégration.
Un exemple concret : l'automatisation de campagne
L'intégration qui motive la plupart des adoptions d'API ressemble à ceci. Votre outil d'automatisation marketing crée une campagne dans Customer.io ou HubSpot, et un hook se déclenche à sa publication. Votre gestionnaire crée le lien court. Puis il le stocke sur la campagne et le renvoie pour le modèle d'e-mail. En Node :
export async function onCampaignPublished(campaign: Campaign) {
const res = await fetch("https://api.elido.app/v1/links", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ELIDO_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": `campaign-${campaign.id}-link`,
},
body: JSON.stringify({
destination_url: campaign.destinationUrl, // UTM parameters already in here
tags: [
"campaign",
`campaign:${campaign.id}`,
`batch:${campaign.batchId}`,
campaign.channel,
],
}),
signal: AbortSignal.timeout(10_000),
});
if (!res.ok)
throw new Error(`shorten failed: ${res.status} ${await res.text()}`);
const link = await res.json();
await campaignStore.update(campaign.id, {
linkId: link.id,
shortUrl: link.short_url,
});
return link;
}
La clé d'idempotence vient de l'identifiant de campagne. Quand le hook de publication se déclenche deux fois (cela arrivera, puisque les livraisons sont au moins une fois), le second appel récupère le même lien au lieu d'un doublon. Les tags campaign: et batch: sont vos propres clés de jointure. Les rapports de clics se rattachent à la campagne grâce à elles. Pour l'attribution de bout en bout avec modèles UTM et transfert des conversions, le guide du suivi UTM déroule le pipeline complet.
Ce qui n'est pas encore dans l'API
Certaines choses que les équipes demandent ne sont pas disponibles aujourd'hui :
- Analytique en un appel. Une requête unique renvoyant toutes les répartitions d'un lien n'existe pas ; aujourd'hui vous demandez séparément clics, pays, référent, appareil et série temporelle : lancez-les en parallèle depuis votre propre code.
- Rejeu de webhook. Le tableau de bord montre l'historique des livraisons et sait les rejouer ; l'API ne le peut pas encore.
- Renommage avec 301. Il en va de même pour les champs
utmetmetadataà la création ; en attendant, mettez les paramètres UTM dans la destination et les clés de jointure dans les tags.
Ma règle empirique : si c'est dans la spécification OpenAPI, construisez dessus. Si ce n'est que dans un article de blog, celui-ci compris, considérez que c'est prévu.
À lire aussi sur le blog
Ce guide se situe dans le cluster des fonctionnalités, à côté de les liens intelligents expliqués, qui décrit comment le moteur de redirection résout un lien à la périphérie.
- API de raccourcisseur d'URL : limites de débit, retries et idempotence : durcir l'intégration pour le trafic de production.
- Les raccourcisseurs d'URL pour développeurs : ce qu'il faut vérifier dans une API avant de bâtir dessus.
- Suivi des conversions côté serveur via les liens courts : prolonger l'API jusqu'au transfert des conversions.
- Comment raccourcir une URL sans code : le tableau de bord, le téléphone et le tableur.
- Surface produit :
/features/api-sdkset/solutions/developers.
Questions fréquentes
Comment raccourcir une URL avec une API ?
Envoyez un POST authentifié avec l'URL longue dans un corps JSON et lisez le lien court dans la réponse. Chez Elido, c'est POST https://api.elido.app/v1/links avec l'en-tête Authorization: Bearer suivi de votre clé API et le corps {"destination_url": "..."} ; la réponse 201 contient short_url. Chaque langage de ce guide n'est qu'une enveloppe autour de cette unique requête.
Comment raccourcir une URL en Python ?
Utilisez requests.post sur le point d'accès des liens, avec votre clé dans un en-tête Authorization: Bearer, json={"destination_url": url} et un timeout, appelez raise_for_status(), puis lisez response.json()["short_url"]. Pour des milliers d'URL, passez à httpx.AsyncClient avec un asyncio.Semaphore afin de ne garder en vol qu'une poignée de requêtes à la fois.
Comment raccourcir une URL en JavaScript ou Node.js ?
Appelez fetch depuis Node 18 ou plus récent avec la méthode POST, l'en-tête Bearer et un corps JSON, vérifiez res.ok avant de lire le corps, puis prenez short_url dans le JSON analysé. Exécutez-le sur un serveur ou dans une route serverless, jamais dans le code du navigateur : une clé livrée à la page peut être lue par quiconque ouvre DevTools.
Faut-il un SDK pour utiliser une API de raccourcisseur d'URL ?
Non. Créer un lien tient en un POST avec deux en-têtes, et le client HTTP standard de chaque langage sait l'envoyer : requests ou httpx, fetch, net/http, java.net.http, HttpClient, curl, Net::HTTP et Invoke-RestMethod. Un SDK devient utile quand vous employez beaucoup de points d'accès et voulez des modèles typés, mais les versions HTTP simples de ce guide sont ce que nous supportons en production aujourd'hui.
Comment éviter que les retries créent des liens courts en double ?
Envoyez un en-tête Idempotency-Key et gardez-le identique à chaque nouvel essai d'une même requête logique, par exemple un hash de la destination ou l'identifiant de votre commande. Elido conserve la première réponse réussie pendant 24 heures par espace de travail et la rejoue pour une clé répétée : un retry après une réponse perdue renvoie le lien d'origine au lieu d'en créer un second.
Combien de liens courts puis-je créer par seconde via l'API ?
Elido limite le trafic API par espace de travail avec un token bucket : 10 requêtes par seconde avec un burst de 20 sur Free, 30 avec un burst de 60 sur Pro, et 100 avec un burst de 200 sur Business. Une réponse 429 porte Retry-After: 1. Pour les gros lots, POST /v1/links/bulk accepte jusqu'à 100 liens par requête.
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.