Ошибки
Все бизнес-коды, что они значат и какие ошибки имеет смысл повторять.
Если вызов не удался, ответ содержит число (code), которое говорит, что это за проблема, и фразу (msg) для человека. Ветвитесь по числу, а фразу показывайте или пишите в лог.
Обёртка ответа
{ "code": 40201, "msg": "Insufficient balance", "request_id": "req_…" }code— бизнес-код.200означает успех, всё остальное перечислено ниже.- При ошибке поля
dataнет — кроме нескольких ошибок библиотеки ассетов, которые добавляютdata.reason. - HTTP-статус тоже выставляется корректно (
40201приходит с HTTP402). Ветвитесь по чему-то одному, не смешивая; мы советуем более точныйcode. request_idдублируется в заголовкеX-Request-Id. Указывайте его при обращении в поддержку.
Если ответ вообще не JSON — например, HTML или текстовая страница от промежуточной сети, — обрабатывайте его по HTTP-статусу и пишите в лог начало тела.
Язык сообщений об ошибках
msg и errorMessage упавшей задачи по умолчанию на английском. Отправьте Accept-Language со значением en, ja, ko, de, fr, es, pt-BR, ru, it или pl либо выберите язык ошибок API в консоли — и получите их на этом языке; для остальных языков вернётся английский. В ответах с ошибкой есть заголовок Content-Language. Коды от языка не зависят — ещё одна причина никогда не разбирать msg.
Если модель сообщила конкретную причину сбоя (например, нечитаемое референсное изображение), errorMessage передаёт её на английском, без внутренних подробностей.
Общие коды
code | HTTP | Значение | Повторять? |
|---|---|---|---|
200 | 200 / 202 | Успех | — |
400 | 400 | Запрос или его input некорректны; msg называет проблему | Нет — исправьте |
40003 | 400 | Загруженный файл не совпадает со своим тикетом (размер, тип или содержимое) | Нет — новый тикет, новая загрузка |
40004 | 400 | Запрос корректен, но модель сейчас не может выполнить такое сочетание настроек | Нет — измените настройку, названную в msg |
401 | 401 | Ключа нет, он неизвестен или отозван (подробнее) | Нет |
403 | 403 | Нет доступа (см. Права) | Нет |
404 | 404 | Не существует или принадлежит другому ключу | Нет |
409 | 409 | Конфликт с текущим состоянием (когда повторять) | Иногда |
40901 | 409 | Расчёт цены устарел или цена изменилась | Запросите расчёт заново |
413 | 413 | Тело запроса слишком большое | Нет — загружайте файлы, а не встраивайте их |
429 | 429 | Слишком много запросов | Да — после Retry-After |
500 | 500 | Что-то сломалось у нас | Да — с растущей паузой |
503 | 503 | Вспомогательный сервис ненадолго недоступен | Да — после Retry-After |
Баланс и лимиты
code | HTTP | Значение | Что делать |
|---|---|---|---|
40201 | 402 | Не хватает кредитов на резерв для этой задачи | Купите кредиты и отправьте снова |
40202 | 402 | Достигнут лимит расходов ключа | Поднимите лимит в API-консоли или дождитесь сброса |
Права
code | HTTP | Значение | Что делать |
|---|---|---|---|
40301 | 403 | Этому ключу нельзя вызывать эту модель | Добавьте модель в список разрешённых для ключа |
40302 | 403 | Запрос пришёл с адреса вне белого списка IP ключа | Добавьте адрес или снимите ограничение |
40303 | 403 | В этом регионе сервис не предоставляется | Обойти нельзя |
40310 | 403 | Адрес почты аккаунта ещё не подтверждён | Откройте ссылку из письма о регистрации; ключ заработает сразу после подтверждения |
Простой 403 при действующем ключе означает, что аккаунт сейчас не может пользоваться API; напишите в поддержку и приложите request_id.
Доступность моделей
code | HTTP | Значение | Повторять? |
|---|---|---|---|
50301 | 503 | Модель сейчас не принимает задачи | Да, позже — или выберите другую модель |
40004 | 400 | Модель работает, но не с такими настройками | Нет — измените настройку |
Ни один из этих кодов не создаёт задачу и не резервирует кредиты.
Коды сбоя задач
У принятой, но затем упавшей задачи state: "failed", а в errorCode — одно из значений ниже. Список закрыт: любое другое значение обрабатывайте как upstream_failed.
errorCode | Значение | Повторять? |
|---|---|---|
invalid_request | Модель не приняла параметры | Нет — исправьте их |
unsupported_combination | Такое сочетание настроек сейчас не поддерживается | Нет — измените настройку |
content_rejected | Модель отклонила этот контент | Нет — измените промпт или референс |
invalid_asset | Референсный файл не удалось прочитать или он больше недоступен | Загрузите заново и создайте новую задачу |
rate_limited | Сервис генерации был перегружен | Да, с растущей паузой |
upstream_unavailable | Генерация временно недоступна | Да, чуть позже |
generation_failed | Генерация прошла, но ничего не выдала | Возможно — тот же вход часто падает так же |
timeout | У задачи кончилось время | Возможно — попробуйте короче или в меньшем разрешении |
upstream_failed | Всё остальное | Да, с растущей паузой |
Упавшая задача освобождает резерв, поэтому ничего не стоит (несколько моделей берут плату за отказ по содержанию и пишут об этом на своей странице). errorMessage объясняет причину словами; ветвитесь только по errorCode.
Когда стоит повторять 409
| Случай | Повторять? |
|---|---|
Два запроса с одним Idempotency-Key пришли одновременно | Да, с тем же ключом после короткой паузы |
Idempotency-Key уже использован для другого запроса или другим ключом | Нет |
| Загрузка или результат ещё обрабатываются | Да, через несколько секунд |
jobs/retry для задачи, которая ещё не завершилась | Да, когда она станет конечной |
jobs/retry для успешной задачи | Нет — создайте новую задачу |
| Тикет загрузки или загруженный файл истёк | Нет — загрузите заново |
Как писать повтор
Повторяйте только то, что может получиться со второй попытки, ждите Retry-After, если он есть, а иначе увеличивайте паузу экспоненциально:
const RETRYABLE = new Set([429, 500, 503, 50301]);
async function call(path, init, attempts = 4) {
for (let i = 0; i < attempts; i++) {
const res = await fetch(`https://api.popnow.ai/api/v1${path}`, init);
if (!res.headers.get('content-type')?.includes('json')) {
throw new Error(`HTTP ${res.status}: ${(await res.text()).slice(0, 200)}`);
}
const body = await res.json();
if (body.code === 200) return body.data;
if (!RETRYABLE.has(body.code)) throw new Error(`${body.code} ${body.msg} (${body.request_id})`);
const wait = Number(res.headers.get('retry-after')) * 1000 || 2 ** i * 1000 + Math.random() * 500;
await new Promise((r) => setTimeout(r, wait));
}
throw new Error('out of retries');
}import random, time, requests
RETRYABLE = {429, 500, 503, 50301}
def call(method, path, attempts=4, **kwargs):
for i in range(attempts):
res = requests.request(method, f"https://api.popnow.ai/api/v1{path}", timeout=60, **kwargs)
try:
body = res.json()
except ValueError:
raise RuntimeError(f"HTTP {res.status_code}: {res.text[:200]}")
if body["code"] == 200:
return body["data"]
if body["code"] not in RETRYABLE:
raise RuntimeError(f'{body["code"]} {body["msg"]} ({body.get("request_id")})')
retry_after = res.headers.get("retry-after")
time.sleep(float(retry_after) if retry_after else 2**i + random.random() / 2)
raise RuntimeError("out of retries")Повторяя createTask, всегда отправляйте тот же Idempotency-Key, чтобы запрос, который на самом деле прошёл, не выполнился и не списался дважды.
Отладка
- Запишите
request_idиcode. - Найдите задачу в логах API-консоли по ID задачи или
request_id. - Не помогло? Напишите в поддержку:
request_id, время (UTC) иcode. Никогда не отправляйте свой API-ключ.

