Коротко
Один API-ключ может отправлять 600 чтений (GET) и 200 записей (POST, PATCH, DELETE) в минуту. При превышении API отвечает 429 с кодом request_rate_limited и заголовком Retry-After, в котором указано, сколько секунд подождать. В каждом успешном ответе приходит X-RateLimit-Remaining — сколько запросов осталось в текущей минуте.
Но не всякий 429 — это частота. Коды tariff_daily_burst и tariff_limit_reached приходят с тем же HTTP-статусом, но это лимиты тарифа: подождать пару секунд не поможет. Поэтому код должен сначала прочитать поле error, а уже потом решать, повторять ли запрос.
Точные лимиты
| Что | Лимит | Счётчик |
|---|---|---|
| GET (чтение) | 600 в минуту | отдельно на каждый API-ключ |
| POST, PATCH, DELETE (запись) | 200 в минуту | отдельно на каждый API-ключ |
| Публичные запросы страницы оплаты | 120 в минуту | по IP-адресу |
Окно — фиксированная минута: когда минута заканчивается, счётчик обнуляется. Если вы создали два ключа, у каждого свой счётчик — это удобно, когда у вас несколько проектов, но плодить ключи ради обхода лимита не нужно: обычный магазин до этих значений не доходит.
Заголовок X-RateLimit-Remaining приходит в каждом успешном ответе. Пишите его в мониторинг — так вы увидите приближение к порогу заранее, а не по факту ошибок.
Три вида 429
| Код | Откуда приходит | Что случилось | Что делать |
|---|---|---|---|
request_rate_limited | API (/api/v1/*) | Превышена минутная частота по ключу | Подождать указанное в Retry-After и повторить |
rate_limited | Код входа в кабинет | Код запрошен повторно раньше чем через 60 секунд или исчерпан суточный лимит кодов | Подождать минуту. В ответе есть поле retryAfterSeconds |
too_many_attempts | Ввод кода | Слишком много неверных попыток ввода | Запросить новый код, старый вводить бесполезно |
rate_limited и too_many_attempts относятся к входу в кабинет и в серверной интеграции обычно не встречаются. Если они у вас появились — значит код обращается к эндпоинтам авторизации.
Как отличить частоту от лимита
Путаница здесь встречается чаще всего. Если исчерпан лимит, ожидание не поможет — сколько ни жди, в эту минуту не пройдёт.
| Код | Тип | Когда отпустит | Можно ли повторять |
|---|---|---|---|
request_rate_limited | Частота | Через несколько секунд | Да, по Retry-After |
tariff_daily_burst | Суточная защита | На следующие сутки | Нет. Сначала проверьте код — это обычно интеграция, ушедшая в цикл |
tariff_limit_reached | Месячный лимит тарифа | В новом месяце или после повышения тарифа | Нет. Кабинет → Тариф |
Слепой ретрай опасен именно здесь: получив tariff_daily_burst, цикл не остановится и будет часами долбить API. Подробнее: Тарифы и лимиты.
Заголовок Retry-After
В ответе request_rate_limited заголовок Retry-After приходит в секундах — это время до конца текущего минутного окна. Прочитать его и подождать ровно столько — самая правильная тактика: если повторить раньше, снова получите 429.
Если заголовка нет (например, его срезал прокси), переходите на бэкофф: 1 секунда, дальше удвоение.
Как обработать это в коде
Правило простое: читайте поле error и решайте по нему.
const RETRYABLE = new Set(['request_rate_limited', 'rate_limited']);
async function callWithRetry(fn, { maxAttempts = 5 } = {}) {
const delays = [1000, 2000, 4000, 8000]; // 1, 2, 4, 8 секунд
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
return await fn();
} catch (err) {
// Лимит тарифа — ожидание не поможет, выходим сразу
if (err.code === 'tariff_limit_reached' || err.code === 'tariff_daily_burst') throw err;
if (!RETRYABLE.has(err.code) && !(err.status >= 500)) throw err;
if (attempt === maxAttempts - 1) throw err;
const retryAfter = Number(err.retryAfterSeconds || 0) * 1000;
const wait = retryAfter || delays[Math.min(attempt, delays.length - 1)];
await new Promise((r) => setTimeout(r, wait + Math.random() * 300));
}
}
}
Случайная добавка в конце (Math.random()) — это джиттер. Он нужен, чтобы несколько параллельных процессов не проснулись в одну и ту же миллисекунду и не получили 429 снова.
Повторяя создание счёта, всегда передавайте тот же Idempotency-Key — иначе может оказаться, что первый запрос всё-таки прошёл, и повтор создаст второй счёт. Подробнее: Идемпотентность.
Как вообще не упираться в частоту
| Сценарий | Правильный подход |
|---|---|
| Выставить много счетов разом | POST /api/v1/invoices/bulk — до 100 счетов за один запрос: Массовое создание счетов |
| Ждать оплату | Вебхук как основной канал, опрос только первые 30 секунд: Вебхук или опрос статуса |
| Периодически сверять все счета | Берите список GET /invoices с фильтрами, а не каждый счёт отдельным запросом |
| Отчётность | Один экспорт в сутки вместо запроса на каждую строку |
Самая частая причина упора в лимит — цикл с GET /invoices/{id} без условия выхода. Пропишите условие остановки в каждом таком цикле.
Вопросы и ответы
Получил 429, но заголовка Retry-After нет. Значит это не request_rate_limited, а код тарифа. Смотрите поле error.
Частота влияет на количество счетов? Нет. Частота — это запросы в минуту, а лимит тарифа — счета за месяц. Считаются независимо.
Можно ли поднять лимит? Стандартных значений хватает при обычной нагрузке. Если ваш сценарий в них не укладывается, напишите в поддержку.
В песочнице лимиты другие? Частота такая же. А лимиты тарифа на счета песочницы не распространяются.
Несколько серверов работают с одним ключом. Счётчик общий на ключ, поэтому лимит делится между всеми серверами. Если нагрузку нужно развести, выдайте каждому сервису свой ключ.