11 min czytaniaFunkcje

API skracacza URL: 30-minutowy szybki start w pięciu językach

Od zera do działającej automatyzacji krótkich linków w TypeScript, Python, Go, Ruby i PHP - autoryzacja, idempotencja, obsługa błędów i haczyki produkcyjne.

Marius Voß
DevRel · edge infra
Pięciojęzykowy diagram szybkiego startu z panelami kodu dla TypeScript, Python, Go, Ruby i PHP, wskazującymi na centralny endpoint API Elido

API skracacza URL to jedna z mniejszych integracji w backlogu typowego zespołu inżynierskiego. Trzy endpointy, nagłówek autoryzacji, ładunek JSON. Strona dokumentacji obiecuje pierwsze wywołanie w pięć minut. Potem uderza ruch produkcyjny, logika ponowień tworzy zduplikowane linki, dashboard zapełnia się wariantami /foo-1, /foo-2, /foo-3 tego samego miejsca docelowego, i ktoś zgłasza ticket.

Ten wpis przechodzi przez faktyczną integrację. Autoryzacja, pierwsze wywołanie, cztery endpointy pokrywające większość przypadków użycia, idempotencja, obsługa błędów, limity szybkości i haczyki produkcyjne, które pięciominutowy szybki start pomija. Przykłady kodu w TypeScript, Python, Go, Ruby i PHP - pierwsze trzy przez oficjalne SDK (@elido/sdk, elido-python, github.com/elido/elido-go), pozostałe dwa przez zwykłe klienty HTTP.

Wymagania wstępne

Zaloguj się do dashboardu, przejdź do /dashboard/api-keys i utwórz klucz API (zaczyna się od elido_). Tokeny są ograniczone do workspace'u - token wydany w workspace'ie A nie może tworzyć linków w workspace'ie B. Tokeny maszynowe (dla systemów CI, narzędzi wewnętrznych, integracji maszyna-maszyna) są tworzone pod /dashboard/machine-users i rotują niezależnie od kluczy osobistych. Oba rodzaje niosą wstępnie ustawioną rolę workspace'u (viewer, editor albo admin), a nie uprawnienia per endpoint, więc nadaj zadaniu CI rolę editor, jeśli tylko tworzy linki. Przewodnik uprawnienia kluczy API dla narzędzi do linków wyjaśnia, do czego można użyć każdej roli, w tym dlaczego zmiany webhooków wymagają roli admin.

Bazowy URL to https://api.elido.app/v1. Domeny przekierowania (f.elido.me, s.elido.me, b.elido.me) są osobne od powierzchni API. Twoje krótkie linki rozwiązują się na domenie przekierowania; API służy do tworzenia, modyfikowania i odczytywania ich.

Specyfikacja OpenAPI jest opublikowana pod https://elido.app/openapi.json i zgodna z OpenAPI 3.1. Oficjalne SDK są generowane z tej specyfikacji i republikowane przy każdym wydaniu API; możesz też wygenerować własnego klienta w dowolnym języku obsługiwanym przez OpenAPI.

Pierwsze wywołanie

