12 хв читанняМожливості

API URL-скорочувача: 30-хвилинний швидкий старт п'ятьма мовами

Від нуля до робочої автоматизації коротких посилань на TypeScript, Python, Go, Ruby та PHP - авторизація, ідемпотентність, обробка помилок і підводні камені продакшену.

Marius Voß
DevRel · edge infra
Діаграма швидкого старту п'ятьма мовами з панелями коду для TypeScript, Python, Go, Ruby та PHP, які всі звертаються до центральної кінцевої точки API Elido

API URL-скорочувача - одна з менших інтеграцій у типовому беклозі інженерної команди. Три кінцеві точки, заголовок авторизації, JSON-payload. Сторінка документації обіцяє перший виклик за п'ять хвилин. Потім приходить продакшн-трафік, логіка повторних спроб створює дублікати посилань, дашборд заповнюється варіантами /foo-1, /foo-2, /foo-3 для одного й того самого пункту призначення, і хтось заводить тікет.

Цей матеріал проходить через реальну інтеграцію. Авторизація, перший виклик, чотири кінцеві точки, що покривають більшість сценаріїв використання, ідемпотентність, обробка помилок, ліміти швидкості і підводні камені продакшену, які пропускає п'ятихвилинний швидкий старт. Приклади коду на TypeScript, Python, Go, Ruby та PHP - перші три через офіційні SDK (@elido/sdk, elido-python, github.com/elido/elido-go), останні два - через звичайні HTTP-клієнти.

Передумови

Увійдіть у дашборд, перейдіть на /dashboard/api-keys і створіть API-ключ (він починається з elido_). Токени прив'язані до робочого простору - токен, виданий у робочому просторі A, не може створювати посилання в робочому просторі B. Токени машинних користувачів (для CI-систем, внутрішніх інструментів, інтеграцій machine-to-machine) створюються на /dashboard/machine-users і ротуються незалежно від персональних ключів. Обидва типи мають задану заздалегідь роль у робочому просторі (viewer, editor або admin), а не окремі права для кожної кінцевої точки, тож видавайте CI-завданню роль editor, якщо воно лише створює посилання. У посібнику з дозволів API-ключів перелічено, до чого може мати доступ кожна роль, зокрема пояснено, чому для змін вебхуків потрібна роль admin.

Базова URL-адреса - https://api.elido.app/v1. Домени для редиректу (f.elido.me, s.elido.me, b.elido.me) окремі від поверхні API. Ваші короткі посилання розв'язуються на домені редиректу; API призначений для створення, зміни і читання посилань.

Специфікація OpenAPI опублікована за адресою https://elido.app/openapi.json і відповідає OpenAPI 3.1. Офіційні SDK згенеровані з цієї специфікації і перевидаються з кожним релізом API; ви також можете згенерувати власний клієнт будь-якою мовою з підтримкою OpenAPI.

Перший виклик

Створіть коротке посилання з цільової URL-адреси. П'ять рядків на 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 (без офіційного SDK - через 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'];

Усі п'ять прикладів дають однаковий результат. Тіло відповіді містить коротку URL-адресу, канонічний ID посилання, ID робочого простору і мітку часу створення. Slug - abc123 у прикладі вище - генерується сервером, якщо ви не передасте slug у запиті. Алфавіт slug - base62 ([0-9A-Za-z]); довжина за замовчуванням - шість символів.

Чотири кінцеві точки, які ви справді використовуватимете

В API більше ніж чотири кінцеві точки, але більшість інтеграцій обходяться цим набором.

Діаграма типу «маточина-спиці» з чотирма основними кінцевими точками посилань навколо ресурсу /v1/links: POST для створення, GET для читання, PATCH для оновлення і DELETE для видалення, кожна зі своїм ключовим підводним каменем.

Створення посилання

POST /v1/links приймає цільову URL-адресу плюс опційні поля:

  • slug - slug, який ви обираєте самі (має бути унікальним на домені).
  • domain_id - для посилань на власному домені; на /v1/links використовується короткий домен за замовчуванням вашого плану, якщо його не вказано. Шлях із робочим простором /v1/workspaces/{workspace_id}/links вимагає його.
  • title - мітка, яка відображається в дашборді.
  • tags - масив довільних рядків для організації.
  • expires_at - мітка часу RFC 3339, після якої посилання повертає 410 Gone.
  • redirect_status - 301, 302 (за замовчуванням) або 307.
  • password - під час створення поки що не приймається; встановіть його одразу після цього через PATCH, і редирект спершу покаже сторінку з паролем.
  • utm і metadata - заплановані. Сьогодні додавайте параметри UTM безпосередньо до destination_url, а власні ключі зв'язку зберігайте в tags.

