Żeby skrócić URL za pomocą API, wyślij jedno uwierzytelnione żądanie POST: umieść długi adres w treści JSON jako destination_url, wyślij go na https://api.elido.app/v1/links z nagłówkiem Authorization: Bearer <your key> i odczytaj short_url z odpowiedzi 201 Created. To cała integracja w każdym języku. Osiem poniższych języków różni się tylko tym, jak zapisują żądanie. Oraz tym, który szczegół produkcyjny ugryzie jako pierwszy.
Pierwsze wywołanie zajmuje pięć minut. Kłopoty zaczynają się później, gdy uderza ruch produkcyjny, a obsługa ponowień tworzy duplikaty, więc panel zapełnia się wariantami /foo-1, /foo-2 i /foo-3 tego samego adresu docelowego i ktoś zakłada zgłoszenie. Ten przewodnik po API skracacza URL to ten, który chciałbym, żeby ten zespół przeczytał wcześniej: uwierzytelnianie, pierwsze wywołanie, cztery endpointy linków, klucze idempotentności, kody błędów, limity zapytań, a potem to samo żądanie krótkiego linku w ośmiu językach wraz z nawykiem, którego wymaga każde środowisko.
Wciąż wybierasz usługę? Porównanie darmowych API skracaczy URL zestawia kształty żądań i limity planów darmowych u różnych dostawców. Wszystko poniżej dotyczy API REST Elido.
Przejdź od razu do swojego języka:
- Python:
requestsdla jednego linku,httpxz semaforem dla tysięcy. - JavaScript i Node.js:
fetch, dlaczego klucz zostaje poza przeglądarką, pula workerów zamiastPromise.all. - Go: jeden współdzielony
http.Clientz limitem czasu,errgroup.SetLimitdla partii. - Java:
HttpClientz JDK, wątki wirtualne zaSemaphore. - C# i .NET:
PostAsJsonAsync, a potem typowany klient zIHttpClientFactory. - PHP, Laravel i WordPress: curl,
Http::z ponowieniami,wp_remote_postprzy publikacji. - Ruby:
Net::HTTP.post, a potem Faraday z middleware ponowień, które naprawdę ponawia POST. - PowerShell:
Invoke-RestMethod, klucz w sejfie i cała kolumna CSV.
Wymagania wstępne: klucz API i adres bazowy
Zaloguj się do panelu, otwórz /dashboard/api-keys i utwórz klucz API. Klucze zaczynają się od elido_ i są pokazywane tylko raz. Każdy klucz jest powiązany z jednym workspace, więc klucz wystawiony w workspace A nie utworzy linków w workspace B, a API ustala, o który workspace chodzi, wyłącznie na podstawie klucza. Tokeny użytkowników maszynowych (dla systemów CI, narzędzi wewnętrznych i integracji maszyna-maszyna) znajdziesz w /dashboard/machine-users, a ich rotacja jest niezależna od kluczy osobistych.
Oba rodzaje niosą z góry ustawioną rolę w workspace (viewer, editor lub admin) zamiast zakresów dla poszczególnych endpointów, więc zadanie CI, które tylko tworzy linki, dostaje editor. Przewodnik po uprawnieniach kluczy API wymienia, do czego sięga każda rola. Zmiany webhooków wymagają admin i wyjaśnia, dlaczego.
Adres bazowy to https://api.elido.app, a trasy linków leżą pod /v1. Domeny przekierowań (f.elido.me w planie Free, s.elido.me w planach płatnych, b.elido.me w Business) to osobna powierzchnia: tam rozwiązują się Twoje krótkie linki, a API je tworzy i edytuje. Specyfikacja OpenAPI 3.1 stojąca za dokumentacją API jest kontraktem. Jeśli pole jest w specyfikacji, jest wspierane.
Jedna pułapka łapie nowe konta. Zapisy wykonane kluczem, którego właściciel nie zweryfikował adresu e-mail, wracają jako 403 (email_not_verified). Odczyty nadal działają. Tokeny użytkowników maszynowych są wyłączone z tej reguły. Zweryfikuj adres raz, a błąd zniknie.
Pierwsze wywołanie: utworzenie krótkiego linku
Oto żądanie, które odtwarza każda sekcja językowa poniżej, w 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"}'
Odpowiedź to 201 Created z rekordem linku. Przycięta do pól, których użyjesz:
{
"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"
}
Zapisz id, jeśli kiedykolwiek będziesz edytować, wygaszać lub usuwać link: to kanoniczny identyfikator, a slugi mogą się zmieniać, podczas gdy ID nie. Nie przekazałeś domain_id, więc API użyło domyślnej krótkiej domeny Twojego planu. Slug generuje serwer, chyba że wyślesz slug; wygenerowane slugi są w base62 ([0-9A-Za-z]) i mają najwyżej sześć znaków.
Trzymaj klucz w zmiennej środowiskowej (ELIDO_API_KEY we wszystkich przykładach), nigdy w kodzie źródłowym, bo klucz skracacza to poświadczenie wydatków i każdy, kto go ma, może tworzyć linki na Twój limit, co najbardziej liczy się w JavaScripcie, jak wyjaśnia sekcja o Node.
Cztery endpointy, których naprawdę użyjesz
API ma więcej niż cztery endpointy, ale większość integracji mieści się w tym zestawie. Płaskie trasy /v1/links wnioskują workspace z Twojego klucza; kanoniczne ścieżki pod /v1/workspaces/{workspace_id}/links zachowują się tak samo i wymagają jawnego ID.
Tworzenie linku
POST /v1/links przyjmuje destination_url oraz opcjonalne pola:
slug- wybrany przez Ciebie back-half; musi być unikalny w obrębie domeny.domain_id- dla własnej domeny; pomiń, żeby dostać domyślną krótką domenę planu.titleitags- etykieta w panelu oraz dowolne ciągi znaków, po których później filtrujesz.expires_at- znacznik czasu RFC 3339, po którym link przestaje przekierowywać i pokazuje stronę wygaśnięcia.redirect_status-301,302(domyślnie) lub307.
Haseł nie ustawia się przy tworzeniu: wyślij PATCH zaraz potem, a przekierowanie pokaże stronę z hasłem przed przekazaniem dalej. Parametry UTM trafiają prosto do destination_url, a Twoje własne klucze łączące do tags.
Własny slug to pole, które gryzie zespoły w produkcji. Użyj tego samego sluga w tej samej domenie, a dostaniesz 409. Naiwna obsługa, która dopisuje licznik (my-slug-1, my-slug-2), to dokładnie sposób, w jaki zaczyna się bałagan z duplikatami ze wstępu. Rozwiązaniem jest klucz idempotentności, omówiony dwie sekcje dalej.
Odczyt i lista linków
GET /v1/links/{id} zwraca pełny rekord, w tym short_url. Liczników kliknięć nie ma w rekordzie linku. Pochodzą z endpointów analityki opisanych w przewodniku po API analityki kliknięć.
GET /v1/links?limit=…&tags=…&host=… wypisuje linki z filtrami (q wyszukuje, status i folder zawężają dalej). Paginacja opiera się na kursorze: odeślij next_cursor z odpowiedzi jako cursor i zatrzymaj się, gdy wynosi null.
Aktualizacja linku
PATCH /v1/links/{id} przyjmuje pola z tworzenia. Typowe aktualizacje to nowy destination_url (rotacja kampanii bez ponownego drukowania kodu QR), nowe tagi i późniejszy expires_at. Wysłanie nowego slug zmienia nazwę linku. Stary slug przestaje działać od razu. Nie zmieniaj nazw niczego, co już zostało wydrukowane.
Usuwanie i przywracanie linku
DELETE /v1/links/{id} zwraca 204 No Content i przenosi link do kosza: przestaje przekierowywać i znika z wywołań listy i odczytu. Jeszcze go nie ma na dobre. POST /v1/links/{id}/restore go przywraca, a GET /v1/links/trash pokazuje, co tam jest. Po 30 dniach kosz jest opróżniany na stałe.
Klucze idempotentności: ponowienia bez zduplikowanych linków
Każde POST, PUT i PATCH przyjmuje nagłówek Idempotency-Key, nieprzezroczysty ciąg wybierany przez Ciebie. Gdy klucz po raz pierwszy przychodzi z udaną odpowiedzią, serwer zapisuje kod statusu i treść na 24 godziny, w zakresie (workspace, key). Powtórzenie tego samego klucza z tym samym żądaniem zwraca zapisaną odpowiedź, więc ponowienie oddaje link, który już istnieje, zamiast tworzyć drugi.
O tym, czy to u Ciebie zadziała, decydują trzy szczegóły:
- Zapisywane są tylko udane odpowiedzi.
429lub500nie trafiają do pamięci podręcznej, więc następne ponowienie z tym samym kluczem dostaje prawdziwą drugą próbę. - Serwer tworzy odcisk z metody, ścieżki i treści. Użyj klucza z inną treścią, a dostaniesz
422zidempotency_key_mismatch, czyli błąd po stronie wywołującego, którego nie należy ponawiać. DELETEnie przyjmuje klucza; dwukrotne usunięcie jest z natury idempotentne.
Sam klucz powinien pochodzić z czegoś stabilnego. Wyprowadź go ze zdarzenia biznesowego (order-12345-link), jeśli jest, albo z hasha SHA-256 adresu docelowego w zadaniu wsadowym. Losowy UUID na każdą próbę niweczy cel. Jeden na adres działa, dopóki partia nie zostanie uruchomiona ponownie.
Pułapką jest okno 24 godzin. Ponowienie w trzecim dniu zablokowanego zadania tworzy nowy link, więc przy wielodniowych partiach zapisz id z pierwszego udanego utworzenia i sprawdź je, zanim wyślesz żądanie ponownie. Klucze są też per workspace: ten sam klucz w dwóch workspace tworzy dwa linki, co jest poprawne dla API wielu workspace i wciąż zaskakuje zespoły, które zakładały, że klucze są globalne.
Klucz oparty na hashu adresu docelowego ma jeden skutek uboczny, który warto znać. Dwie kampanie wskazujące ten sam URL w ciągu 24 godzin dzielą jeden link. Jeśli chcesz dwóch linków, dodaj do klucza ID kampanii.
Obsługa błędów i kody statusu
Błędy wracają jako mały obiekt JSON z czytelnym dla człowieka error i, w niektórych przypadkach, czytelnym dla maszyn code:
{ "error": "destination_url is required" }
Kody statusu, które spotkasz:
400- błąd walidacji: brakującydestination_url, źle sformatowanyexpires_at, nieprawidłowyredirect_status. Popraw dane; nie ponawiaj.401- klucza brakuje, został unieważniony lub jest źle wpisany. Sprawdź, czy nagłówek brzmiBearerze spacją przed kluczem.402- limit linków Twojego planu został wyczerpany. Ponawianie nie pomoże; pomoże zmiana planu lub czekanie na następny okres.403- rola klucza nie może tego zrobić (kluczviewernie tworzy linków), e-mail konta nie jest zweryfikowany albo adres docelowy zablokowało skanowanie bezpieczeństwa ("code": "destination_blocked").404- link nie istnieje lub ten klucz go nie widzi. Zwracamy 404 zamiast 403, żeby wywołujący nie mógł sondować zasobów, które nie należą do niego.409- slug jest zajęty w tej domenie.422- klucz idempotentności użyty z inną treścią albo niedozwolona kombinacja opcji.429- przekroczyłeś limit zapytań. Poczekaj naRetry-Afteri spróbuj ponownie z tym samym kluczem idempotentności.500,502,503,504- błąd po stronie serwera lub przejściowy. Zastosuj backoff i ponów z tym samym kluczem.
Ten podział to cała polityka ponowień, a każda sekcja językowa poniżej realizuje ją tak samo: ponawiaj 429 i 5xx, rezygnuj przy całej reszcie. Ponowienie 400 z tymi samymi danymi nie da innej odpowiedzi, a ponawianie 401 trzy razy to tylko wolniejsza porażka. Przewodnik po limitach i ponowieniach wchodzi głębiej w jitter i w to, dlaczego ślepe pętle ponowień zamieniają jedną awarię w dwie.
Limity zapytań i endpoint masowy
Ruch API jest mierzony na workspace za pomocą token bucket:
| Plan | Tempo stałe | Seria |
|---|---|---|
| Free | 10 req/s | 20 |
| Pro | 30 req/s | 60 |
| Business | 100 req/s | 200 |
Pełny bucket pozwala wysłać serię naraz. Potem ustalasz się na tempie stałym. Każdy klucz API ma też własny bucket ponad tym dla workspace, więc pojedynczy rozbiegany skrypt nie wyczerpie sam całego limitu workspace.
Odrzucone żądanie dostaje 429, nagłówek Retry-After: 1 i nagłówek X-RateLimit-Scope mówiący, który bucket się opróżnił (workspace lub api_key). Nie ma nagłówków z pozostałym limitem, według których można by dostosować tempo. Praktyczne podejście to więc stały limit współbieżności plus ponowienie przy 429: osiem żądań w locie to wygodny start w Pro, a cztery pasują do Free. Jedno dziwactwo mnie zaskoczyło. Treść 429 to tekst w kształcie JSON wysyłany jako text/plain, więc klient parsujący tylko typy zawartości JSON zobaczy zwykły ciąg tam, gdzie oczekiwał obiektu.
Duże zadanie? POST /v1/links/bulk przyjmuje {"links": [...]} z maksymalnie 100 pozycjami i liczy się jako jedno żądanie w limicie. Odpowiada 200 nawet wtedy, gdy część pozycji zawodzi, z licznikami ok i failed oraz status każdej pozycji równym ok lub error, więc czytaj każdą pozycję. Nie pomijaj tego. Zwrócone linki mają slug, ale nie mają short_url, co oznacza, że URL budujesz sam z własnej domeny i sluga. Poradnik masowego importu z Arkuszy Google przenosi w ten sposób cały arkusz.
Skróć URL w swoim języku: wspólny kształt
Każda sekcja od tego miejsca wysyła żądanie z pierwszego wywołania i stosuje te same cztery nawyki: klucz ze środowiska, limit czasu przy każdym żądaniu, sprawdzenie kodu statusu przed parsowaniem treści oraz stabilny Idempotency-Key przy wszystkim, co może się wykonać dwa razy. Nie będę ich wyjaśniać osobno dla każdego języka. Zmienia się idiom. Każde środowisko ma też jeden szczegół, który ludzie robią źle.
Każda sekcja zaczyna się od minimalnego działającego wywołania. To wystarczy do skryptu uruchamianego ręcznie. Potem przychodzi element produkcyjny, o który użytkownicy danego środowiska pytają najczęściej: współbieżność w Pythonie, Go lub Javie; czas życia klienta w C#; hosting w PHP; middleware w Ruby; sekrety i CSV w PowerShell. Chcesz uruchomić je tak, jak są napisane? Utwórz klucz w planie darmowym, wyeksportuj go jako ELIDO_API_KEY, a każdy fragment zadziała bez zmian.
Skracanie URL w Pythonie
W Pythonie skrócenie URL to jedno requests.post z kluczem w nagłówku Bearer, adresem docelowym w json= i limitem czasu:
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
Biblioteka requests nie ma domyślnego limitu czasu, więc bez timeout=10 zawieszone połączenie blokuje skrypt na zawsze. raise_for_status() zamienia 401 lub 500 w wyjątek, zamiast pozwolić, żeby nieudane wywołanie wyglądało na udane. Jeśli nie możesz użyć pip install, urllib.request z biblioteki standardowej wyśle to samo POST, z większą ceremonią.
Masowo z httpx i semaforem
Pętla requests.post wystarczy dla kilkuset linków. Przy dziesiątkach tysięcy czas idzie na czekanie na sieć, a httpx z asyncio nakłada te oczekiwania na siebie. Najważniejszy jest semafor. Ogranicza liczbę żądań w locie, a to różnica między szybką partią a ścianą 429, z której pętla ponowień musi się potem wygrzebywać.
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))
Wyniki są indeksowane oryginalnym URL. Częściowa porażka pozostaje widoczna. Ponieważ klucz jest hashem adresu docelowego, ponowne uruchomienie całej listy po poprawieniu trzech błędnych wierszy niczego nie tworzy dwa razy. Spanie wewnątrz semafora jest celowe: worker, któremu kazano się wycofać, nie powinien oddawać swojego miejsca świeżemu żądaniu.
Skracanie URL w JavaScripcie i Node.js
W JavaScripcie skrócenie URL to jedno POST przez fetch. Node ma fetch jako globalny od wersji 18, więc nie trzeba niczego instalować:
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);
Uruchom to przez node --env-file=.env shorten.mjs. Ta linia if (!res.ok) to nie ozdoba: fetch odrzuca obietnicę tylko przy awarii sieci lub przerwaniu, więc 401 przychodzi jako rozwiązana obietnica z ok: false. Użytkownicy Axios łapią się na to raz. Zwykle objawia się to jako undefined tam, gdzie powinien być link.
Trzymaj klucz na serwerze
Wklej ten fragment do komponentu React, a zepsują się dwie rzeczy. CORS zablokuje odpowiedź, bo API uwierzytelniane kluczem nie wysyła Access-Control-Allow-Origin dla dowolnych witryn. Większy problem kryje się za błędem: klucz w kodzie front-endu może przeczytać każdy, kto otworzy DevTools, a prefiks NEXT_PUBLIC_ go reklamuje. Dlatego przeglądarka wysyła na Twoją trasę zwykły URL. Trasa dodaje klucz.
Waliduj URL, który przyjmuje Twoja trasa, bo inaczej zbudowałeś otwarte przekierowanie z dodatkowymi krokami; tekst o podatnościach otwartego przekierowania pokazuje, co z nim robią atakujący.
Przy partii oprzyj się Promise.all(urls.map(shorten)). Odpala wszystkie żądania w tym samym ticku, więc 500 adresów to 500 równoczesnych połączeń, a większość wraca jako 429. Mała pula workerów pobierających ze wspólnej kolejki utrzymuje stałą współbieżność bez względu na długość listy:
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;
}
W środowiskach edge (Vercel Edge, Cloudflare Workers, Deno) node:crypto może nie istnieć; zahaszuj przez crypto.subtle.digest("SHA-256", …), a reszta pozostaje bez zmian.
Skracanie URL w Go
W Go skrócenie URL wymaga net/http i encoding/json z biblioteki standardowej: zserializuj treść, ustaw dwa nagłówki, zdekoduj short_url. Napisz to od początku jako funkcję na współdzielonym kliencie, bo to wersja, którą zachowasz:
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 i http.Post nie mają żadnego limitu czasu. Żadnego. Nieodpowiadający host może trzymać goroutine przez minuty. Jeden klient na poziomie pakietu utrzymuje pulę połączeń przy życiu między wywołaniami, a NewRequestWithContext sprawia, że anulowane CLI lub rozłączony wywołujący zatrzymują żądanie, zamiast zostawiać je uruchomione. Jeśli partia otwiera znacznie więcej gniazd, niż się spodziewałeś, podnieś MaxIdleConnsPerHost w transporcie: domyślne 2 jest niewidoczne w skrypcie i kosztowne, gdy osiem goroutine dzieli jednego hosta.
Ograniczony fan-out z errgroup.SetLimit
Jedna goroutine na URL jest tania dla Go i zgubna dla limitu zapytań: 5 000 adresów to 5 000 żądań naraz. errgroup z SetLimit daje fan-out z sufitem.
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
}
Używa to zwykłego errgroup.Group celowo. errgroup.WithContext anuluje wszystko przy pierwszym błędzie, co pasuje do kroku budowania typu wszystko albo nic, a jest dokładnie błędne dla importera, który ma skrócić, co się da, iść dalej mimo złych wierszy i zgłosić porażki na końcu. Od Go 1.22 zmienna pętli jest per iteracja, więc stara linia u := u zniknęła, ale mutex nie jest opcjonalny: go test -race oznaczy niezabezpieczoną mapę. Dodaj ponowienie przy 429 i 5xx wewnątrz shorten, używając select na ctx.Done() i time.After, żeby backoff dało się przerwać.
Skracanie URL w Javie
W Javie skrócenie URL to jedno POST przez java.net.http.HttpClient, który jest w JDK od Javy 11, więc pierwsza wersja nie potrzebuje zależności 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"
}
Dwa limity czasu robią dwie rzeczy. connectTimeout ogranicza handshake TCP i TLS; timeout żądania ogranicza całą wymianę, a bez niego serwer, który przyjmie połączenie i zamilknie, trzyma wątek. send rzuca wyjątek tylko przy awariach transportu i przekroczeniach czasu, nigdy przy błędzie HTTP, dlatego sprawdzenie statusu jest kontraktem, a nie programowaniem defensywnym. Zbuduj jeden HttpClient na aplikację: jest bezpieczny wątkowo i ma własną pulę połączeń, a tworzenie go przy każdym wywołaniu powtarza za każdym razem handshake TLS.
Wątki wirtualne za semaforem
Wątki wirtualne stały się finalne w Javie 21. Sprawiają, że jeden wątek na URL jest wystarczająco tani, by miał sens. Limit zapytań jednak się nie ruszył, więc prawdziwą pracę robi Semaphore. Tu parsuj Jacksonem. Ręcznie zbudowany ciąg JSON psuje się przy pierwszym adresie docelowym zawierającym cudzysłów.
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;
}
Blok try-with-resources robi prawdziwą robotę: zamknięcie executora wątków wirtualnych czeka na każde przesłane zadanie, więc nie ma tańca z awaitTermination. W Javie od 11 do 17 ten sam kształt działa z sendAsync i stałą pulą. Nie działa w żadnej wersji CompletableFuture.allOf na nieograniczonej liście, który startuje wszystko naraz i zbiera stos 429.
Skracanie URL w C# i .NET
W C# skrócenie URL to jedno wywołanie PostAsJsonAsync na HttpClient, który już niesie nagłówek 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 to linia, która się liczy. Bez niej 401 kończy się bez zająknięcia, ReadFromJsonAsync oddaje rekord pełen nulli, a porażka wypływa trzy warstwy dalej jako NullReferenceException. Obiekt anonimowy zachowuje nazwę pola w snake_case bez polityki nazewnictwa; w prawdziwym projekcie ustaw raz JsonNamingPolicy.SnakeCaseLower w opcjach serializatora.
Typowany klient z IHttpClientFactory
Ten fragment jest w porządku w konsolowym spike'u, a w usłudze błędny. HttpClient tworzony przy każdym wywołaniu za każdym razem otwiera nową pulę połączeń, a jego zwolnienie zostawia gniazda w TIME_WAIT; rób tak w pętli, a skończą ci się porty. IHttpClientFactory łączy handlery w pulę i rotuje je, żeby zmiany DNS były wychwytywane.
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 pochodzi z Microsoft.Extensions.Http.Resilience i przynosi ponowienia z jitterem przy 429 i 5xx, limity czasu oraz wyłącznik awaryjny. Ponowi POST, chyba że każesz mu tego nie robić, a nagłówek Idempotency-Key sprawia, że jest to bezpieczne. Odczyt treści błędu przed rzuceniem wyjątku to druga połowa: EnsureSuccessStatusCode zachowuje tylko kod statusu, więc o trzeciej w nocy zostajesz z zgadywaniem, do którego pola miało zastrzeżenia 400. Przy masowych operacjach Parallel.ForEachAsync z MaxDegreeOfParallelism = 8 ogranicza żądania w locie i czyta się lepiej niż ręcznie zrobiony semafor. Klucz trzymaj lokalnie w user secrets, a w produkcji w zmiennych środowiskowych lub sejfie, nigdy w zacommitowanym appsettings.json.
Skracanie URL w PHP, Laravel i WordPress
W PHP skrócenie URL działa z rozszerzeniem curl dołączonym do PHP, więc działa nawet na hostingu współdzielonym, gdzie Composer nie wchodzi w grę:
<?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'];
Dwie linie istnieją z powodu błędów, w które każdy wpada raz. Bez CURLOPT_RETURNTRANSFER curl wypisuje JSON na wyjście i zwraca true, co jest prawdziwą historią stojącą za większością pytań o pustą odpowiedź. A CURLOPT_POSTFIELDS przełącza się na kodowanie formularza multipart w chwili, gdy przekażesz mu tablicę, więc to json_encode czyni z tego żądanie JSON. Wiele samouczków PHP wciąż wywołuje urlshortener/v1 Google lub endpoint v3 bit.ly; oba dawno nie istnieją, a wpis o alternatywie dla skracacza linków Google opisuje, gdzie trafiły linki goo.gl.
Klient Http w Laravel z ponowieniami
W aplikacji Laravel lub Symfony klient HTTP Laravel (opakowanie wokół Guzzle) deklaruje politykę ponowień raz:
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;
Callback when sprawia, że 401 nie jest ponawiane trzy razy. Czysty Guzzle zachowuje się inaczej niż curl: domyślnie rzuca ClientException przy 4xx i ServerException przy 5xx, więc je przechwyć albo ustaw http_errors na false. Przy nocnym imporcie Pool Guzzle z 'concurrency' => 8 to odpowiedź PHP na ograniczony paralelizm. Jedna pułapka specyficzna dla PHP: sleep() wewnątrz żądania webowego trzyma przez cały czas workera php-fpm, więc ponowienia należą do skryptów CLI, zadań kolejki i WP-Cron.
WordPress: wp_remote_post przy publikacji
WordPress ma własną warstwę HTTP, wp_remote_post, która wybiera transport i działa na hostach z wyłączonym curl. Podepnij ją pod publikację i zapisz wynik jako meta wpisu:
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']);
});
Zdefiniuj ELIDO_API_KEY w wp-config.php, nie w pliku wtyczki, który trafia do publicznego repozytorium, i nie w wierszu opcji czytelnym dla każdego admina. Klucz post-123 jest stabilny. Odpal hook dwa razy, a drugie wywołanie dostanie istniejący link. Jeśli wolisz w ogóle nie utrzymywać hooka, poradnik o skracaczu URL dla WordPressa porównuje to z wtyczką i ścieżkami bez kodu.
Skracanie URL w Ruby
W Ruby skrócenie URL to jedno Net::HTTP.post z biblioteki standardowej, więc skrypt nie potrzebuje żadnego gema:
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 zamiast ENV[] zawodzi od razu, gdy brakuje klucza, zamiast wysyłać Bearer i wracać z mylącym 401. Sprawdzenie is_a?(Net::HTTPSuccess) to linia, którą ludzie pomijają: Net::HTTP rzuca wyjątek przy martwych gniazdach i przekroczeniach czasu, i na niczym więcej. Net::HTTP.post nie przyjmuje też argumentów limitu czasu, więc przy czymkolwiek bez nadzoru przejdź na Net::HTTP.start z open_timeout i read_timeout.
Faraday z middleware ponowień
W aplikacji Rails Faraday daje jeden obiekt połączenia z dołączonym nagłówkiem uwierzytelniania, JSON w obie strony i zadeklarowaną politykę ponowień. Od Faraday 2 middleware ponowień mieszka w osobnym gemie 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
Linia methods: %i[post] to ta, którą pomija większość fragmentów Faraday w sieci. Domyślnie faraday-retry ponawia tylko idempotentne czasowniki (GET, PUT, DELETE, HEAD, OPTIONS), więc konfiguracja POST bez niej nigdy niczego nie ponowi i nikt tego nie zauważy, dopóki 503 nie zgubi linku. Dopuszczenie POST jest tu bezpieczne właśnie dzięki Idempotency-Key. Middleware respektuje też Retry-After przy 429. Dla listy Queue i osiem wątków działają dobrze nawet w CRuby, bo globalna blokada jest zwalniana podczas I/O; w Rails opakuj shorten w Active Job, żeby śpiące ponowienie nie trzymało wątku Pumy.
Skracanie URL w PowerShell
W PowerShell skrócenie URL to jedno wywołanie Invoke-RestMethod, wbudowane w Windows PowerShell 5.1 i PowerShell 7, które zwraca sparsowany obiekt zamiast ciągu 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 nie jest opcjonalne. Przekaż tablicę asocjacyjną prosto do -Body, a PowerShell zakoduje ją jako formularz i dostaniesz 400, który wygląda jak problem serwera. Zaskoczenie dla osób z innych języków działa w przeciwną stronę: 401 lub 404 jest tu błędem kończącym, a nie wartością zwracaną, więc przechwytujesz wyjątek zamiast sprawdzać kod statusu. PowerShell 7 dodaje -SkipHttpErrorCheck, który zamiast tego zwraca odpowiedź.
Klucz w sejfie i cała kolumna CSV
Klucz wpisany w pliku .ps1 ląduje w kontroli wersji, w Get-History i w każdym zapisie sesji, który włączono w środowisku. Moduł SecretManagement trzyma go w sejfie, więc rotacja klucza oznacza aktualizację jednego wpisu zamiast szukania po skryptach na czterech serwerach. Najczęstsze prawdziwe zadanie to kolumna arkusza, a nie pojedynczy 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 utrzymuje osiem żądań w locie bez względu na to, czy plik ma pięćdziesiąt, czy pięćdziesiąt tysięcy wierszy. Każda iteracja -Parallel działa we własnej przestrzeni uruchomieniowej, dlatego funkcja wjeżdża przez $using:, a cała konstrukcja wymaga PowerShell 7; w 5.1 zwykły foreach to uczciwa odpowiedź dla kilkuset wierszy. Błędy trafiają do kolumny wyjściowej, a ponieważ klucz idempotentności wynika z adresu docelowego, ponowne uruchomienie pliku po poprawieniu trzech wierszy nie tworzy duplikatów. Sprawdź wyrywkowo kilka wierszy przez curl.exe -sI, zanim cokolwiek zostanie wydrukowane. CSV masz już w Arkuszu Google? Import masowy bez kodu robi tę samą robotę.
Co dziś robią SDK
Oficjalne biblioteki klienckie istnieją dla czterech języków (TypeScript jako @elido/sdk, Python, Go, Ruby) i podążają za specyfikacją OpenAPI. Na razie opublikowane jest tylko SDK dla Go, jako moduł github.com/elidoapp/elido-go (v0.1.0). SDK dla TypeScript, Python i Ruby nie zostały jeszcze opublikowane w npm, PyPI ani RubyGems. Dlatego każdy przykład powyżej używa własnego klienta HTTP danego języka, a to jest też to, co dziś wspieramy w integracjach produkcyjnych. Gdy pozostałe biblioteki się ukażą, strona API i SDK będzie zawierać instrukcje instalacji.
Gdy już wyjdą, spodziewaj się, że opakują trasy z zakresem workspace, więc przekażesz jawnie ID workspace i domain_id, oraz że będą się różnić zachowaniem ponowień. Klient TypeScript dodaje Idempotency-Key do każdego POST, PUT lub PATCH i ponawia 502, 503 i 504 z backoffem, zostawiając 429 Tobie. Klient Ruby też generuje klucze. Zgłasza typowany błąd limitu z retry_after, ale nigdy sam nie ponawia. Klienci Python i Go w ogóle nie ponawiają. Którego byś nie użył, polityka ponowień z sekcji o błędach nadal obowiązuje.
Dla terminala zamiast bazy kodu przewodnik po CLI skracacza URL opakowuje to samo wywołanie tworzenia w curl i funkcję powłoki, a przewodnik po serwerze MCP łączy API z Claude, Cursorem lub dowolnym innym klientem obsługującym MCP.
Dostęp do analityki
Endpointy analityki są tylko do odczytu i leżą pod /v1/workspaces/{id}/analytics/; przewodnik po API analityki linków wymienia każdy raport, jego parametry i kształt odpowiedzi. Najczęstsze zapytania:
GET .../clicks/recent?from=…&to=…- pojedyncze kliknięcia, od najnowszych, stronicowane przeznext_cursor. Przydatne w potokach eksportu.GET .../timeseries?from=…&to=…&interval=day- liczby kliknięć w przedziałach;intervaltohourlubday, atzustawia strefę czasową przedziałów.GET .../breakdown/country?from=…&to=…- podział geograficzny.GET .../breakdown/referrer?from=…&to=…- podział według źródła odsyłającego.
Pozostałe raporty to summary, links/top, pozostałe podziały (host, device, browser, destination) oraz listy czołówek (top-countries, top-regions, top-cities, top-referrers, top-destinations). from i to to daty YYYY-MM-DD, a to jest wyłączne; dodaj link_id, żeby zawęzić dowolny raport do jednego linku, i limit, żeby dopasować rozmiar podziałów i list czołówek.
Surowy strumień kliknięć jest największy. Workspace z 10M kliknięć miesięcznie produkuje około 600MB JSON surowych zdarzeń miesięcznie, więc przechodź go według harmonogramu, a nie za jednym zamachem.
Webhooki zdarzeń linków
Webhooki to odwrotność odpytywania: zamiast pytać API, co się zmieniło, dostajesz zdarzenia linków i domen dostarczane na swój endpoint. Skonfiguruj je w /dashboard/webhooks, podając adres endpointu, interesujące Cię zdarzenia (link.created, link.updated, link.expired i inne) oraz sekret podpisu.
Zdarzenie click.created dla pojedynczego kliknięcia jest w planach i jeszcze niedostępne, więc dane o kliknięciach na razie pochodzą z endpointów analityki. Każda dostawa niesie nagłówek X-Elido-Signature (wysyłany także jako X-Webhook-Signature) o wartości v1=<hex>: HMAC-SHA256, z kluczem w postaci sekretu Twojego endpointu, liczony z wartości X-Webhook-Timestamp, kropki i surowej treści żądania. Zweryfikuj go przed przetwarzaniem, bo inaczej każdy może wysłać żądanie na Twój endpoint i podszyć się pod Elido; przewodnik po weryfikacji podpisów webhooków pokazuje to sprawdzenie w kilku językach.
Dostarczanie jest at-least-once. Nieudane dostawy dostają łącznie trzy próby, w odstępach kilku minut. Porównanie webhooków i odpytywania waży oba wzorce integracji.
Przykład z życia: automatyzacja kampanii
Integracja, która napędza większość wdrożeń API, wygląda tak. Twoja automatyzacja marketingu tworzy kampanię w Customer.io lub HubSpot, a hook odpala się przy jej publikacji. Twój handler tworzy krótki link. Potem zapisuje go przy kampanii i zwraca do szablonu e-maila. W 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;
}
Klucz idempotentności pochodzi z ID kampanii. Gdy hook publikacji odpali się dwa razy (odpali, bo dostawy są at-least-once), drugie wywołanie dostaje ten sam link zamiast duplikatu. Tagi campaign: i batch: to Twoje własne klucze łączące. Raporty kliknięć wiążą się z kampanią właśnie przez nie. Pełny potok atrybucji end-to-end z szablonami UTM i przekazywaniem konwersji opisuje przewodnik po śledzeniu UTM.
Czego jeszcze nie ma w API
Niektórych rzeczy, o które pytają zespoły, dziś nie ma:
- Analityka linku w jednym wywołaniu. Pojedyncze żądanie zwracające każdy podział dla jednego linku nie istnieje; dziś osobno pobierasz kliknięcia, kraj, źródło odsyłające, urządzenie i szereg czasowy, więc uruchamiaj je równolegle z własnego kodu.
- Odtwarzanie webhooków z poziomu API. Panel pokazuje historię dostaw i potrafi je odtworzyć; API jeszcze nie.
- Zmiany sluga z przekierowaniem 301. To samo dotyczy pól
utmimetadataprzy tworzeniu; dopóki ich nie ma, wpisuj parametry UTM w adres docelowy, a klucze łączące w tagi.
Moja żelazna zasada: jeśli jest w specyfikacji OpenAPI, buduj na tym. Jeśli jest tylko we wpisie na blogu, także w tym, traktuj to jako planowane.
Powiązane na blogu
Ten hub stoi w klastrze funkcji obok tekstu smart linki wyjaśnione, który opisuje, jak silnik przekierowań rozwiązuje link na brzegu sieci.
- API skracacza URL: limity zapytań, ponowienia i idempotentność - utwardzanie integracji pod ruch produkcyjny.
- Skracacze URL dla programistów - co sprawdzić w API, zanim zaczniesz na nim budować.
- Śledzenie konwersji po stronie serwera przez krótkie linki - rozszerzenie API o przekazywanie konwersji.
- Jak skrócić URL bez kodu - ścieżki przez panel, telefon i arkusz kalkulacyjny.
- Powierzchnia produktu:
/features/api-sdksi/solutions/developers.
Najczęściej zadawane pytania
Jak skrócić URL za pomocą API?
Wyślij uwierzytelnione żądanie POST z długim adresem URL w treści JSON i odczytaj krótki link z odpowiedzi. W Elido to POST https://api.elido.app/v1/links z nagłówkiem Authorization: Bearer, po którym następuje Twój klucz API, oraz treścią {"destination_url": "..."}; odpowiedź 201 zawiera short_url. Każdy język w tym przewodniku to opakowanie wokół tego jednego żądania.
Jak skrócić URL w Pythonie?
Użyj requests.post na endpoincie linków, z kluczem w nagłówku Authorization: Bearer, json={"destination_url": url} i limitem czasu, wywołaj raise_for_status(), a potem odczytaj response.json()["short_url"]. Przy tysiącach adresów przejdź na httpx.AsyncClient z asyncio.Semaphore, żeby w locie było tylko kilka żądań naraz.
Jak skrócić URL w JavaScripcie lub Node.js?
Wywołaj fetch z Node 18 lub nowszego metodą POST, z nagłówkiem Bearer i treścią JSON, sprawdź res.ok przed odczytem treści i weź short_url ze sparsowanego JSON. Uruchamiaj to na serwerze lub w trasie serverless, nigdy w kodzie przeglądarki, bo klucz wysłany na stronę przeczyta każdy, kto otworzy DevTools.
Czy potrzebuję SDK, żeby korzystać z API skracacza linków?
Nie. Utworzenie linku to jedno żądanie POST z dwoma nagłówkami, a wyśle je standardowy klient HTTP każdego języka: requests lub httpx, fetch, net/http, java.net.http, HttpClient, curl, Net::HTTP i Invoke-RestMethod. SDK się opłaca, gdy korzystasz z wielu endpointów i chcesz typowanych modeli, ale zwykłe wersje HTTP z tego przewodnika to to, co dziś wspieramy w produkcji.
Jak zapobiec temu, żeby ponowienia nie tworzyły zduplikowanych krótkich linków?
Wyślij nagłówek Idempotency-Key i zachowaj go bez zmian przy każdym ponowieniu jednego logicznego żądania, na przykład jako hash adresu docelowego lub identyfikator zamówienia. Elido przechowuje pierwszą udaną odpowiedź przez 24 godziny dla danego workspace i odtwarza ją przy powtórzonym kluczu, więc ponowienie po zgubionej odpowiedzi zwraca oryginalny link zamiast tworzyć drugi.
Ile krótkich linków na sekundę mogę tworzyć przez API?
Elido ogranicza ruch API na workspace za pomocą token bucket: 10 żądań na sekundę z serią do 20 w planie Free, 30 z serią do 60 w Pro i 100 z serią do 200 w Business. Odpowiedź 429 zawiera Retry-After: 1. Przy dużych partiach POST /v1/links/bulk przyjmuje do 100 linków w jednym żądaniu.
Wypróbuj Elido
Wklej URL, otrzymaj krótki link
Bez rejestracji. Link działa 30 dni. Zarejestruj się, aby zachować go na zawsze.
Bez rejestracji. Załóż darmowe konto i korzystaj z 50 linków miesięcznie.