Utwórz krótki link z docelowego URL. Pięć linijek w 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 (bez oficjalnego SDK - używając 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'];

Wszystkie pięć produkuje ten sam wynik. Treść odpowiedzi zawiera krótki URL, kanoniczny identyfikator linku, identyfikator workspace'u i znacznik czasu utworzenia. Slug - abc123 w powyższym przykładzie - jest generowany przez serwer, chyba że przekażesz slug w żądaniu. Alfabet sluga to base62 ([0-9A-Za-z]); domyślna długość to sześć znaków.

Cztery endpointy, których faktycznie będziesz używać

API ma więcej niż cztery endpointy, ale większość integracji zostaje w tym zestawie.

Diagram typu hub-and-spoke czterech głównych endpointów linków wokół zasobu /v1/links: POST create, GET read, PATCH update i DELETE delete, każdy z kluczowym haczykiem.

Tworzenie linku

POST /v1/links przyjmuje docelowy URL plus opcjonalne pola:

  • slug - slug, który wybierasz (musi być unikalny w obrębie domeny).
  • domain_id - dla linków na własnej domenie; w /v1/links używana jest domyślna krótka domena Twojego planu, jeśli pole zostanie pominięte. Ścieżka obejmująca workspace, /v1/workspaces/{workspace_id}/links, wymaga tego pola.
  • title - etykieta wyświetlana w dashboardzie.
  • tags - tablica dowolnych ciągów do organizacji.
  • expires_at - znacznik czasu RFC 3339, po którym link zwraca 410 Gone.
  • redirect_status - 301, 302 (domyślnie) albo 307.
  • password - nie jest jeszcze akceptowane przy tworzeniu; ustaw je od razu potem przez PATCH, a przekierowanie będzie wyświetlać stronę z hasłem przed przekazaniem dalej.
  • utm i metadata - planowane. Dziś umieszczaj parametry UTM bezpośrednio w destination_url, a własne klucze złączeń przechowuj w tags.

Niestandardowy slug to pole, które gryzie zespoły na produkcji. Jeśli przekażesz slug już używany przez inny link w tej samej domenie, API zwraca 409 Conflict. Naiwny handler ponowień, który dopisuje licznik (my-slug-1, my-slug-2), produkuje problem zduplikowanych linków opisany na wstępie. Właściwe zachowanie przy ponowieniu jest opisane w sekcji o idempotencji poniżej.

Odczyt linku

GET /v1/links/{id} zwraca pełny rekord linku, w tym short_url i całą konfigurację. Liczby kliknięć nie znajdują się w rekordzie linku; pochodzą z opisanych niżej endpointów analityki. Identyfikator linku to kanoniczny identyfikator - slugi mogą się zmieniać, identyfikatory nie.

GET /v1/links?host=…&tags=…&limit=… listuje linki w workspace'ie z filtrami. Paginacja jest oparta na kursorze; next_cursor w odpowiedzi jest nieprzezroczysty i wraca jako parametr zapytania cursor w następnym żądaniu.

Aktualizacja linku

PATCH /v1/links/{id} przyjmuje te same pola co tworzenie. Najczęstsze aktualizacje: zmiana docelowego URL (przydatne przy rotacji kampanii bez ponownego drukowania kodów QR), zmiana tagów, przedłużenie expires_at. Slug zmienia się przez to samo PATCH, wysyłając nowy slug. Stary slug natychmiast przestaje działać; osobny endpoint zmiany nazwy, który utrzymuje 301 ze starego sluga przez okres retencji, jest planowany, ale jeszcze nie został zbudowany.

Usuwanie linku

DELETE /v1/links/{id} usuwa miękko i zwraca 204 No Content. Link przestaje przekierowywać i wypada z wywołań listowania oraz odczytu. Widok kosza z endpointem przywracania i 90-dniowym okresem przed trwałym usunięciem jest planowany; dziś nie ma wywołania API, które przywraca usunięty link.

Klucze idempotencji

Każde żądanie mutujące - POST, PATCH, DELETE - przyjmuje nagłówek Idempotency-Key. Wartość nagłówka to nieprzezroczysty ciąg do 255 znaków; serwer przechowuje treść odpowiedzi i kod statusu przez 24 godziny, kluczowane przez (workspace_id, idempotency_key), i zwraca zapisaną odpowiedź, jeśli ten sam klucz zostanie przedstawiony ponownie.

Oficjalne SDK generują klucze idempotencji automatycznie, gdy nie są dostarczone. Możesz to nadpisać:

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

Przypadek użycia to pętla ponowień. Jeśli Twoje zadanie tworzy link jako część przetwarzania zamówienia wyższego w łańcuchu, wygeneruj klucz idempotencji z identyfikatora zamówienia. Ponowienie tego samego zadania widzi ten sam klucz, trafia w cache idempotencji i zwraca oryginalnie utworzony link zamiast wyprodukować drugi.

Pipeline, w którym hook kampanii typu at-least-once wystrzeliwuje dwa wywołania create niosące ten sam klucz idempotencji; 24-godzinny cache deduplikuje drugie, więc powstaje dokładnie jeden link.

Kluczowy haczyk: cache idempotencji żyje 24 godziny, nie wiecznie. Ponowienie trzeciego dnia zablokowanego zadania stworzy nowy link. Jeśli integracja działa na przestrzeni wielodniowych partii, zapisz identyfikator linku zwrócony przez pierwsze udane utworzenie i sprawdź go, zanim wydasz ponownie.

Drugi haczyk: idempotencja jest per workspace. Ten sam klucz w dwóch workspace'ach tworzy dwa linki. To właściwa semantyka dla API wieloworkspace'owego, ale może zaskoczyć zespoły, które zakładają, że klucz jest globalnie unikalny.

Obsługa błędów

API zwraca standardowe kody statusu HTTP plus ustrukturyzowaną treść błędu:

{
  "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
  }
}

