10 мин чтенияВозможности

API сокращателя ссылок: 30-минутный быстрый старт на пяти языках

От нуля до работающей автоматизации коротких ссылок на TypeScript, Python, Go, Ruby и PHP - аутентификация, идемпотентность, обработка ошибок и нюансы, возникающие только в продакшене

Marius Voß
DevRel · edge infra
Five-language quickstart diagram with code panels for TypeScript, Python, Go, Ruby, and PHP all pointing at a central Elido API endpoint

API сокращателя ссылок - одна из тех небольших интеграций, которые обычно висят в бэклоге инженерной команды. Три эндпоинта, заголовок аутентификации, JSON-полезная нагрузка. Страница документации обещает первый успешный вызов через пять минут. Но затем в продакшен приходит трафик, логика повторных попыток создает дубликаты ссылок, панель управления заполняется вариантами /foo-1, /foo-2, /foo-3 для одного и того же адреса назначения, и кто-то заводит тикет.

В этом посте мы разберем реальный процесс интеграции. Аутентификация, первый вызов, четыре эндпоинта, которые покрывают большинство сценариев, идемпотентность, обработка ошибок, лимиты запросов (rate limits) и нюансы продакшена, которые пропускают «пятиминутные» руководства. Примеры кода на TypeScript, Python, Go, Ruby и PHP: первые три через официальные SDK (@elido/sdk, elido-python, github.com/elido/elido-go), последние два - через обычные HTTP-клиенты.

Предварительные условия

Войдите в панель управления, перейдите в /settings/api и создайте персональный токен доступа (personal access token). Токены привязаны к области рабочего пространства (workspace) - токен, выданный в рабочем пространстве A, не может создавать ссылки в рабочем пространстве B. Токены сервисных аккаунтов (для систем CI, внутренних инструментов, интеграций machine-to-machine) создаются на том же экране в тарифах Pro и выше; они имеют явные области доступа (links:write, analytics:read, domains:write) и ротируются независимо от персональных токенов.

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

Спецификация OpenAPI опубликована по адресу https://api.elido.app/v1/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 - генерируется сервером, если вы не передали custom_slug в запросе. Алфавит слага - base62 ([0-9A-Za-z]); длина по умолчанию - шесть символов.

Четыре эндпоинта, которые вы действительно будете использовать

У API больше четырех эндпоинтов, но большинство интеграций ограничиваются этим набором.

Схема с четырьмя основными эндпоинтами ссылок вокруг ресурса /v1/links: POST создание, GET чтение, PATCH обновление и DELETE удаление - каждый с ключевым нюансом.

Создание ссылки

POST /v1/links принимает целевой URL и дополнительные поля:

  • custom_slug - слаг, который вы выбираете сами (должен быть уникальным в пределах рабочего пространства).
  • domain_id - для ссылок на кастомных доменах; если не указано, используется основной домен рабочего пространства.
  • tags - массив строк в свободной форме для организации ссылок.
  • utm - параметры кампании, которые будут добавлены к целевому URL при редиректе.
  • expires_at - временная метка ISO 8601, после которой ссылка будет возвращать 410 Gone.
  • password - если установлено, перед перенаправлением будет отображаться страница запроса пароля.
  • metadata - произвольный JSON-объект, который не интерпретируется при редиректе; полезен для ваших собственных ключей связки.

Пользовательский слаг (custom_slug) - это поле, на котором часто обжигаются команды. Если вы передадите слаг, который уже используется другой ссылкой в том же рабочем пространстве, API вернет 409 Conflict. Наивный обработчик повторов, который просто добавляет счетчик (my-slug-1, my-slug-2), создает проблему дубликатов, описанную в начале статьи. Правильное поведение при повторных попытках описано ниже в разделе про идемпотентность.

Чтение ссылки

GET /v1/links/{id} возвращает полную запись ссылки, включая текущее количество кликов, метку времени последнего клика и все настройки. ID ссылки - это канонический идентификатор: слаги могут меняться (Pro+ поддерживает переименование слагов), а ID - нет.

GET /v1/links?domain_id=…&tag=…&limit=… выводит список ссылок в рабочем пространстве с фильтрами. Пагинация основана на курсорах; значение next_cursor в ответе является непрозрачным и передается обратно в параметре запроса cursor для получения следующей страницы.

Обновление ссылки

PATCH /v1/links/{id} принимает те же поля, что и создание. Самые частые обновления: изменение целевого URL (полезно для ротации кампаний без перепечатки QR-кодов), изменение тегов, продление expires_at. Обновление слага выполняется через отдельный эндпоинт POST /v1/links/{id}/rename, который настраивает 301 редирект со старого слага на заданный период удержания (по умолчанию 30 дней).

Удаление ссылки

DELETE /v1/links/{id} выполняет «мягкое» удаление. Ссылка будет возвращать 410 Gone в течение следующих 90 дней, после чего будет удалена окончательно. В панели управления есть раздел «Корзина» с мягко удаленными ссылками; их можно восстановить через интерфейс или через POST /v1/links/{id}/restore в течение 90-дневного окна.

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

