Три эндпоинта, заголовок авторизации, JSON-тело. API сокращателя URL - одна из самых простых интеграций в любом бэклоге, и quickstart позволяет получить рабочую короткую ссылку за несколько минут. Что quickstart пропускает - так это всё, что происходит, когда интеграция работает под нагрузкой: rate limiter начинает отклонять запросы, в середине батча прилетает временный 503, очередь задач доставляет одно и то же сообщение дважды. Допустите ошибки здесь - и получите дублирующие ссылки, потерянную работу и шторм из 429, который делает ситуацию ещё хуже.
Эта статья - компаньон по продакшн-закалке к API quickstart. Она охватывает три механики, отделяющие демо от надёжной интеграции: лимиты запросов и способы регулировать темп, какие ошибки повторять и как выполнять backoff, и ключи идемпотентности, которые не дают повторной попытке создать вторую ссылку. Примеры используют API Elido, но паттерны одинаковы для любого грамотно построенного API сокращателя. Если вы относитесь к коротким ссылкам как к инфраструктуре, управляемой кодом, более широкое обоснование этого подхода - в статье короткие ссылки как Terraform.
Лимиты запросов: token bucket и заголовок Retry-After
Elido регулирует API с помощью token bucket, привязанного к рабочему пространству. Базовые скорости: 10 запросов в секунду на Free, 30 на Pro и 100 на Business, с burst-ёмкостью 20, 60 и 200. Например, на Pro полный bucket позволяет отправить 60 запросов сразу, прежде чем скорость вернётся к базовым 30 в секунду. Сверху действует отдельный bucket на API-ключ, так что один шумный ключ не съест всё пространство. Большинство задач по созданию ссылок укладываются в burst и вообще не ощущают лимита.
Гадать, когда сработал лимит, не нужно. Отклонённый запрос несёт три сигнала:
Retry-After- сколько секунд ждать перед повтором (сейчас1).X-RateLimit-Scope- какой bucket сработал:workspaceилиapi_key.- JSON-тело -
{"error":"workspace rate limit exceeded"}, называющее лимит.
Заголовков с остатком токенов нет, поэтому темп регулируйте на стороне клиента: держите параллелизм не выше базовой скорости вашего тарифа, а на первом 429 остановитесь и дождитесь Retry-After, а не мчитесь в стену отказов. Заблаговременный темп держит пропускную способность ровной; слепые повторы после каждого отказа тратят запросы и, если все клиенты повторяют одновременно, порождают thundering herd.
Когда нужно создать много ссылок, не зацикливайте эндпоинт единичного создания. POST /v1/workspaces/{workspace_id}/links/bulk принимает до 100 ссылок в одном запросе. Один bulk-вызов переносит сотню ссылок за один обмен, тогда как сотня одиночных вызовов потратит сотню токенов и большую часть burst. Именно так импорт из Google Sheets переносит ссылки целой кампании, не задевая лимитер.
429 Too Many Requests - статус, который RFC 6585 зарезервировал именно для этого - приходит с заголовком Retry-After, сообщающим, сколько секунд ждать. Соблюдайте его. Это число лимитера говорит, когда появится токен, а это лучше любой догадки вашего backoff.
Повторные попытки: какие коды и как выполнять backoff
Не каждую ошибку стоит повторять, и повтор неправильной ошибки - это путь от маленького сбоя к аварии. Разделите ответы на две группы.
Повторяйте эти, потому что они временные: 429 (слишком быстро), и 500, 502, 503, 504 (серверная или шлюзовая ошибка, которая может исчезнуть сама). Не повторяйте эти, потому что тот же запрос завершится так же: 400 (payload невалиден), 401 (токен отсутствует или неверен), 403 (у токена нет нужного скоупа), 404 (ресурс не найден или не принадлежит вам), 409 (конфликт slug или редактирование с устаревшей версией). Первая группа - «подождать и попробовать снова». Вторая - «исправить код или входные данные». Повтор 400 в тугом цикле превращает баг в DoS-атаку на себя.
Для повторяемых кодов ключевой алгоритм - экспоненциальный backoff с jitter. Чистый экспоненциальный backoff - удвоение задержки на каждой попытке - всё равно синхронизирует клиентов, потому что все клиенты, упавшие в один момент, повторяют попытки тоже в одни и те же моменты. Добавление случайности разбрасывает их. Материал AWS о экспоненциальном backoff и jitter - каноническая ссылка, показывающая, почему jitter-версия резко снижает конкуренцию. Компактная версия на TypeScript:
const RETRYABLE = new Set([429, 500, 502, 503, 504]);
async function withRetry<T>(
call: () => Promise<Response>,
max = 5,
): Promise<Response> {
let attempt = 0;
while (true) {
const res = await call();
if (res.ok || !RETRYABLE.has(res.status) || attempt >= max) return res;
// Honor server guidance first; otherwise back off exponentially with full jitter.
const retryAfter = Number(res.headers.get("retry-after"));
const base =
Number.isFinite(retryAfter) && retryAfter > 0
? retryAfter * 1000
: Math.min(1000 * 2 ** attempt, 20_000);
const wait = Math.random() * base; // full jitter
await new Promise((r) => setTimeout(r, wait));
attempt++;
}
}
Три вещи делают это безопасным, а не опасным. Ограничение числа попыток: стойкая ошибка завершается явно, а не крутится бесконечно. Соблюдение Retry-After, когда его присылает сервер, с откатом на вычисляемый backoff только при его отсутствии. И jitter, чтобы группа воркеров, восстанавливающихся после одного сбоя, не бросилась в атаку в одну ногу. Пока опубликован только Go SDK; SDK для TypeScript, Python и Ruby ещё не в реестрах пакетов, поэтому на этих языках эта небольшая обёртка над обычным HTTP - весь клиент, который вам нужен.
Есть одно правило, связывающее повторы со следующим разделом: повтор create безопасен только если create идемпотентен. Иначе каждая попытка рискует создать вторую ссылку.
Идемпотентность: как не создать дублирующие ссылки
Классический сбой выглядит так. Воркер создаёт короткую ссылку, ссылка создана, но 200 не доходит обратно - соединение обрывается на обратном пути. Воркер видит таймаут, считает это провалом и повторяет. Теперь у вас две ссылки для одной кампании. В масштабе дашборд заполняется /foo, /foo-1, /foo-2, и дубли искажают все отчёты ниже по стеку.
Ключи идемпотентности закрывают этот пробел. Отправьте заголовок Idempotency-Key в мутирующем запросе - любую строку до 255 символов - и сервер сохранит ответ под этим ключом. Предъявите тот же ключ снова - получите исходный ответ обратно, с тем же статус-кодом и телом, без повторного выполнения операции. Паттерн тот же, что Stripe документирует для идемпотентных запросов, и это стандартный способ сделать ненадёжную сеть безопасной для записи.
Деталь, от которой зависит всё, - откуда берётся ключ. Не генерируйте случайный ключ на каждую попытку - это бессмысленно, потому что каждый повтор тогда выглядит как новая операция. Формируйте ключ из стабильного бизнес-идентификатора, чтобы одно и то же логическое действие всегда давало один и тот же ключ:
const res = await withRetry(() =>
fetch(`https://api.elido.app/v1/workspaces/${WORKSPACE_ID}/links`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ELIDO_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": `order-${order.id}-link`,
},
body: JSON.stringify({ destination_url: order.landingUrl }),
}),
);
const link = await res.json();
Теперь повтор той же задачи снова несёт order-12345-link, попадает в сохранённый ответ и возвращает уже существующую ссылку. Ровно одна ссылка на заказ, сколько бы раз очередь ни доставила сообщение. Именно это позволяет безопасно комбинировать приведённый выше backoff-цикл с созданием ссылок: повтор и ключ идемпотентности - две половины одной гарантии.
Две границы, которые нужно помнить. Ключ привязан к рабочему пространству: один и тот же ключ в двух разных рабочих пространствах создаёт две ссылки - это правильно для мультитенантного API, но удивляет команды, считающие ключи глобальными. И кэш не вечен - в Elido он хранится 24 часа с ключом по паре (workspace, key). Повтор в пределах окна выполняет дедупликацию; повтор через три дня, от зависшей задачи, которая наконец дренировала очередь, создаст новую ссылку. Для многодневных батчей не полагайтесь только на ключ. Сохраняйте ID ссылки, возвращённый при первом успехе, и проверяйте его перед повторным вызовом. IETF стандартизирует этот заголовок в черновике Idempotency-Key, и оговорка о 24-часовом окне там тоже упоминается.
Если вы подключаете API-интеграцию сегодня и хотите, чтобы она переживала собственные повторы, начните с бесплатного рабочего пространства, сгенерируйте сервисный токен и поставьте ключ идемпотентности на самый первый create - лучше, чем добавлять его потом, когда дубли уже появились.
Всё вместе
Продакшн-готовый вызов create - это три механики в стеке. Держите темп клиента на уровне базовой скорости вашего тарифа, чтобы 429 был редкостью. Оберните вызов в jitter-backoff, который повторяет только временные коды и соблюдает Retry-After. Несите ключ идемпотентности, основанный на бизнес-ID, чтобы повтор был безопасным. С обычным fetch обёртка выше даёт первые два, а ключ вы поставляете сами:
export async function shortenForOrder(order: Order) {
const res = await withRetry(() =>
fetch(`https://api.elido.app/v1/workspaces/${WORKSPACE_ID}/links`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ELIDO_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": `order-${order.id}-link`,
},
body: JSON.stringify({
destination_url: order.landingUrl,
tags: [`order:${order.id}`],
}),
}),
);
if (res.status === 429) {
// withRetry gave up and we are still limited. Defer the job.
throw new RetryableJobError(Number(res.headers.get("retry-after")) || 1);
}
if (!res.ok) throw new Error(`create failed: ${res.status}`); // non-retryable
return res.json();
}
Ничего экзотического. Это та же дисциплина, которой заслуживает любой API с интенсивной записью, применённая к ссылкам. Награда - интеграция, которая делает правильное под нагрузкой, а не тихо портит ваш инвентарь ссылок. Для read-стороны того же API - как вытягивать данные о кликах, не нагружая лимитер, - компромиссы разобраны в вебхуки против поллинга для трекинга кликов, а полная поверхность эндпоинтов - на странице API и SDK и в обзоре решений для разработчиков.
Другие материалы в блоге
- API сокращателя URL: quickstart на 30 минут на пяти языках
- Короткие ссылки как Terraform: управление ссылками как кодом
- Вебхуки против поллинга для трекинга кликов
- Пакетный импорт кампаний из Google Sheets
- Подключение API к Claude и Cursor через MCP
- Бесплатный API URL-сокращателя: форма запроса, авторизация и ограничения
Частые вопросы
Каковы лимиты запросов к API сокращателя URL?
В Elido лимиты действуют на уровне рабочего пространства и зависят от тарифа, по схеме token bucket: 10 запросов в секунду на Free (burst 20), 30 на Pro (burst 60) и 100 на Business (burst 200). Сверху действует отдельный bucket на каждый API-ключ. Когда лимит срабатывает, API возвращает 429 с заголовками Retry-After и X-RateLimit-Scope, указывающим, какой bucket исчерпан, так что клиент знает, сколько ждать. Массовое создание принимает до 100 ссылок за запрос, поэтому bulk-эндпоинт - правильный инструмент для больших задач.
Какие ошибки API стоит повторять?
Повторяйте только временные сбои: 429 (превышен лимит), 500, 502, 503 и 504. Используйте экспоненциальный backoff с jitter и ограничением числа попыток. Никогда не повторяйте 400, 401, 403, 404 и 409 - это ошибки валидации, проблемы аутентификации или конфликты, и тот же запрос снова завершится ошибкой. Сочетайте повторы с ключом идемпотентности, чтобы повторная попытка создания не породила дублирующую ссылку.
Что такое ключ идемпотентности и когда он нужен?
Ключ идемпотентности - это непрозрачная строка, которую вы отправляете в мутирующем запросе, чтобы сервер мог распознать повтор и вернуть исходный результат, не выполняя операцию дважды. Он нужен всегда, когда создание или обновление может быть повторено - а это всегда, потому что сети сбоят, а очереди задач гарантируют доставку хотя бы раз. Формируйте ключ из стабильного бизнес-идентификатора, например ID заказа, чтобы одна и та же логическая операция всегда несла один и тот же ключ.
Как избежать создания дублирующих коротких ссылок?
Отправляйте ключ идемпотентности, основанный на стабильном бизнес-идентификаторе, при каждом создании. Если вызов повторяется, сервер возвращает изначально созданную ссылку, а не создаёт новую. Для задач, у которых повтор может произойти через несколько дней - за пределами окна кэша идемпотентности - дополнительно сохраняйте ID ссылки, возвращённый при первом успехе, и проверяйте его перед повторным вызовом create.
Как долго действует ключ идемпотентности?
В Elido кэш идемпотентности хранится 24 часа с ключом по паре (рабочее пространство, key). Повтор в пределах этого окна вернёт сохранённый ответ; повтор после него будет воспринят как новый запрос и создаст новый ресурс. Для многодневных пакетных задач не полагайтесь только на ключ - сохраняйте ID результирующей ссылки при первом успехе.
Попробуйте Elido
Вставьте URL - получите короткую ссылку
Без регистрации. Ссылка живёт 30 дней. Зарегистрируйтесь, чтобы оставить её навсегда.
Без регистрации. Зарегистрируйтесь бесплатно и получите 50 ссылок в месяц.