Un'API per abbreviare URL è una delle integrazioni più piccole nel backlog di un tipico team di ingegneria. Tre endpoint, un header di autenticazione, un payload JSON. La pagina della documentazione promette la prima chiamata in cinque minuti. Poi arriva il traffico di produzione, la logica di retry crea link duplicati, la dashboard si riempie di varianti /foo-1, /foo-2, /foo-3 della stessa destinazione e qualcuno apre un ticket.
Questo post analizza l'integrazione reale. L'autenticazione, la prima chiamata, i quattro endpoint che coprono la maggior parte dei casi d'uso, l'idempotenza, la gestione degli errori, i rate limit e le insidie della produzione che le guide rapide da cinque minuti tralasciano. Esempi di codice in TypeScript, Python, Go, Ruby e PHP — i primi tre attraverso gli SDK ufficiali (@elido/sdk, elido-python, github.com/elido/elido-go), gli ultimi due tramite semplici client HTTP.
Prerequisiti#
Accedi alla dashboard, vai su /settings/api e crea un personal access token. I token sono legati al workspace: un token emesso nel workspace A non può creare link nel workspace B. I token per i service-account (per sistemi di CI, strumenti interni, integrazioni machine-to-machine) vengono creati nella stessa schermata sui piani Pro e superiori; hanno scope espliciti (links:write, analytics:read, domains:write) e ruotano indipendentemente dai token personali.
L'URL di base è https://api.elido.app/v1. I domini di redirect (f.elido.me, s.elido.me, b.elido.me) sono separati dalla superficie dell'API. I tuoi link brevi si risolvono sul dominio di redirect; l'API serve per crearli, modificarli e leggerli.
La specifica OpenAPI è pubblicata su https://api.elido.app/v1/openapi.json e conforma a OpenAPI 3.1. Gli SDK ufficiali sono generati da tale specifica e ripubblicati a ogni rilascio dell'API; puoi anche generare il tuo client in qualsiasi linguaggio supportato da OpenAPI.
La prima chiamata#
Crea un link breve dall'URL di destinazione. Cinque righe in 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 (senza SDK ufficiale — utilizzando 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'];
Tutti e cinque producono lo stesso risultato. Il corpo della risposta contiene l'URL breve, l'ID canonico del link, l'ID del workspace e il timestamp di creazione. Lo slug — abc123 nell'esempio sopra — è generato dal server a meno che non si passi custom_slug nella richiesta. L'alfabeto dello slug è base62 ([0-9A-Za-z]); la lunghezza predefinita è di sei caratteri.
I quattro endpoint che userai davvero#
L'API ha più di quattro endpoint, ma la maggior parte delle integrazioni rimane all'interno di questo set.
Crea un link#
POST /v1/links accetta l'URL di destinazione più alcuni campi opzionali:
custom_slug— uno slug a tua scelta (deve essere univoco all'interno del workspace).domain_id— per i link su dominio personalizzato; se omesso, viene utilizzato il dominio primario del workspace.tags— un array di stringhe libere per l'organizzazione.utm— parametri di campagna da aggiungere alla destinazione al momento del redirect.expires_at— timestamp ISO 8601 dopo il quale il link restituisce 410 Gone.password— se impostata, il redirect mostra una pagina di inserimento password prima di procedere.metadata— oggetto JSON opaco che il redirect non interpreta; utile per le proprie chiavi di join.
Lo slug personalizzato è il campo che crea più problemi ai team in produzione. Se passi uno slug già in uso da un altro link nello stesso workspace, l'API restituisce 409 Conflict. Un gestore di retry ingenuo che aggiunge un contatore (my-slug-1, my-slug-2) produce il problema dei link duplicati descritto in apertura. Il comportamento corretto per il retry è descritto nella sezione sull'idempotenza più in basso.
Leggi un link#
GET /v1/links/{id} restituisce il record completo del link, inclusi il conteggio attuale dei click, il timestamp dell'ultimo click e tutta la configurazione. L'ID del link è l'identificatore canonico — gli slug possono cambiare (il piano Pro+ supporta il rinnovo dello slug), gli ID no.
GET /v1/links?domain_id=…&tag=…&limit=… elenca i link nel workspace con filtri. La paginazione è basata su cursore; il campo next_cursor nella risposta è opaco e va inviato come parametro query cursor nella richiesta successiva.
Aggiorna un link#
PATCH /v1/links/{id} accetta gli stessi campi della creazione. Gli aggiornamenti più comuni: cambiare l'URL di destinazione (utile per la rotazione delle campagne senza dover ristampare i codici QR), cambiare i tag, estendere expires_at. L'aggiornamento dello slug avviene tramite un endpoint separato POST /v1/links/{id}/rename che gestisce il redirect 301 dal vecchio slug per un periodo di conservazione configurabile (predefinito 30 giorni).
Elimina un link#
DELETE /v1/links/{id} esegue una eliminazione logica (soft-delete). Il link restituisce 410 Gone per i successivi 90 giorni, poi viene eliminato definitivamente. La vista cestino della dashboard mostra i link eliminati logicamente; puoi ripristinarli tramite la dashboard o tramite POST /v1/links/{id}/restore entro la finestra di 90 giorni.
Chiavi di idempotenza#
Ogni richiesta di modifica — POST, PATCH, DELETE — accetta un header Idempotency-Key. Il valore dell'header è una stringa opaca fino a 255 caratteri; il server memorizza il corpo della risposta e il codice di stato per 24 ore associandoli a (workspace_id, idempotency_key) e restituisce la risposta memorizzata se la stessa chiave viene presentata di nuovo.
Gli SDK ufficiali generano automaticamente le chiavi di idempotenza quando non fornite. Puoi sovrascriverle:
const link = await elido.links.create(
{ destinationUrl: "https://shop.example.com/spring-sale" },
{ idempotencyKey: "order-12345-link" },
);
Il caso d'uso tipico è un ciclo di retry. Se il tuo job crea un link come parte dell'elaborazione di un ordine a monte, genera la chiave di idempotenza dall'ID dell'ordine. Un retry dello stesso job vedrà la stessa chiave, colpirà la cache di idempotenza e restituirà il link creato originariamente invece di produrne un secondo.
L'insidia principale: la cache di idempotenza vive per 24 ore, non per sempre. Un retry al terzo giorno di un job bloccato creerà un nuovo link. Se l'integrazione viene eseguita su batch di più giorni, memorizza l'ID del link restituito dalla prima creazione riuscita e cercalo prima di emetterlo nuovamente.
Una seconda insidia: l'idempotenza è per workspace. La stessa chiave in due workspace diversi creerà due link. Questa è la semantica corretta per un'API multi-workspace, ma può sorprendere i team che presumono che la chiave sia globalmente univoca.
Gestione degli errori#
L'API restituisce codici di stato HTTP standard e un corpo di errore strutturato:
{
"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
}
}
I codici che vedrai più spesso:
400 invalid_request— fallimento della validazione del payload. Il campomessageelenca i campi specifici. Non riprovare; correggi il payload.401 unauthorized— token mancante o non valido. Non riprovare senza aver ruotato il token.403 forbidden— il token non ha lo scope richiesto. Controlla l'elenco degli scope del token su/settings/api.404 not_found— la risorsa non esiste o il token non ha accesso ad essa (restituiamo 404 invece di 403 per evitare di confermare l'esistenza di risorse a chiamanti non autorizzati).409 conflict— slug già in uso, o rilevata modifica simultanea (PATCH su una versione obsoleta). Recupera nuovamente i dati e riprova.429 rate_limit_exceeded— attendi secondo il valoreretry_after.500 internal_server_error— errore lato server. È sicuro riprovare con la stessa chiave di idempotenza.502 bad_gateway,503 service_unavailable,504 gateway_timeout— problemi infrastrutturali temporanei. Attendi e riprova.
Gli SDK ufficiali implementano il backoff esponenziale con jitter per i codici 429, 500, 502, 503 e 504. Non eseguono retry per 400, 401, 403, 404 o 409 — si tratta di errori di programmazione o conflitti di logica di business, non di guasti temporanei. I client HTTP personalizzati dovrebbero seguire lo stesso schema; riprovare un 400 con lo stesso payload non produrrà un risultato diverso.
Il request_id nel corpo dell'errore è il campo da includere nei ticket di supporto. Possiamo tracciare qualsiasi richiesta da quell'ID attraverso l'audit log, il log dell'applicazione e le metriche della piattaforma — e non possiamo tracciare una richiesta senza di esso.
Rate limit#
I rate limit pubblicati sono di 100 richieste al secondo per workspace su Pro, 500 su Business e un limite negoziato su Enterprise. Il piano Free è di 10 req/s.
Lo stato del rate limit è esposto in tre header in ogni risposta dell'API:
X-RateLimit-Limit— il limite attuale al secondo.X-RateLimit-Remaining— richieste rimanenti nel secondo attuale.X-RateLimit-Reset— timestamp Unix di quando il bucket si resetta.
Il limite di 100/s è un'implementazione token-bucket con una capacità di burst di 200 — il che significa che puoi emettere 200 richieste contemporaneamente se il bucket è pieno, per poi stabilizzarti sulla velocità sostenuta di 100/s. La maggior parte dei job di creazione di link brevi rientra comodamente nel burst; le integrazioni pesanti di analytics che paginano attraverso eventi di click storici beneficiano del margine offerto dal piano Pro.
Per le operazioni massive su Business+, l'endpoint POST /v1/links/bulk accetta fino a 1000 link per richiesta e conta come una singola unità di rate limit. Questo è l'endpoint corretto per qualsiasi job che crei più di cento link alla volta.
Cosa offrono gli SDK rispetto al semplice HTTP#
Gli SDK ufficiali includono quattro funzionalità che si ripagano rapidamente:
- Retry automatico con backoff per i codici di stato che lo consentono.
- Generazione della chiave di idempotenza quando non fornita esplicitamente.
- Errori tipizzati in modo da poter fare
catch (err) { if (err instanceof ElidoRateLimitError) { … } }invece di analizzare il JSON nei blocchi catch. - Iteratori per la paginazione in modo che gli endpoint di elenco espongano iteratori asincroni o generatori invece di richiedere la gestione manuale del cursore.
L'SDK Go espone inoltre il client HTTP sottostante per la strumentazione — utile se desideri collegarlo alla tua configurazione di tracciamento esistente. La pagina delle funzionalità API + SDKs del repository copre l'intera superficie; il riferimento API è pubblicato su /docs/api-reference.
Accesso agli analytics#
Gli endpoint di analytics sono in sola lettura e si trovano in /v1/workspaces/{id}/analytics/. Le query più comuni:
GET .../links/{id}/clicks?from=…&to=…— eventi di click grezzi con paginazione. Utile per le pipeline di esportazione.GET .../timeseries?from=…&to=…&bucket=day— conteggi di click raggruppati per un intervallo di tempo.GET .../breakdown/country?from=…&to=…— ripartizione geografica.GET .../breakdown/referrer?from=…&to=…— ripartizione per referrer.
Il feed degli eventi di click grezzi è il più voluminoso. Un workspace con 10 milioni di click al mese produce circa 600 MB di JSON al mese di dati sugli eventi grezzi. Per esportazioni di questa portata, la guida all'esportazione su ClickHouse illustra il meccanismo di esportazione bulk che evita l'involucro JSON e trasmette i dati direttamente dal warehouse di analytics.
Webhook per gli eventi di click#
I webhook sono l'opposto del polling: invece di chiedere all'API nuovi click, è l'API a inviarli al tuo endpoint. Configurali su /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,
});
Ogni invio include un header Elido-Signature contenente un HMAC-SHA256 del corpo della richiesta con il tuo segreto condiviso. Verifica la firma prima dell'elaborazione — senza di essa, qualsiasi chiamante potrebbe inviare dati al tuo endpoint webhook spacciandosi per Elido.
La semantica di invio è at-least-once (almeno una volta) con backoff esponenziale fino a una conservazione massima di 72 ore. Per i dettagli sulla forma dei dati e sul comportamento dei retry, il post webhook vs polling confronta i due modelli di integrazione.
Un esempio pratico: automazione delle campagne#
L'integrazione che spinge maggiormente verso l'adozione dell'API è simile a questa. La tua automazione di marketing crea una campagna in Customer.io o HubSpot. Un hook viene attivato quando la campagna viene pubblicata. Il tuo gestore crea il link breve, lo associa al record della campagna e lo restituisce allo strumento di gestione delle campagne per inserirlo nel template dell'email.
In 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 chiave di idempotenza è derivata dall'ID della campagna. Se l'hook di pubblicazione della campagna viene attivato due volte (succede — l'invio dei webhook è at-least-once), la seconda chiamata restituisce lo stesso link senza crearne uno duplicato. Il campo metadata conserva le tue chiavi di join, così puoi correlare gli eventi di click di Elido alla campagna senza dover analizzare i tag.
Per l'attribuzione end-to-end delle campagne con template UTM e inoltro delle conversioni, l'articolo tracciamento UTM end-to-end illustra l'intera pipeline.
Cosa non è ancora presente nell'API#
Due funzionalità richieste di frequente, ma attualmente non disponibili:
- Una singola GET di analytics per link che restituisca tutte le ripartizioni in un'unica chiamata. Il modello attuale richiede chiamate separate per click, nazione, referrer, dispositivo e serie temporali. L'aggregazione è in roadmap; per ora, gli SDK parallelizzano le richieste con un singolo metodo di supporto.
- Replay dei webhook dall'API. La dashboard mostra lo storico degli invii dei webhook e supporta il replay; l'API non lo fa ancora. Anche questo è in roadmap.
Se una funzionalità è presente nella specifica OpenAPI, è supportata. Se è presente in questo post ma non nella specifica, considerala pianificata ma non garantita.
Letture correlate#
- Spiegazione dei link intelligenti — il pilastro per il cluster delle funzionalità; spiega come il motore di redirect risolve un link all'edge.
- Webhook vs polling per il tracciamento dei click — quando usare l'uno o l'altro modello di integrazione.
- Tracciamento delle conversioni lato server tramite link brevi — estendere l'API nel flusso di inoltro delle conversioni.
- Importazione massiva di campagne da Google Sheets — un esempio pratico dell'endpoint bulk.
- Approfondimento operativo: la guida al server MCP per connettere l'API di Elido a Claude, Cursor e altri client compatibili con MCP.
- Panoramica del prodotto:
/features/api-sdkse/solutions/developers.