Elido
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 больше четырех эндпоинтов, но большинство интеграций ограничиваются этим набором.

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

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 заказа. Повторный запуск той же задачи увидит тот же ключ, попадет в кэш идемпотентности и вернет изначально созданную ссылку вместо создания второй.

Важный нюанс: кэш идемпотентности живет 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 с той же нагрузкой не даст иного результата.

Поле 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-данных в месяц. Для экспорта в таком масштабе в руководстве по экспорту в ClickHouse описан механизм массового экспорта, который обходит 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-сокращатель с хостингом в ЕС: собственные домены, глубокая аналитика, открытый API. Бесплатный тариф — без банковской карты.

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

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