Rate limit в API: что это, какие бывают лимиты и клиент под них

Что значит 429 на самом деле, чем RPM отличается от TPM и burst и как написать клиент, который не упирается в лимиты и не ретраит то, что ретраить бесполезно.

5 мин · · API-ключи · разработка

Rate limit for API — это ограничение на то, как часто и как много вы можете отправлять запросов за единицу времени. Когда вы его превышаете, сервер отвечает кодом 429, и дальше всё зависит от клиента: либо он аккуратно подождёт и повторит, либо засыплет сервер ретраями и усугубит ситуацию. Ниже — какие бывают лимиты у API моделей, как они выглядят через шлюз и как написать клиент на Python, который с ними живёт.

Какие бывают лимиты

У LLM API обычно несколько независимых ограничений, и 429 может прийти по любому из них:

ЛимитЧто считаетТипичный симптом
RPM (requests per minute)число запросов в минуту429 при серии коротких запросов
TPM (tokens per minute)входные и выходные токены в минуту429 при немногих, но «тяжёлых» запросах
Одновременные запросысколько запросов «в полёте»429 при запуске 50 потоков сразу
Бюджетный лимит ключапотраченные доллары за день или месяц429 до конца суток или месяца

Первые три задаёт поставщик модели, причём для разных моделей и разных уровней доступа цифры различаются. Конкретные значения у поставщиков меняются, поэтому в статье их нет: смотрите документацию поставщика.

Burst и средняя скорость: в чём разница

Лимит «120 запросов в минуту» можно понимать двумя способами. Первый: окно в минуту, внутри которого можно потратить все 120 запросов за первые пять секунд. Второй: равномерная скорость, 2 запроса в секунду, без всплесков. Разница называется burst (всплеск).

Часто это схема token bucket: «ведро» вмещает, скажем, 10 запросов (burst), а наполняется со скоростью 2 запроса в секунду. Пока ведро не пустое, вы можете выстрелить пачкой из 10, потом упрётесь в среднюю скорость. Если вы не знаете, как устроен лимит у конкретного поставщика, безопасно держаться средней скорости и считать burst запасом на неровности.

Простая арифметика для планирования: при 120 RPM и burst 10 тысяча запросов займёт не меньше (1000 − 10) / 2 = 495 секунд, то есть около 8 минут. Если ваши запросы весят по 1 500 токенов, то на 120 RPM приходится 120 × 1 500 = 180 000 токенов в минуту, и TPM может сработать раньше RPM.

Как это устроено через шлюз Zukko

У самого Zukko общего rate limit нет. 429 через шлюз приходит в двух случаях:

  1. Исчерпан дневной или месячный лимит ключа (key_daily_limit_reached или key_monthly_limit_reached). Вы сами задаёте его в долларах для каждого ключа, подробнее в документации по лимитам ключей и кэшу.
  2. Ограничение у провайдера модели: поставщик ответил 429, и шлюз передаёт это вам.

Различать их важно, потому что реакция противоположная. Лимит поставщика проходит через секунды или минуты, и повтор с паузой помогает. Лимит ключа не пройдёт, пока не наступит новый день или месяц, либо пока вы не поднимете его в кабинете: ретрай здесь только жжёт время. Отличить можно по коду в теле ответа.

Если лимиты ключа — ваш предохранитель от «убежавшего» скрипта, ставьте их заранее, а ретраи пишите только для лимита провайдера. Про ошибку отдельно: что делать с 429.

Клиент с очередью и экспоненциальной задержкой

Ниже асинхронный клиент на Python. Он держит среднюю скорость через token bucket, ограничивает число одновременных запросов семафором, а при 429 от провайдера ждёт 1, 2, 4, 8 секунд и так далее с небольшим случайным добавком (jitter), чтобы параллельные задачи не просыпались одновременно.

bash
import asyncio, os, random, time
from openai import AsyncOpenAI, RateLimitError, APIStatusError, APIConnectionError

client = AsyncOpenAI(
    base_url="https://api.zukko.pro/v1",
    api_key=os.environ["ZUKKO_API_KEY"],
    max_retries=0,  # ретраи делаем сами
)

class Limiter:
    """Token bucket: средняя скорость rpm в минуту, всплеск до burst."""
    def __init__(self, rpm: int, burst: int):
        self.rate = rpm / 60.0
        self.capacity = burst

Что здесь важно. Пауза происходит вне семафора, иначе «спящие» задачи занимали бы слоты. Ошибки 400-го диапазона, кроме 429, вылетают сразу: ретраить неверный запрос бессмысленно. Лимитер ставится перед каждой попыткой, включая повторные, потому что повтор тоже расходует запрос. Если в ответе есть заголовок Retry-After, лучше использовать его вместо расчётной паузы; проверьте, приходит ли он у вашей модели.

Практические правила

  • Начинайте ниже лимита. Если поставщик разрешает 120 RPM, ставьте 80–100: запас на клиентские часы и чужие запросы с того же ключа.
  • Считайте токены, а не только запросы. Тяжёлые запросы упираются в TPM; заранее оценить размер можно через count_tokens или по длине текста.
  • Ограничьте параллелизм. Часто достаточно 4–8 одновременных запросов, чтобы выйти на нужную скорость.
  • Логируйте причину 429. Сохраняйте тело ответа: по нему видно, лимит ключа это или поставщика.
  • Не ретрайте бесконечно. Потолок попыток и общий таймаут задачи обязательны.

Для больших пакетов смотрите разбор массовой обработки без упора в лимит.

Частые вопросы

Есть ли у Zukko общий rate limit?

Нет, своего общего ограничения скорости нет. 429 приходит либо из-за дневного или месячного лимита вашего ключа, либо потому, что ответил 429 провайдер модели.

Чем RPM отличается от TPM?

RPM считает запросы, TPM считает токены, входные и выходные вместе. Много мелких запросов упираются в RPM, несколько огромных — в TPM.

Нужно ли ретраить 429?

Если это лимит провайдера, да, с экспоненциальной паузой и jitter. Если лимит ключа (key_daily_limit_reached, key_monthly_limit_reached), нет: поднимите лимит в кабинете или дождитесь нового периода.

Как не получить 429 от своего же ключа?

Задайте дневной и месячный лимит в долларах под реальный бюджет задачи и следите за расходом в разделе «Использование». Создать ключ и выставить лимиты можно в кабинете.

zukko

Платите в 25×
меньше

за Claude Code & ChatGPT

Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.

Rate limit for API: RPM, TPM, burst и клиент на Python · Zukko