Власний slug - це поле, яке кусає команди в продакшені. Якщо ви передаєте slug, який вже використовується іншим посиланням на тому самому домені, API повертає 409 Conflict. Наївний обробник повторних спроб, що додає лічильник (my-slug-1, my-slug-2), спричиняє проблему з дублікатами посилань, описану на початку. Правильна поведінка при повторних спробах описана в розділі про ідемпотентність нижче.

Читання посилання

GET /v1/links/{id} повертає повний запис посилання, включно з short_url і всіма налаштуваннями. Кількість кліків відсутня в записі посилання; її можна отримати з наведених нижче кінцевих точок аналітики. ID посилання - канонічний ідентифікатор: slug може змінюватися, ID - ні.

GET /v1/links?host=…&tags=…&limit=… перелічує посилання в робочому просторі з фільтрами. Пагінація на основі курсору; next_cursor у відповіді непрозорий і повертається як параметр запиту cursor у наступному запиті.

Оновлення посилання

PATCH /v1/links/{id} приймає ті самі поля, що й створення. Найпоширеніші оновлення: зміна цільової URL-адреси (корисно для ротації кампаній без повторного друку QR-кодів), зміна тегів, продовження expires_at. Slug змінюється через той самий PATCH, якщо передати новий slug. Старий slug одразу перестає розв'язуватися; окрема кінцева точка перейменування, яка зберігала б 301 зі старого slug протягом періоду збереження, запланована, але ще не створена.

Видалення посилання

DELETE /v1/links/{id} виконує м'яке видалення і повертає 204 No Content. Посилання перестає перенаправляти і зникає з викликів списку та читання. Розділ «Кошик» з кінцевою точкою відновлення і 90-денним вікном до остаточного видалення запланований; сьогодні немає виклику API, який повертає видалене посилання.

Ключі ідемпотентності

Кожен запит, що змінює дані - POST, PATCH, DELETE - приймає заголовок Idempotency-Key. Значення заголовка - непрозорий рядок довжиною до 255 символів; сервер зберігає тіло відповіді і код статусу протягом 24 годин за ключем (workspace_id, idempotency_key) і повертає збережену відповідь, якщо той самий ключ надійде знову.

Офіційні SDK генерують ключі ідемпотентності автоматично, якщо їх не передано. Ви можете перевизначити його:

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

Сценарій використання - цикл повторних спроб. Якщо ваше завдання створює посилання в межах обробки вхідного замовлення, генеруйте ключ ідемпотентності з ID замовлення. Повторна спроба того самого завдання побачить той самий ключ, потрапить у кеш ідемпотентності і поверне вже створене посилання, а не створить друге.

Конвеєр, де хук кампанії з гарантією at-least-once надсилає два виклики створення з однаковим ключем ідемпотентності; 24-годинний кеш усуває дублювання другого виклику, тож створюється рівно одне посилання.

Ключовий підводний камінь: кеш ідемпотентності живе 24 години, а не вічно. Повторна спроба на третій день зависання завдання створить нове посилання. Якщо інтеграція працює на багатоденних пакетах, зберігайте ID посилання, повернутий першим успішним створенням, і шукайте його перед повторним запитом.

Другий підводний камінь: ідемпотентність діє в межах одного робочого простору. Той самий ключ у двох робочих просторах створює два посилання. Це правильна семантика для API з кількома робочими просторами, але вона може здивувати команди, які вважають ключ глобально унікальним.

Обробка помилок

API повертає стандартні HTTP-коди статусу плюс структуроване тіло помилки:

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

