13 min di letturaFunzionalità

API per URL shortener: una guida rapida di 30 minuti in cinque linguaggi

Da zero all'automazione dei link brevi funzionante in TypeScript, Python, Go, Ruby e PHP: autenticazione, idempotenza, gestione degli errori e le insidie della produzione.

Marius Voß
DevRel · edge infra
Diagramma di avvio rapido in cinque linguaggi con pannelli di codice per TypeScript, Python, Go, Ruby e PHP, tutti puntati verso un endpoint API Elido centrale

Un'API per URL shortener è una delle integrazioni più piccole nel backlog tipico di un 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 articolo ripercorre l'integrazione reale. Autenticazione, la prima chiamata, i quattro endpoint che coprono la maggior parte dei casi d'uso, idempotenza, gestione degli errori, limiti di frequenza e le insidie di produzione che la guida rapida di cinque minuti salta. Esempi di codice in TypeScript, Python, Go, Ruby e PHP: i primi tre tramite 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 /dashboard/api-keys e crea una chiave API (inizia con elido_). I token sono legati al workspace: un token emesso nel workspace A non può creare link nel workspace B. I token per utenti macchina (per sistemi CI, strumenti interni, integrazioni machine-to-machine) vengono creati sotto /dashboard/machine-users e ruotano in modo indipendente dalle chiavi personali. Entrambi i tipi hanno un ruolo di workspace preimpostato (viewer, editor o admin) piuttosto che permessi per singolo endpoint, quindi assegna editor a un job CI se si limita a creare link. La guida alle autorizzazioni delle chiavi API spiega cosa può raggiungere ciascun ruolo, incluso il motivo per cui le modifiche ai webhook richiedono admin.

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 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://elido.app/openapi.json e rispetta OpenAPI 3.1. Gli SDK ufficiali sono generati da quella specifica e ripubblicati a ogni release dell'API; puoi anche generare un tuo client in qualsiasi linguaggio supportato da OpenAPI.

La prima chiamata

Crea un link breve a partire 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 (nessun SDK ufficiale, si usa 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 tu non passi 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 resta all'interno di questo insieme.

Diagramma a raggiera dei quattro endpoint principali per i link intorno alla risorsa /v1/links: POST per creare, GET per leggere, PATCH per aggiornare e DELETE per eliminare, ciascuno con la sua insidia principale.

POST /v1/links accetta l'URL di destinazione più campi opzionali:

  • slug - uno slug scelto da te (deve essere unico sul dominio).
  • domain_id - per link su dominio personalizzato; su /v1/links viene usato il dominio breve predefinito del tuo piano se omesso. Il percorso con ambito workspace /v1/workspaces/{workspace_id}/links lo richiede.
  • title - un'etichetta mostrata nella dashboard.
  • tags - un array di stringhe libere per l'organizzazione.
  • expires_at - timestamp RFC 3339 dopo il quale il link restituisce 410 Gone.
  • redirect_status - 301, 302 (il valore predefinito) oppure 307.
  • password - non ancora accettato in fase di creazione; impostalo con un PATCH subito dopo, e il redirect mostrerà una pagina con password prima di inoltrare.
  • utm e metadata - pianificati. Oggi inserisci i parametri UTM direttamente in destination_url e conserva le tue chiavi di join in tags.

Lo slug personalizzato è il campo che morde i team in produzione. Se passi uno slug già in uso da un altro link sullo stesso dominio, l'API restituisce 409 Conflict. Il 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 di retry corretto è descritto nella sezione sull'idempotenza più sotto.

GET /v1/links/{id} restituisce il record completo del link, incluso short_url e tutta la configurazione. I conteggi dei click non sono nel record del link; provengono dagli endpoint di analytics qui sotto. L'ID del link è l'identificatore canonico: gli slug possono cambiare, gli ID no.

GET /v1/links?host=…&tags=…&limit=… elenca i link nel workspace con filtri. La paginazione è basata su cursore; next_cursor nella risposta è opaco e va passato come parametro di query cursor nella richiesta successiva.

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 ristampare i codici QR), cambiare i tag, estendere expires_at. Lo slug si cambia tramite lo stesso PATCH, inviando un nuovo slug. Il vecchio slug smette immediatamente di risolversi; un endpoint di rinomina dedicato che mantenga un 301 dal vecchio slug per un periodo di conservazione è pianificato, non ancora realizzato.

DELETE /v1/links/{id} esegue un soft delete e restituisce 204 No Content. Il link smette di eseguire il redirect e scompare dalle richieste di elenco e lettura. Una vista cestino con un endpoint di ripristino e una finestra di 90 giorni prima dell'eliminazione definitiva è pianificata; oggi non esiste una chiamata API per riportare indietro un link eliminato.

Chiavi di idempotenza

