Ошибка 429 и лимиты: как генерировать без отказов
Что значит ответ 429 в API Rendergrid, какие лимиты его вызывают и что делать: держать свою очередь в пределах лимитов аккаунта, повторять запрос с паузой и тем же ключом повтора, получать готовые картинки вебхуком вместо частого опроса.
Редакция RendergridО редакции
Коротко
Ответ 429 значит, что превышен один из лимитов: аккаунт отправил больше 120 запросов в минуту или держит больше 100 заданий в очереди или в работе либо одно задание опрашивали чаще, чем раз в 5 секунд. Держите свою очередь в этих пределах, повторяйте запрос после паузы не короче Retry-After и с тем же ключом повтора, а готовые картинки получайте вебхуком вместо частого опроса.
Что значит ответ 429?
Ответ 429 приходит, когда запросов слишком много: API отказывает, пока лимит не освободится. Частота запросов и число заданий считаются на аккаунт, а не на ключ: все ключи делят один счёт, и дополнительный ключ его не увеличивает. По умолчанию значения одинаковы для любого аккаунта, и для только что зарегистрированного тоже.
| Что считается | Предел по умолчанию | Код ошибки | Источник |
|---|---|---|---|
| Частота запросов | 120 запросов в минуту, окна по 60 с | ERR_RATE_LIMIT | Документация: лимиты |
| Задания в работе | 100 в очереди или в работе | ERR_QUOTA_EXCEEDED | Документация: лимиты |
| Опрос задания | не чаще раза в 5 с на ключ и задание | ERR_RATE_LIMIT | Документация: лимиты |
| Загрузка образцов | 100 образцов в сутки до первого пополнения | ERR_RATE_LIMIT | Документация: лимиты |
| Ошибки доступа | 30 отказов 401 или 30 отказов 403 в минуту | ERR_RATE_LIMIT | Документация: лимиты |
У вашего аккаунта лимит может быть другим. Если его не хватает, напишите в поддержку: назовите аккаунт и нужный объём.
В лимит частоты входят запуск генераций, загрузка файлов, чтение каталога моделей и чтение баланса. Опрос задания считается отдельно, по своему правилу. Место в лимите заданий занято, пока задание стоит в очереди или рисуется.
Ещё два случая не связаны с частотой запросов. Аккаунт, который зарегистрировался сам и ещё не пополнял баланс, может использовать не больше 100 образцов в сутки; после первого пополнения лимита нет. А если с одного IP-адреса приходит 30 отказов 401 или 30 отказов 403 в минуту, адрес блокируется до 120 секунд: ответ 429 содержит details.reason = burst_block.
Как отличить один 429 от другого
Статус один, причин несколько. Смотрите на error.code и error.details: по ним выбирают, что делать. Текст error.message написан для человека и может меняться, не разбирайте его программой.
HTTP/1.1 429 Too Many Requests Retry-After: 17 { "error": { "code": "ERR_RATE_LIMIT", "message": "rate limit exceeded", "details": { "limit": 120, "window": "1m", "retry_after": 17 } } }
HTTP/1.1 429 Too Many Requests Retry-After: 2 { "error": { "code": "ERR_QUOTA_EXCEEDED", "message": "concurrency limit reached", "details": { "limit": 100, "scope": "account" } } }
В первом ответе details.limit и details.window называют сработавший лимит, а details.retry_after — паузу в секундах. Во втором details.scope = "account" значит, что все места заняты заданиями аккаунта: дождитесь готовых и отправляйте новые реже. У слишком раннего опроса в details лежит min_interval_seconds.
Сколько запросов отправлять одновременно?
Предел задан двумя числами: на весь аккаунт не больше 100 заданий в очереди или в работе и не больше 120 запросов в минуту. Сколько из этого занимать, решаете вы, а правило простое: держите очередь у себя и запускайте новое задание, когда освободилось место, а не пачкой.
- Считайте на весь аккаунт. Если на нём работают несколько сервисов, лимит у них общий: назначьте каждому долю и оставьте запас на повторы.
- Освобождайте место по событию. Задание стало
completedилиfailed— запускайте следующее. Узнать об этом можно вебхуком, о нём ниже. - Учитывайте все запросы. В 120 запросов в минуту входят не только запуски: загрузка файлов, чтение каталога моделей и баланса тоже считаются.
- Распределяйте запуски по минуте. 120 запросов в минуту — в среднем два в секунду; серия запросов подряд быстро расходует окно.
Как повторять запрос после 429?
Правило такое: ждите не меньше Retry-After секунд (заголовок приходит в ответах 429 и 503), добавьте небольшую случайную задержку, чтобы процессы не повторяли запрос одновременно, и ограничьте число попыток. Сохраните тело запроса, ключ API и ключ повтора X-Idempotency-Key: повтор той же отправки тогда не создаст второе задание и второе списание.
- Новая картинка — новый ключ повтора. Повтор той же отправки берёт прежний ключ.
idempotency_in_flight. Первая отправка с этим ключом ещё обрабатывается: подождитеRetry-Afterи повторите с тем же ключом.- Опрос задания. Ждите не меньше большего из
details.min_interval_secondsиRetry-After: заголовок иногда указывает меньший интервал. burst_block. Остановите повторы, устраните причину отказов доступа (неверный ключ, отключённый аккаунт) и продолжайте только послеRetry-After.
Пример ниже повторяет отправку по этим правилам.
# Нужны bash, curl и uuidgen. Ключ повтора создаётся один раз на операцию. IDEMPOTENCY_KEY="$(uuidgen)" || exit 1 curl -i --max-time 30 \ -X POST https://api.rendergrid.io/ api/ public/ v1/ images/ generate \ -H "Authorization: Bearer $RENDERGRID_API_KEY" \ -H "Content-Type: application/json" \ -H "X-Idempotency-Key: $IDEMPOTENCY_KEY" \ -d '{ "model": "nano-banana-2.1", "prompt": "Пять деревянных кубиков в ровный ряд на светло-сером фоне", "resolution": "1K", "aspect_ratio": "1:1" }' # при ответе 429 прочитайте заголовок Retry-After, подождите # и повторите этот же запрос с тем же $IDEMPOTENCY_KEY
import os import random import time import uuid import requests API = "https://api.rendergrid.io/ api/ public/ v1" def submit_with_backoff(payload, idempotency_key, max_attempts=5): key = os.environ["RENDERGRID_API_KEY"] headers = { "Authorization": f"Bearer {key}", # один и тот же на все попытки "X-Idempotency-Key": idempotency_key, } for attempt in range(max_attempts): response = requests.post( f"{API}/images/generate", headers=headers, json=payload, timeout=(10, 120), ) if response.status_code != 429: return response try: body = response.json() except ValueError: body = {} details = body.get("error", {}).get("details", {}) last = attempt + 1 == max_attempts if details.get("reason") == "burst_block" or last: # блокировку и последнюю попытку отдаём наверх return response wait = int(response.headers.get("Retry-After", "2")) # ждём не меньше Retry-After time.sleep(max(0, wait) + random.uniform(0, 1)) payload = { "model": "nano-banana-2.1", "prompt": "Пять деревянных кубиков в ровный ряд на светло-сером фоне", "resolution": "1K", "aspect_ratio": "1:1", } response = submit_with_backoff(payload, str(uuid.uuid4())) print(response.status_code, response.text)
// Node.js 22+, файл .mjs import { randomUUID } from "node:crypto"; const API = "https://api.rendergrid.io/ api/ public/ v1"; const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); async function submitWithBackoff(payload, idempotencyKey, maxAttempts = 5) { for (let attempt = 0; attempt < maxAttempts; attempt++) { const response = await fetch(`${API}/images/generate`, { method: "POST", headers: { Authorization: `Bearer ${process.env.RENDERGRID_API_KEY}`, "Content-Type": "application/json", // один и тот же на все попытки "X-Idempotency-Key": idempotencyKey, }, body: JSON.stringify(payload), signal: AbortSignal.timeout(120_000), }); if (response.status !== 429) return response; const body = await response.clone().json().catch(() => ({})); const last = attempt + 1 === maxAttempts; if (body.error?.details?.reason === "burst_block" || last) { // блокировку и последнюю попытку отдаём наверх return response; } const wait = Number(response.headers.get("Retry-After") ?? 2); const delay = Math.max(0, wait); // ждём не меньше Retry-After await sleep((delay + Math.random()) * 1000); } } const response = await submitWithBackoff( { model: "nano-banana-2.1", prompt: "Пять деревянных кубиков в ровный ряд на светло-сером фоне", resolution: "1K", aspect_ratio: "1:1", }, randomUUID(), ); console.log(response.status, await response.text());
Пример повторяет только 429, остальные ответы отдаёт вашему коду, а при burst_block останавливается. Пять попыток — настройка примера, а не рекомендация: в других примерах документации их три.
Как опрашивать задание, чтобы не получить 429?
Одно задание одним ключом спрашивайте не чаще раза в 5 секунд и только до статуса completed или failed: на конечном статусе опрос прекращают. Слишком ранний опрос получит 429 ERR_RATE_LIMIT.
Ещё два правила из документации. Ограничьте общее время ожидания: если оно вышло, сохраните id и продолжите позже, тайм-аут вашего клиента задание не отменяет. Если опрашивают несколько процессов, согласуйте между ними общий интервал. Чтобы не опрашивать вовсе, подключите вебхук.
Как заменить частый опрос вебхуком?
Передайте webhook_url в каждом запросе генерации, для которого нужно уведомление. Когда задание станет completed или failed, мы пришлём на этот адрес подписанный POST с событием creation.completed или creation.failed. Адрес должен быть публичным HTTPS.
Что учесть в обработчике:
- Проверьте подпись. Это HMAC-SHA256 от строки из
X-Timestamp, точки и исходного тела; она лежит в заголовкеX-Webhook-Signature-256какsha256=и 64 шестнадцатеричных знака. Считайте её по исходным байтам, до разбора JSON. Окно свежести метки времени в примерах — 300 секунд: это правило получателя. - Отвечайте быстро. Любой ответ
2xxзначит «получили»; мы ждём ответа 10 секунд. Сначала сохраните событие или поставьте его в очередь, скачивание картинок оставьте на потом. - Готовьтесь к повторам. Если ответа нет или он не
2xx, мы повторяем доставку: по умолчанию до пяти попыток, через 60, 120, 240 и 480 секунд после предыдущей. Выполняйте действие один раз на пару(id, event). - Держите опрос про запас. Доставка не гарантируется: если вебхук не пришёл, спросите задание методом
GET /creations/{id}.
Вебхук убирает опрос, но остальные лимиты остаются: запуски и число заданий в работе считаются как прежде. Про очередь на своей стороне, вебхуки и ключ идемпотентности подробнее — в статье «Генерация пачками: очередь, вебхуки и ключ идемпотентности» и в разделе «Вебхуки» документации.
Что приходит, когда задание готово?
Когда задание дошло до completed, в result_urls лежат ссылки на готовые файлы. Файлы хранятся сутки, поэтому скачивайте их сразу: опрос срок хранения не продлевает.
Ниже три обычных результата Nano Banana 2.1 в 1K: по одной попытке на каждый, с промптами. Это успешные результаты, а не снимки ошибки. Ошибку 429 они не показывают, а время и нагрузку при их создании мы не измеряли.
Промпт: пять кубиков в ровный ряд
Пять одинаковых деревянных кубиков стоят в прямой горизонтальной линии на светло-сером фоне. Расстояния между кубиками одинаковые, передние грани выровнены. Камера чуть выше уровня предметов, мягкий свет слева. Все кубики целиком, ровные тени и широкие поля. Без надписей, логотипов, людей и водяных знаков.
Nano Banana 2.1
nano-banana-2.1 · 1K · 1:1 · $0,02
Nano Banana 2.1, 1K, 1:1, одна попытка. Обычный успешный результат, а не снимок ошибки. Кубики светлые, у каждого свой рисунок древесины, а на передней грани среднего он перекрёстный. В кадре крайние кубики чуть шире средних, а просветы между крайними и их соседями на глаз уже, чем между тремя средними. Поля сверху и снизу шире боковых. Надписей нет.
Промпт: девять чашек сеткой три на три
Девять маленьких белых керамических чашек без ручек расположены сеткой три на три. Вид строго сверху, интервалы ровные, отверстия чашек одинакового размера. Матовый бежевый фон, мягкое равномерное освещение, тонкие тени под чашками и свободная рамка вокруг всей группы. Без надписей, логотипов, людей и водяных знаков.
Nano Banana 2.1
nano-banana-2.1 · 1K · 1:1 · $0,02
Nano Banana 2.1, 1K, 1:1, одна попытка. Обычный успешный результат, а не снимок ошибки. Сетка ровная, интервалы между чашками близки, вокруг группы свободное поле. На этом виде чашки похожи на неглубокие пиалы, ручек у них нет. Фон бежевый, с мелкой бумажной фактурой. Надписей нет.
Промпт: пять баночек дугой
Пять одинаковых закрытых баночек кремового цвета образуют плавную дугу на песочном фоне. Вид немного сверху, расстояния между банками равные. Мягкий свет справа, гладкие круглые крышки и отдельные тени. Не обрезайте ни одну банку, оставьте открытый центр дуги. Без надписей, логотипов, людей и водяных знаков.
Nano Banana 2.1
nano-banana-2.1 · 1K · 1:1 · $0,02
Nano Banana 2.1, 1K, 1:1, одна попытка. Обычный успешный результат, а не снимок ошибки. Баночки не обрезаны. Две крайние ближе к камере и крупнее трёх остальных, а тень левой крайней уходит за край кадра. Баночки и песок близки по цвету, контраст невысокий. Фон — песок с волнистой фактурой. Надписей нет.
Где эти советы не помогут?
- Лимиты общие для аккаунта. Другие ключи и сервисы расходуют те же 120 запросов и 100 мест, а очередь одного сервиса не видит, сколько заняли другие: заложите запас и договоритесь о долях.
- Блокировка за ошибки доступа — не про частоту. При
burst_blockодной паузы мало: сначала устраните причину отказов. - Готового расписания повторов нет. Документация задаёт правило: не меньше
Retry-Afterи случайная добавка. Примеры в ней берут три или пять попыток, а число для вашего сервиса выбираете вы. - Рекомендованного числа заданий в работе тоже нет. Предел — 100 на аккаунт, а сколько занимать, зависит от ваших процессов.
- Три кадра статьи ничего не измеряют. Это обычные успешные результаты: по ним нельзя судить ни о скорости, ни о нагрузке.
Неудачный запрос бесплатен
Проверьте запрос в песочнице
Соберите запрос на странице Nano Banana 2.1 без кода, а потом возьмите его код одной кнопкой. Лимиты аккаунта и коды ответов описаны в документации.
Попробовать модельВопросы и ответы
Что значит ошибка 429?
error.code и error.details. Подождите Retry-After секунд и повторите запрос.Сколько запросов можно отправлять одновременно?
Через сколько повторять запрос после 429?
Retry-After секунд из заголовка ответа. Добавьте небольшую случайную задержку, ограничьте число попыток и отправляйте то же тело с тем же X-Idempotency-Key.Как часто можно спрашивать статус задания?
completed или failed. Чтобы не опрашивать вовсе, передайте webhook_url.Увеличат ли лимит дополнительные ключи?
Платят ли за запрос, на который пришёл 429?
Источники
- Документация Rendergrid: лимитыПределы по умолчанию, примеры ответов
429и повтор с паузой. - Документация Rendergrid: коды ошибокФормат ошибки, коды
ERR_RATE_LIMITиERR_QUOTA_EXCEEDED, заголовокRetry-After. - Документация Rendergrid: задачи и статусыКак часто опрашивать задание и сколько хранятся файлы.
- Документация Rendergrid: вебхукиПодпись, повторы доставки и время ответа.
- Документация Rendergrid: повтор без двойного списанияКлюч повтора и повтор после
429.