Kody, które zobaczysz najczęściej:

  • 400 invalid_request - awaria walidacji ładunku. Pole message wymienia konkretne pola. Nie ponawiaj; napraw ładunek.
  • 401 unauthorized - brak tokena albo nieprawidłowy. Nie ponawiaj bez rotacji tokena.
  • 403 forbidden - rola tokena nie pozwala na akcję (klucz viewer nie może tworzyć linków). Sprawdź rolę klucza pod /dashboard/api-keys.
  • 404 not_found - zasób nie istnieje albo token nie ma do niego dostępu (zwracamy 404 zamiast 403, żeby uniknąć ujawniania istnienia zasobu nieautoryzowanym wywołującym).
  • 409 conflict - slug już używany, albo wykryto jednoczesną edycję (PATCH na nieaktualnej wersji). Pobierz ponownie i spróbuj jeszcze raz.
  • 429 rate_limit_exceeded - wycofaj się zgodnie z wartością retry_after.
  • 500 internal_server_error - awaria po stronie serwera. Bezpiecznie ponowić z tym samym kluczem idempotencji.
  • 502 bad_gateway, 503 service_unavailable, 504 gateway_timeout - przejściowe problemy infrastrukturalne. Wycofaj się i ponów.

Oficjalne SDK implementują wykładnicze wycofanie z jitterem dla 429, 500, 502, 503 i 504. Nie ponawiają 400, 401, 403, 404 ani 409 - to błędy programistyczne albo konflikty logiki biznesowej, nie przejściowe awarie. Niestandardowe klienty HTTP powinny stosować ten sam wzorzec; ponowienie 400 z tym samym ładunkiem nie da innego wyniku.

Podział decyzyjny sortujący kody statusu API na kolumnę ponów-z-wycofaniem (429, 500, 502, 503, 504) i kolumnę nie-ponawiaj (400, 401, 403, 404, 409) dla błędów programistycznych i konfliktów.

request_id w treści błędu to pole do umieszczenia w ticketach wsparcia. Możemy prześledzić dowolne żądanie z tego identyfikatora przez log audytu, log aplikacji i metryki platformy - a nie możemy prześledzić żądania bez niego.

Limity szybkości

Opublikowane limity szybkości to 100 żądań na sekundę per workspace na Pro, 500 na Business i negocjowany limit na Enterprise. Darmowy poziom to 10 req/s.

Stan limitu szybkości jest wystawiony w trzech nagłówkach odpowiedzi przy każdej odpowiedzi API:

  • X-RateLimit-Limit - bieżący limit na sekundę.
  • X-RateLimit-Remaining - żądania pozostałe w bieżącej sekundzie.
  • X-RateLimit-Reset - znacznik czasu Unix, kiedy zasobnik się resetuje.

Limit 100/s to implementacja token-bucket z pojemnością wybuchu 200 - co oznacza, że możesz wydać 200 żądań naraz, jeśli zasobnik jest pełny, a potem ustabilizować się w stałym tempie 100/s. Większość zadań tworzenia krótkich linków mieści się wygodnie w wybuchu; integracje mocno oparte na analityce, które stronicują przez historyczne zdarzenia kliknięć, korzystają z zapasu poziomu Pro.

Dla operacji masowych endpoint POST /v1/links/bulk przyjmuje do 100 linków na żądanie i liczy się jako jedna jednostka limitu szybkości. To właściwy endpoint dla każdego zadania, które tworzy więcej niż sto linków naraz. Dla głębszego omówienia tempa względem token bucket, wyboru, które kody statusu ponawiać, i tego, jak klucze idempotencji chronią przed duplikowaniem linków przy ponowieniach, zobacz limity szybkości, ponowienia i idempotencja na produkcji.

Co robią SDK, czego nie robi zwykłe HTTP

Oficjalne SDK dostarczają cztery rzeczy, które szybko się zwracają:

  • Automatyczne ponowienie z wycofaniem dla kodów statusu podlegających ponowieniu.
  • Generowanie kluczy idempotencji, gdy nie są jawnie dostarczone.
  • Typowane błędy, więc możesz zrobić catch (err) { if (err instanceof ElidoRateLimitError) { … } } zamiast parsować JSON w blokach catch.
  • Iteratory paginacji, więc endpointy listujące wystawiają asynchroniczne iteratory albo generatory zamiast wymagać ręcznej obsługi kursora.

SDK w Go dodatkowo wystawia bazowego klienta HTTP do instrumentacji - przydatne, jeśli chcesz podłączyć go do swojej istniejącej konfiguracji tracingu. Strona funkcji API + SDK w repozytorium omawia pełną powierzchnię; referencja API jest opublikowana pod /docs/api-reference.

Dostęp do analityki

Endpointy analityki są tylko do odczytu i żyją pod /v1/workspaces/{id}/analytics/; przewodnik po API analityki linków zawiera listę wszystkich raportów, ich parametrów i kształtów odpowiedzi. Najczęstsze zapytania:

  • GET .../clicks/recent?from=…&to=… - pojedyncze kliknięcia, od najnowszych, stronicowane przez next_cursor. Przydatne dla pipeline'ów eksportu.
  • GET .../timeseries?from=…&to=…&interval=day - liczby kliknięć w koszykach dla zakresu czasu; interval to hour albo day, a tz ustawia strefę czasową koszyka.
  • GET .../breakdown/country?from=…&to=… - podział geograficzny.
  • GET .../breakdown/referrer?from=…&to=… - podział wg referrera.