Коди, які ви бачитимете найчастіше:

  • 400 invalid_request - помилка валідації payload. Поле message перелічує конкретні поля. Не повторюйте запит; виправте payload.
  • 401 unauthorized - токен відсутній або недійсний. Не повторюйте запит без ротації токена.
  • 403 forbidden - роль токена не дозволяє цю дію (ключ з роллю viewer не може створювати посилання). Перевірте роль ключа на /dashboard/api-keys.
  • 404 not_found - ресурс не існує або токен не має до нього доступу (ми повертаємо 404, а не 403, щоб не розкривати існування ресурсу неавторизованим викликам).
  • 409 conflict - slug вже використовується, або виявлено одночасне редагування (PATCH застарілої версії). Отримайте дані заново і повторіть спробу.
  • 429 rate_limit_exceeded - зменшіть частоту запитів відповідно до значення retry_after.
  • 500 internal_server_error - помилка на боці сервера. Можна безпечно повторити з тим самим ключем ідемпотентності.
  • 502 bad_gateway, 503 service_unavailable, 504 gateway_timeout - тимчасові проблеми інфраструктури. Зменшіть частоту і повторіть спробу.

Офіційні SDK реалізують експоненційну затримку з джиттером для 429, 500, 502, 503 і 504. Вони не повторюють 400, 401, 403, 404 чи 409 - це помилки програмування або конфлікти бізнес-логіки, а не тимчасові збої. Власні HTTP-клієнти мають дотримуватися того самого підходу; повторення запиту 400 з тим самим payload не дасть іншого результату.

Схема рішення, що розподіляє коди статусу API на колонку «повторювати із затримкою» (429, 500, 502, 503, 504) і колонку «не повторювати» (400, 401, 403, 404, 409) для помилок програмування і конфліктів.

request_id у тілі помилки - це поле, яке варто включати в тікети підтримки. Ми можемо відстежити будь-який запит за цим ID через журнал аудиту, журнал застосунку і метрики платформи - і не можемо відстежити запит без нього.

Ліміти швидкості

Опубліковані ліміти швидкості - 100 запитів на секунду на робочий простір на плані Pro, 500 на Business і узгоджений ліміт на Enterprise. Безкоштовний план - 10 запитів на секунду.

Стан ліміту швидкості відображається в трьох заголовках відповіді на кожен запит до API:

  • X-RateLimit-Limit - поточний ліміт за секунду.
  • X-RateLimit-Remaining - кількість запитів, що залишилися в поточній секунді.
  • X-RateLimit-Reset - мітка часу Unix, коли скидається лічильник.

Ліміт 100/с реалізований через token bucket з пропускною здатністю сплеску 200 - тобто ви можете надіслати 200 запитів одразу, якщо кошик повний, а потім перейти на сталий темп 100/с. Більшість завдань зі створення коротких посилань комфортно вкладаються в цей сплеск; інтеграції з інтенсивною аналітикою, що гортають історичні події кліків, виграють від запасу плану Pro.

Для масових операцій кінцева точка POST /v1/links/bulk приймає до 100 посилань за один запит і рахується як одна одиниця ліміту швидкості. Це правильна кінцева точка для будь-якого завдання, що створює більше сотні посилань за раз. Детальніший розбір темпу відносно token bucket, вибору кодів статусу для повторення і того, як ключі ідемпотентності запобігають дублюванню посилань під час повторних спроб, дивіться в ліміти швидкості, повторні спроби та ідемпотентність у продакшені.

Що роблять SDK, чого не робить звичайний HTTP

Офіційні SDK постачають чотири речі, які швидко окупаються:

  • Автоматичне повторення із затримкою для кодів статусу, що допускають повтор.
  • Генерація ключів ідемпотентності, якщо їх не передано явно.
  • Типізовані помилки, тож ви можете писати catch (err) { if (err instanceof ElidoRateLimitError) { … } }, а не розбирати JSON у блоках catch.
  • Ітератори пагінації, тож кінцеві точки списків надають асинхронні ітератори чи генератори замість необхідності вручну обробляти курсор.

Go SDK додатково надає доступ до нижчерівневого HTTP-клієнта для інструментування - корисно, якщо ви хочете підключити його до наявного налаштування трасування. Сторінка функцій API + SDK охоплює всю поверхню; довідник API опубліковано на /docs/api-reference.

Доступ до аналітики

Кінцеві точки аналітики доступні лише для читання і живуть за адресою /v1/workspaces/{id}/analytics/. У посібнику з API аналітики посилань перелічено всі звіти, їхні параметри і форми відповідей. Найпоширеніші запити:

  • GET .../clicks/recent?from=…&to=… - окремі кліки, від найновіших до найстаріших, із пагінацією через next_cursor. Корисно для конвеєрів експорту.
  • GET .../timeseries?from=…&to=…&interval=day - згруповані за відрізками часу кількості кліків для заданого діапазону; interval може бути hour або day, а tz задає часовий пояс відрізка.
  • GET .../breakdown/country?from=…&to=… - розбивка за географією.
  • GET .../breakdown/referrer?from=…&to=… - розбивка за реферером.