Каждый изменяющий запрос - 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 отправляет два вызова create с одинаковым ключом идемпотентности; 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 - ошибка валидации полезной нагрузки. Поле message перечисляет конкретные поля. Не повторяйте запрос, исправьте данные.
  • 401 unauthorized - токен отсутствует или невалиден. Не повторяйте запрос без ротации токена.
  • 403 forbidden - у токена нет нужной области доступа. Проверьте список областей доступа токена в /settings/api.
  • 404 not_found - ресурс не существует или у токена нет к нему доступа (мы возвращаем 404 вместо 403, чтобы не раскрывать существование ресурса неавторизованным отправителям).
  • 409 conflict - слаг уже используется или обнаружено одновременное редактирование (PATCH для устаревшей версии). Получите актуальные данные и попробуйте снова.
  • 429 rate_limit_exceeded - сделайте паузу согласно значению retry_after.
  • 500 internal_server_error - ошибка на стороне сервера. Можно безопасно повторить с тем же ключом идемпотентности.
  • 502 bad_gateway, 503 service_unavailable, 504 gateway_timeout - временные проблемы инфраструктуры. Сделайте паузу и повторите запрос.

Официальные SDK реализуют экспоненциальную задержку с джиттером (jitter) для кодов 429, 500, 502, 503 и 504. Они не повторяют запросы с кодами 400, 401, 403, 404 или 409 - это ошибки программирования или конфликты бизнес-логики, а не временные сбои. Кастомные HTTP-клиенты должны следовать тому же паттерну; повтор запроса 400 с той же нагрузкой не даст иного результата.

Разделение кодов состояния API на два столбца: повтор с задержкой (429, 500, 502, 503, 504) и без повтора (400, 401, 403, 404, 409) - для ошибок программирования и конфликтов.

Поле request_id в теле ошибки - это то, что нужно указывать в тикетах поддержки. По этому ID мы можем отследить любой запрос через аудит-логи, логи приложения и метрики платформы - и мы не сможем найти запрос без него.

Лимиты запросов (Rate limits)

Опубликованные лимиты составляют 100 запросов в секунду на рабочее пространство в тарифе Pro, 500 в тарифе Business и индивидуальный лимит в тарифе Enterprise. Бесплатный уровень - 10 запросов в секунду.

Состояние лимитов передается в трех заголовках каждого ответа API:

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

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

Для массовых операций в тарифах Business+ эндпоинт POST /v1/links/bulk принимает до 1000 ссылок в одном запросе и считается как одна единица лимита. Это правильный выбор для любой задачи, создающей более сотни ссылок за раз.

Что делают SDK, чего не делает обычный HTTP

Официальные SDK предоставляют четыре возможности, которые быстро окупаются:

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

SDK для Go дополнительно предоставляет доступ к базовому HTTP-клиенту для инструментирования - это полезно, если вы хотите подключить его к существующей системе трассировки. Полный обзор представлен на странице возможностей API + SDKs; справочник API опубликован в /docs/api-reference.

Доступ к аналитике

Эндпоинты аналитики предназначены только для чтения и находятся по адресу /v1/workspaces/{id}/analytics/. Самые частые запросы:

  • GET .../links/{id}/clicks?from=…&to=… - «сырые» события кликов с пагинацией. Полезно для пайплайнов экспорта.
  • GET .../timeseries?from=…&to=…&bucket=day - агрегированное количество кликов за период времени.
  • GET .../breakdown/country?from=…&to=… - распределение по странам.
  • GET .../breakdown/referrer?from=…&to=… - распределение по реферерам.

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

Вебхуки для событий кликов

Вебхуки - это противоположность опросу (polling): вместо того чтобы вы спрашивали API о новых кликах, API доставляет их на ваш эндпоинт. Настройка выполняется в /settings/webhooks:

await elido.webhooks.create({
  url: "https://your-app.example/webhooks/elido",
  events: ["link.click", "link.created", "link.expired"],
  secret: process.env.WEBHOOK_SIGNING_SECRET,
});

Каждая доставка включает заголовок Elido-Signature, содержащий HMAC-SHA256 тела запроса с вашим общим секретом. Обязательно проверяйте подпись перед обработкой - без этого любой отправитель сможет отправить запрос на ваш эндпоинт вебхука, выдавая себя за Elido.

Семантика доставки - «как минимум один раз» (at-least-once) с экспоненциальной задержкой до максимального срока удержания в 72 часа. Подробности о структуре данных и поведении при повторах читайте в посте вебхуки против опроса.

Практический пример: автоматизация кампаний

Интеграция, которая чаще всего мотивирует к переходу на 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}`, campaign.channel],
      utm: {
        source: campaign.channel,
        medium: "email",
        campaign: campaign.slug,
      },
      metadata: { campaign_id: campaign.id, batch: campaign.batchId },
    },
    {
      idempotencyKey: `campaign-${campaign.id}-link`,
    },
  );

  await campaignStore.update(campaign.id, { shortUrl: link.shortUrl });
  return link;
}

Ключ идемпотентности вычисляется на основе ID кампании. Если хук публикации кампании сработает дважды (а вебхуки доставляются по принципу at-least-once), второй вызов вернет ту же ссылку без создания дубликата. Поле metadata хранит ваши собственные ключи связки, чтобы вы могли соотнести события кликов Elido с кампанией без парсинга тегов.

Для сквозной атрибуции кампаний с UTM-шаблонами и передачей конверсий ознакомьтесь с материалом отслеживание UTM от начала до конца.

Чего еще нет в API

Две вещи, о которых часто спрашивают, но которые пока недоступны:

  • Единый GET-запрос аналитики для ссылки, возвращающий все распределения за один вызов. Текущая модель требует отдельных вызовов для кликов, стран, рефереров, устройств и временных рядов. Агрегация есть в планах; пока что SDK параллелят эти запросы с помощью одного вспомогательного метода.
  • Повтор (replay) вебхуков через API. Панель управления отображает историю доставок вебхуков и поддерживает повторную отправку, но в 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

Читать дальше