Ліміти запитів API: чому з’являється 429 і як цього уникнути

Як працюють ліміти запитів API, що означають 429 Too Many Requests і Retry-After, поширені алгоритми обмеження та як писати клієнти, що вкладаються в ліміти.

Ілюстрація до статті: Ліміти запитів API: чому з’являється 429 і як цього уникнути

Майже кожен публічний API обмежує, скільки запитів один клієнт може надіслати за певний час. Перевищите ліміт — і API відповість 429 Too Many Requests, а інколи тимчасово заблокує ваш ключ. Ліміти — не покарання: вони тримають спільний сервіс швидким і чесним для всіх, захищають його від помилок і зловживань та роблять витрати передбачуваними.

У цьому посібнику пояснено, як працюють ліміти запитів, як читати їх із заголовків відповіді та як писати клієнти, що вкладаються в ліміти, а не борються з ними.

Навіщо API обмежують запити

  • Справедливість. Один активний клієнт не повинен сповільнювати сервіс для всіх інших.
  • Стабільність. Помилку, що шле запити в нескінченному циклі, зупиняють раніше, ніж вона перевантажить базу даних.
  • Безпека. Ліміти роблять підбір паролів, масове копіювання даних і атаки з викраденими обліковими даними повільними й дорогими.
  • Тарифи. Безкоштовні плани мають нижчі ліміти, ніж платні; часто саме за ліміт ви й платите.

Що саме рахують

Ліміти зазвичай рахують на API-ключ, на обліковий запис, на IP-адресу або на їх поєднання. Багато API мають різні ліміти для різних ресурсів: читання даних може дозволяти сотні запитів на хвилину, а розміщення замовлень, надсилання листів чи вхід — значно менше. Деякі сервіси рахують вагу, а не кількість: один важкий запит, що повертає великий список, «коштує» більше за простий. Завжди перевіряйте документацію: два ресурси одного API можуть мати дуже різні бюджети.

Поширені алгоритми обмеження

Фіксоване вікно

Найпростіший підхід: наприклад, 100 запитів за календарну хвилину. Лічильник обнуляється на початку кожної хвилини. Це зрозуміло, але клієнт може надіслати 100 запитів о 12:00:59 і ще 100 о 12:01:00 — сплеск у 200 запитів за дві секунди.

Ковзне вікно

Сервіс дивиться на останні 60 секунд від поточного моменту, а не на календарну хвилину. Сплески на межі вікон зникають, і ліміт працює плавніше.

Відро з токенами (token bucket)

Уявіть відро, що вміщує, скажімо, 20 токенів і поповнюється на 5 токенів за секунду. Кожен запит забирає токен; коли відро порожнє, запити відхиляються, доки воно не наповниться. Це дозволяє короткі сплески до розміру відра, але тримає стабільний середній темп. Це один із найпоширеніших алгоритмів.

Діряве відро й черги

Запити потрапляють у чергу, яка обробляється з фіксованою швидкістю. Зайві запити не відхиляються одразу, а затримуються; відмова приходить лише тоді, коли черга заповнена.

Як читати заголовки лімітів

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

HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 7
X-RateLimit-Reset: 1760880000
  • Limit – розмір бюджету на вікно.
  • Remaining – скільки запитів ще лишилося.
  • Reset – коли бюджет поповниться: як Unix-час або як кількість секунд від зараз.

Коли ліміт перевищено, відповідь зазвичай така:

HTTP/1.1 429 Too Many Requests
Retry-After: 30

Retry-After — стандартний заголовок: кількість секунд або дата. Якщо він є, чекайте щонайменше стільки. Новіший проєкт стандарту вводить заголовки RateLimit і RateLimit-Policy, тож у сучасних API можна побачити й їх.

Як вкладатися в ліміти

1. Поважайте Retry-After і відступайте

Отримавши 429 чи тимчасову помилку 5xx, не повторюйте запит одразу. Використовуйте експоненційну затримку з випадковим зсувом (jitter): чекайте 1 секунду, потім 2, 4, 8 і так далі, додаючи невелику випадкову паузу, щоб багато клієнтів не повторювали запит в одну мить.

async function callWithRetry(fn, maxAttempts = 5) {
  for (let attempt = 1; ; attempt++) {
    const res = await fn();
    if (res.status !== 429 && res.status < 500) return res;
    if (attempt === maxAttempts) return res;
    const retryAfter = Number(res.headers.get('retry-after'));
    const backoff = Math.min(2 ** attempt * 500, 30000);
    const wait = (retryAfter > 0 ? retryAfter * 1000 : backoff) + Math.random() * 250;
    await new Promise(r => setTimeout(r, wait));
  }
}

2. Кешуйте те, що не змінюється

Якщо значення змінюється раз на годину, не запитуйте його щосекунди. Зберігайте відповіді локально й використовуйте повторно; де API підтримує ETag та If-None-Match, користуйтеся ними — відповідь 304 Not Modified часто дешевша або безкоштовна.

3. Об’єднуйте запити й розумно гортайте сторінки

Багато API приймають кілька ID в одному запиті або повертають до сотень елементів на сторінку. Десять запитів на десять елементів зазвичай можна замінити одним.

4. Обирайте push замість опитування

Замість щосекундного «чи щось змінилося?» використовуйте вебхуки або потокове з’єднання (WebSocket), якщо провайдер їх пропонує. Оновлення приходять миттєво, а бюджет запитів майже не витрачається.

5. Обмежуйте темп на своєму боці

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

6. Стежте за залишком

Записуйте в лог заголовок Remaining або рахуйте власні запити. Стабільне падіння до нуля — ранній сигнал, ще до того, як користувачі побачать помилки.

Помилки, що ведуть до блокувань

  • Повтор запитів у щільному циклі після 429 — це лише подовжує блокування.
  • Кілька копій одного скрипта з одним API-ключем, кожна з яких вважає, що має весь ліміт.
  • Зміна IP-адрес або створення багатьох безкоштовних акаунтів, щоб обійти ліміти. Зазвичай це порушує умови провайдера й може закінчитися закриттям усіх ключів.
  • Ігнорування лімітів окремих ресурсів і орієнтація лише на загальну цифру.

Якщо у вас власний API

  • Повертайте 429 з Retry-After, а не загальні 400 чи 500.
  • Додавайте заголовки ліміту й залишку, щоб клієнти могли підлаштуватися.
  • Ставте суворіші ліміти на чутливі ресурси: вхід, скидання пароля, платежі.
  • Чітко документуйте ліміти — хороша документація запобігає більшості випадкових перевантажень.

Головне

  • Відповідь 429 означає «повільніше», а не «щось зламалося».
  • Читайте заголовки лімітів і дотримуйтеся Retry-After.
  • Повторюйте з експоненційною затримкою та jitter і обмежуйте кількість спроб.
  • Кешуйте, об’єднуйте запити й використовуйте вебхуки, щоб запитів потрібно було менше.

Читайте також: коди стану HTTP, методи REST API і що таке API-сервіси.