Інші звіти: summary, links/top, решта розбивок (host, device, browser, destination) і топсписки (top-countries, top-regions, top-cities, top-referrers, top-destinations). from і to - дати у форматі YYYY-MM-DD, причому to є виключною датою; додайте link_id, щоб звузити будь-який звіт до одного посилання, і limit, щоб задати розмір розбивок і топсписків.

Потік сирих подій кліків - найбільший за обсягом. Робочий простір із 10 млн кліків на місяць генерує близько 600 МБ JSON сирих даних подій на місяць. Для експорту в такому масштабі гайд з експорту аналітики описує механізм масового експорту, що обходить JSON-обгортку і передає дані напряму зі сховища аналітики.

Вебхуки для подій посилань

Вебхуки - протилежність опитуванню: замість того, щоб ви питали API, що змінилося, API сам доставляє події посилань і доменів на вашу кінцеву точку. Налаштуйте на /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,
});

Подія click.created для кожного окремого кліка - в дорожній карті, але поки недоступна, тож наразі дані про кліки надходять через кінцеві точки аналітики. Кожна доставка містить заголовок X-Elido-Signature (також надсилається як X-Webhook-Signature) зі значенням v1=<hex>: HMAC-SHA256, підписаний вашим секретом кінцевої точки, над значенням X-Webhook-Timestamp, крапкою і сирим тілом запиту. Перевіряйте підпис перед обробкою - без цього будь-який виклик може надіслати запит на вашу кінцеву точку webhook і видати себе за Elido.

Семантика доставки - at-least-once: невдала доставка повторюється із затримкою у хвилинах, за замовчуванням до трьох спроб загалом. Детальнішу структуру і поведінку повторних спроб порівнює матеріал про вебхуки проти опитування, який зіставляє два підходи до інтеграції.

Розібраний приклад: автоматизація кампаній

Інтеграція, що мотивує більшість впроваджень API, виглядає так. Ваша маркетингова автоматизація створює кампанію в Customer.io чи HubSpot. Хук спрацьовує, коли кампанію опубліковано. Ваш обробник створює коротке посилання, прив'язує його до запису кампанії і надсилає назад в інструмент керування кампаніями для підстановки в шаблон листа.

На 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;
}

Ключ ідемпотентності виводиться з ID кампанії. Якщо хук публікації кампанії спрацьовує двічі (а так і буває - доставки вебхуків мають гарантію at-least-once), другий виклик повертає те саме посилання, не створюючи дубліката. Теги campaign: і batch: містять ваші власні ключі зв'язку, щоб ви могли зіставляти події кліків Elido з кампанією; окреме поле metadata для цього заплановане. Параметри UTM належать до самого campaign.destinationUrl, доки поле utm не буде випущено.

Наскрізну атрибуцію кампаній з UTM-шаблонами і пересиланням конверсій розглядає наріжний матеріал про відстеження UTM.

Чого ще немає в API

Дві речі, про які часто запитують, наразі недоступні:

  • Єдиний GET для аналітики посилання, що повертає всі розбивки одним викликом. Поточна модель вимагає окремих викликів для кліків, країни, реферера, пристрою і часового ряду. Агрегація - в дорожній карті; наразі виконуйте запити паралельно у власному коді.
  • Повторна доставка webhook через API. Дашборд показує історію доставок webhook і підтримує повторну доставку; API поки ні. Це також в дорожній карті.

Якщо функція є в специфікації OpenAPI, вона підтримується. Якщо вона згадана в цьому матеріалі, але відсутня в специфікації, вважайте її запланованою, а не гарантованою.

Що почитати ще

Спробуйте Elido

Вставте URL - отримайте коротке посилання

Без реєстрації. Посилання живе 30 днів. Зареєструйтесь, щоб зберегти назавжди.

Безкоштовно, без реєстрації · 2 на день

Спробуйте Elido

URL-скорочувач із хостингом у ЄС: власні домени, глибока аналітика, відкритий API. Безкоштовний тариф - без кредитної картки.

Теги
url shortener api
bitly api alternative
link shortener api
rest api short link
url shortener sdk
openapi 3.1
idempotency keys

Читати далі