Pozostałe raporty to summary, links/top, pozostałe podziały (host, device, browser, destination) oraz listy top (top-countries, top-regions, top-cities, top-referrers, top-destinations). from i to to daty w formacie YYYY-MM-DD, a to jest wyłączne; dodaj link_id, aby zawęzić dowolny raport do jednego linku, oraz limit, aby określić rozmiar podziałów i list top.

Strumień surowych zdarzeń kliknięć jest największy. Workspace z 10 mln kliknięć miesięcznie produkuje około 600 MB danych JSON surowych zdarzeń miesięcznie. Dla eksportów na tę skalę przewodnik eksportu analityki omawia mechanizm eksportu masowego, który omija kopertę JSON i strumieniuje bezpośrednio z hurtowni analityki.

Webhooki dla zdarzeń linków

Webhooki to odwrotność pollingu - zamiast Ciebie pytającego API, co się zmieniło, API dostarcza zdarzenia linków i domen na Twój endpoint. Skonfiguruj pod /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,
});

Zdarzenie click.created per kliknięcie jest na mapie drogowej, ale jeszcze niedostępne, więc dziś dane o kliknięciach pochodzą z endpointów analityki. Każda dostawa zawiera nagłówek X-Elido-Signature (wysyłany też jako X-Webhook-Signature) z wartością v1=<hex>: HMAC-SHA256, kluczowany Twoim sekretem endpointu, nad wartością X-Webhook-Timestamp, kropką i surową treścią żądania. Zweryfikuj sygnaturę przed przetwarzaniem - bez tego każdy wywołujący może wysłać POST na Twój endpoint webhooka i podszyć się pod Elido.

Semantyka dostawy to at-least-once: nieudana dostawa jest ponawiana z wycofaniem liczonym w minutach, domyślnie w sumie trzy próby. Po szczegółowy kształt i zachowanie ponowień, wpis webhooki kontra polling porównuje oba wzorce integracji.

Przerobiony przykład: automatyzacja kampanii

Integracja, która motywuje większość adopcji API, wygląda tak. Twoja automatyzacja marketingowa tworzy kampanię w Customer.io albo HubSpot. Hook wystrzeliwuje, gdy kampania jest publikowana. Twój handler tworzy krótki link, dołącza go do rekordu kampanii i wysyła go z powrotem do narzędzia zarządzania kampanią, żeby podstawić do szablonu e-maila.

W 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;
}

Klucz idempotencji jest wyprowadzony z identyfikatora kampanii. Jeśli hook publikacji kampanii wystrzeli dwa razy (a wystrzeli - dostawy webhooków są at-least-once), drugie wywołanie zwraca ten sam link bez tworzenia duplikatu. Tagi campaign: i batch: przenoszą Twoje własne klucze złączeń, żebyś mogła skorelować zdarzenia kliknięć Elido z powrotem do kampanii; osobne pole metadata do tego celu jest planowane. Parametry UTM umieszczaj w samym campaign.destinationUrl, dopóki pole utm nie zostanie wdrożone.

Dla atrybucji kampanii od początku do końca z szablonami UTM i przekazywaniem konwersji, artykuł filarowy o śledzeniu UTM przechodzi przez cały pipeline.

Czego jeszcze nie ma w API

Dwie rzeczy, o które często się pyta, obecnie niedostępne:

  • Pojedynczy GET analityki linku, który zwraca wszystkie podziały w jednym wywołaniu. Obecny model wymaga osobnych wywołań dla kliknięć, kraju, referrera, urządzenia i szeregu czasowego. Agregacja jest na mapie drogowej; na razie uruchamiaj żądania równolegle we własnym kodzie.
  • Odtwarzanie webhooków z API. Dashboard wystawia historię dostaw webhooków i obsługuje odtwarzanie; API jeszcze nie. To też jest na mapie drogowej.

Jeśli funkcja jest w specyfikacji OpenAPI, jest obsługiwana. Jeśli jest w tym wpisie, ale nie w specyfikacji, traktuj ją jako planowaną, a nie gwarantowaną.

Powiązana lektura

Wypróbuj Elido

Wklej URL, otrzymaj krótki link

Bez rejestracji. Link działa 30 dni. Zarejestruj się, aby zachować go na zawsze.

Za darmo, bez rejestracji · 2 dziennie

Wypróbuj Elido

Skracarka URL hostowana w UE: własne domeny, głęboka analityka i otwarte API. Darmowy plan - bez karty kredytowej.

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

Czytaj dalej