Ogni richiesta che modifica dati, 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, indicizzati su (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 vengono fornite. Puoi sovrascriverle:

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

Il caso d'uso è 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 a partire dall'ID dell'ordine. Un retry dello stesso job vede la stessa chiave, colpisce la cache di idempotenza e restituisce il link creato originariamente invece di produrne un secondo.

Pipeline in cui un hook di campagna at-least-once genera due chiamate di creazione con la stessa chiave di idempotenza; la cache di 24 ore deduplica la seconda cosicché venga creato esattamente un link.

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 gira su batch multi-giorno, memorizza l'ID del link restituito dalla prima creazione riuscita e cercalo prima di riemettere la richiesta.

Una seconda insidia: l'idempotenza è per workspace. La stessa chiave in due workspace crea due link. Questa è la semantica corretta per un'API multi-workspace, ma può sorprendere i team che presumono che la chiave sia globalmente unica.

Gestione degli errori

L'API restituisce codici di stato HTTP standard più 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 - errore di validazione del payload. Il campo message elenca i campi specifici. Non riprovare; correggi il payload.
  • 401 unauthorized - token mancante o non valido. Non riprovare senza ruotare il token.
  • 403 forbidden - il ruolo del token non consente l'azione (una chiave viewer non può creare link). Verifica il ruolo della chiave su /dashboard/api-keys.
  • 404 not_found - la risorsa non esiste o il token non ha accesso ad essa (restituiamo 404 invece di 403 per evitare di rivelare l'esistenza della risorsa a chiamanti non autorizzati).
  • 409 conflict - slug già in uso, oppure modifica simultanea rilevata (PATCH su una versione obsoleta). Ricarica e riprova.
  • 429 rate_limit_exceeded - rallenta in base al valore di retry_after.
  • 500 internal_server_error - guasto lato server. È sicuro riprovare con la stessa chiave di idempotenza.
  • 502 bad_gateway, 503 service_unavailable, 504 gateway_timeout - problemi infrastrutturali transitori. Rallenta e riprova.

Gli SDK ufficiali implementano il backoff esponenziale con jitter per 429, 500, 502, 503 e 504. Non riprovano per 400, 401, 403, 404 o 409: sono errori di programmazione o conflitti di logica di business, non guasti transitori. I client HTTP personalizzati dovrebbero seguire lo stesso schema; riprovare un 400 con lo stesso payload non produrrà un risultato diverso.

Schema decisionale che suddivide i codici di stato dell'API in una colonna retry con backoff (429, 500, 502, 503, 504) e una colonna da non riprovare (400, 401, 403, 404, 409) per errori di programmazione e conflitto.

Il request_id nel corpo dell'errore è il campo da includere nei ticket di supporto. Possiamo tracciare qualsiasi richiesta a partire da quell'ID attraverso il log di audit, il log dell'applicazione e le metriche di piattaforma, e non possiamo tracciare una richiesta senza di esso.

Limiti di frequenza

I limiti di frequenza pubblicati sono 100 richieste al secondo per workspace su Pro, 500 su Business e un limite negoziato su Enterprise. Il livello Free è di 10 req/s.

Lo stato del limite di frequenza è esposto in tre header di risposta su ogni risposta dell'API:

  • X-RateLimit-Limit - il limite attuale al secondo.
  • X-RateLimit-Remaining - richieste rimanenti nel secondo corrente.
  • X-RateLimit-Reset - timestamp Unix in cui il bucket si azzera.

Il limite di 100/s è un'implementazione a token bucket con una capacità di burst di 200, il che significa che puoi emettere 200 richieste in una volta se il bucket è pieno, per poi assestarti sul tasso sostenuto di 100/s. La maggior parte dei job di creazione di link brevi rientra comodamente nel burst; le integrazioni fortemente orientate all'analytics che scorrono la cronologia degli eventi di click beneficiano del margine offerto dal livello Pro.

Per le operazioni in blocco, l'endpoint POST /v1/links/bulk accetta fino a 100 link per richiesta e conta come una singola unità di rate limit. Questo è l'endpoint giusto per qualsiasi job che crea più di cento link alla volta. Per un approfondimento su come gestire il ritmo rispetto al token bucket, su quali codici di stato riprovare e su come le chiavi di idempotenza evitano che i retry duplichino i link, vedi limiti di frequenza, retry e idempotenza in produzione.

Cosa fanno gli SDK che l'HTTP semplice non fa

Gli SDK ufficiali offrono quattro cose che si ripagano rapidamente:

  • Retry automatico con backoff per i codici di stato che si possono riprovare.
  • Generazione della chiave di idempotenza quando non viene fornita esplicitamente.
  • Errori tipizzati, così puoi scrivere catch (err) { if (err instanceof ElidoRateLimitError) { … } } invece di analizzare JSON nei blocchi catch.
  • Iteratori di paginazione, così gli endpoint di elenco espongono 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 vuoi collegarlo alla tua configurazione di tracing esistente. La pagina delle funzionalità API + SDK del repository copre l'intera superficie; il riferimento API è pubblicato su /docs/api-reference.

Accesso alle analytics

Gli endpoint di analytics sono di sola lettura e si trovano sotto /v1/workspaces/{id}/analytics/; la guida all'API delle analytics dei link elenca ogni report, i relativi parametri e la struttura della risposta. Le query più comuni:

  • GET .../clicks/recent?from=…&to=… - singoli click, dal più recente, con paginazione tramite next_cursor. Utile per pipeline di export.
  • GET .../timeseries?from=…&to=…&interval=day - conteggi di click raggruppati per intervallo di tempo; interval può essere hour oppure day, e tz imposta il fuso orario dei raggruppamenti.
  • GET .../breakdown/country?from=…&to=… - ripartizione geografica.
  • GET .../breakdown/referrer?from=…&to=… - ripartizione per referrer.

Gli altri report sono summary, links/top, le restanti ripartizioni (host, device, browser, destination) e le liste dei primi risultati (top-countries, top-regions, top-cities, top-referrers, top-destinations). from e to sono date nel formato YYYY-MM-DD e to è esclusivo; aggiungi link_id per restringere qualsiasi report a un solo link e limit per dimensionare le ripartizioni e le liste dei primi risultati.

Il flusso degli eventi di click grezzi è il più grande. Un workspace con 10 milioni di click al mese produce circa 600 MB di dati JSON grezzi al mese. Per export a questa scala, la guida all'export delle analytics copre il meccanismo di export in blocco che bypassa l'involucro JSON e trasmette direttamente dal data warehouse di analytics.

I webhook sono l'inverso del polling: invece di chiedere tu all'API cosa è cambiato, è l'API a consegnare eventi su link e domini al tuo endpoint. Configurali su /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 evento click.created per singolo click è nella roadmap ma non ancora disponibile, quindi oggi i dati sui click arrivano dagli endpoint di analytics. Ogni consegna include un header X-Elido-Signature (inviato anche come X-Webhook-Signature) con valore v1=<hex>: un HMAC-SHA256, con chiave il secret del tuo endpoint, calcolato sul valore di X-Webhook-Timestamp, un punto e il corpo grezzo della richiesta. Verifica la firma prima di elaborare i dati: senza di essa, chiunque può inviare richieste al tuo endpoint webhook impersonando Elido.

La semantica di consegna è at-least-once: una consegna fallita viene riprovata con un backoff di minuti, con tre tentativi totali per impostazione predefinita. Per la struttura dettagliata e il comportamento dei retry, l'articolo webhook contro polling confronta i due pattern di integrazione.

Un esempio pratico: automazione delle campagne

L'integrazione che motiva la maggior parte delle adozioni dell'API è questa. La tua automazione di marketing crea una campagna in Customer.io o HubSpot. Un hook scatta quando la campagna viene pubblicata. Il tuo handler crea il link breve, lo collega al record della campagna e lo rimanda allo strumento di gestione campagne per sostituirlo 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}`,
        `batch:${campaign.batchId}`,
        campaign.channel,
      ],
    },
    {
      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 scatta due volte (succede: le consegne dei webhook sono at-least-once), la seconda chiamata restituisce lo stesso link senza crearne uno duplicato. I tag campaign: e batch: contengono le tue chiavi di join, così puoi correlare gli eventi di click di Elido alla campagna; un campo metadata dedicato è pianificato. I parametri UTM appartengono direttamente a campaign.destinationUrl finché il campo utm non sarà disponibile.

Per l'attribuzione delle campagne end-to-end con template UTM e inoltro delle conversioni, il cornerstone sul tracciamento UTM ripercorre l'intera pipeline.

Cosa manca ancora nell'API

Due cose spesso richieste, attualmente non disponibili:

  • Un singolo GET di analytics per link che restituisca tutte le ripartizioni in una sola chiamata. Il modello attuale richiede chiamate separate per click, paese, referrer, dispositivo e serie temporale. L'aggregazione è nella roadmap; per ora, esegui le richieste in parallelo dal tuo codice.
  • Replay dei webhook dall'API. La dashboard espone la cronologia di consegna dei webhook e supporta il replay; l'API non ancora. Anche questo è nella roadmap.

Se una funzionalità è nella specifica OpenAPI, è supportata. Se è in questo articolo ma non nella specifica, trattala come pianificata piuttosto che garantita.

Letture correlate

Prova Elido

Incolla un URL, ottieni un link breve

Senza registrazione. Il link vive 30 giorni. Iscriviti per conservarlo.

Gratis, nessuna registrazione richiesta · 2 al giorno

Prova Elido

Accorciatore di URL ospitato nell'UE: domini personalizzati, analisi approfondite e API aperta. Piano gratuito - senza carta di credito.

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

Continua a leggere