Коды ошибок API для AI-видео: что они значат и когда повторять запрос
Задачи генерации видео завершаются ошибкой чаще, чем обычные вызовы API: модели бывают заняты, отклоняют промпты или не укладываются в тайм-аут. В этом руководстве перечислены все ошибки, которые вы можете получить, какие из них стоит повторять и как делать это безопасно.
Два места, где может возникнуть ошибка
Задача генерации видео или изображения может завершиться неудачей в два момента, и ваш код должен обрабатывать оба:
- При отправке.
POST /v1/generationsотвечает HTTP-статусом ошибки и телом вида{"error": {"code": "...", "message": "..."}}. Ничего не запущено, ничего не списано. - Во время выполнения. Отправка прошла успешно и вернула
202, но позже задача завершается со статусомstatus: "failed". В еёerrorестьcode,message, который можно показать пользователю, сделавшему запрос, иretryable.
Неудавшаяся задача не тарифицируется ни на одной модели. Стоимость резервируется на вашем балансе при отправке и освобождается, если задача завершилась ошибкой.
HTTP-ошибки при отправке
| Статус | Код | Значение | Повторять? |
|---|---|---|---|
| 400 | invalid_request | Тело запроса не в формате JSON, в нём нет model или input, либо есть поле, которое API не знает. | Нет. Исправьте тело запроса. |
| 401 | unauthorized | API-ключ отсутствует или неверен. | Нет. Проверьте ключ. |
| 402 | insufficient_credits | Баланса не хватает на стоимость, зарезервированную для задачи. | После пополнения. |
| 403 | account_disabled, generation_not_enabled | Аккаунт приостановлен, или ключ позволяет смотреть цены, но не запускать генерацию. | Нет. Напишите в поддержку. |
| 404 | not_found | На вашем аккаунте нет генерации с таким id. | Нет. |
| 409 | idempotency_conflict | Idempotency-Key уже использовался с другим телом запроса. | С новым ключом. |
| 409 | not_ready | Вы запросили файл до того, как задача успешно завершилась. | Да, после успешного завершения. |
| 413 | body_too_large | JSON-тело запроса больше 1 МБ. Загружайте файлы и отправляйте ссылки. | Нет. Уменьшите тело запроса. |
| 422 | invalid_input | Значение, которое модель не принимает, например длительность или размер, которых нет в её списке. В сообщении указано, какое именно. | Нет. Измените значение. |
| 422 | max_cost_exceeded | Любой маршрут стоит больше, чем ваш max_cost_usd. | Нет. Увеличьте лимит или снизьте настройки. |
| 422 | model_not_available | Модели с таким id нет. | Нет. Проверьте id. |
| 429 | rate_limited | Слишком много запросов за одну секунду с одного аккаунта. | Да, через Retry-After. |
| 429 | concurrency_limit | На аккаунте уже запущено максимальное число задач. | Да, когда одна из них завершится. |
| 500 | server_error | Что-то сломалось на нашей стороне. | Да, с тем же Idempotency-Key. |
| 503 | busy | Кратковременная нагрузка. | Да, через Retry-After, с тем же ключом. |
| 503 | model_unavailable | Сейчас нет доступного маршрута для этой модели. | Да, чуть позже. |
Практическое правило: 429, 500 и 503 стоит повторять с нарастающей задержкой; всё из диапазона 4xx, кроме 429, означает, что нужно изменить сам запрос.
Почему задача завершается ошибкой после запуска
| error.code | retryable | Что произошло |
|---|---|---|
content_policy | no | Модель отклонила промпт или медиафайл либо не отдала результат. reason объясняет причину: sexual, minors, copyright, real_person или other. |
invalid_input | no | Референс не удалось прочитать, или модель отклонила значение. В сообщении указано, какое именно. |
model_declined | no | Модель ответила словами вместо того, чтобы создать изображение или видео. Опишите в промпте сам результат. |
reference_changed | no | Файл-референс изменился после проверки. Отправьте его снова. |
max_cost_exceeded | no | Единственный доступный для запуска маршрут стоит больше вашего лимита. |
model_busy | yes | Все маршруты были перегружены. Повторите через минуту. |
model_unavailable | yes | Сейчас ни один маршрут не может принять задачу. |
timeout | yes | Ни один маршрут не вернул результат вовремя, либо истёк ваш max_wait_s. |
all_routes_failed | yes | На каждом маршруте произошёл сбой с его стороны. |
Если задача попадает на занятый или неисправный маршрут, она сама переходит на следующий, так что к моменту, когда вы видите model_busy или all_routes_failed, все маршруты уже опробованы. Поле attempts показывает, сколько их было. Обычно достаточно подождать минуту-две и повторить.
Отказы по содержанию
content_policy — самая частая ошибка. Решает её модель, а не API, и reason показывает, какое правило сработало. Типичные причины: имя или лицо известного человека в промпте или референсе (real_person), бренды и известные персонажи (copyright), а также всё, что выглядит как изображение несовершеннолетнего. Повтор того же запроса не поможет; переформулируйте промпт или замените изображение. Модели различаются по строгости, поэтому один и тот же промпт может пройти на одной и быть отклонён на другой.
Цикл повторов, который делает всё правильно
Эта функция на Python повторяет то, что стоит повторять, и останавливается на том, что не стоит. Важны две детали:
- Повторная отправка после
503или обрыва соединения использует тот жеIdempotency-Key, поэтому она никогда не запустит вторую платную задачу. - Повтор после того, как задача завершилась ошибкой, использует новый ключ. Тот же ключ просто вернул бы неудавшуюся задачу.
import os
import time
import uuid
import requests
API = "https://api.revana.si/v1"
AUTH = {"Authorization": f"Bearer {os.environ['REVANA_API_KEY']}"}
RETRY_HTTP = {429, 500, 503}
class GenerationError(Exception):
pass
def submit(body):
"""POST a job. Retries 429, 500 and 503 with the same Idempotency-Key, so a retry never starts a second job."""
headers = {**AUTH, "Idempotency-Key": str(uuid.uuid4())}
for attempt in range(6):
r = requests.post(f"{API}/generations", headers=headers, json=body, timeout=60)
if r.ok:
return r.json()
if r.status_code in RETRY_HTTP:
time.sleep(float(r.headers.get("Retry-After", 2 ** attempt)))
continue
err = r.json()["error"] # 400, 401, 402, 403, 409, 413, 422: fix the request, do not retry
raise GenerationError(f"{r.status_code} {err['code']}: {err['message']}")
raise GenerationError("still busy after 6 tries")
def wait(job):
while job["status"] in ("queued", "running"):
time.sleep(5)
job = requests.get(f"{API}/generations/{job['id']}", headers=AUTH).json()
return job
def generate(body, tries=3):
for n in range(tries):
job = wait(submit(body)) # each call uses a new key: a failed job is final
if job["status"] == "succeeded":
return job
err = job["error"]
if not err["retryable"]:
raise GenerationError(f"{err['code']}: {err['message']}")
time.sleep(60 * (n + 1)) # model_busy, timeout and the like: wait, then try again
raise GenerationError(f"gave up after {tries} tries: {err['code']}")
job = generate({
"model": "seedance-2.0-fast",
"input": {"prompt": "Waves roll onto black sand at sunset.", "resolution": "480p", "duration": 5},
"max_cost_usd": 0.3,
})
print(job["output"]["urls"][0], job["cost_usd"])Пример задачи — 5-секундный клип Seedance 2.0 Fast в 480p, который стоит $0.25 при успехе и ничего при ошибке. Лимит max_cost_usd не даст неверной настройке обойтись дороже ожидаемого.
Как избежать ошибок заранее
- Прочитайте параметры до отправки.
GET /v1/modelsи страница моделей перечисляют размеры, длительности и лимиты референсов каждой модели. Значение за их пределами сразу отклоняется с ошибкой422 invalid_input. - Загружайте файлы, а не встраивайте их.
POST /v1/uploadsпринимает изображения до 30 МБ, видео до 50 МБ и звук до 15 МБ и возвращает ссылку, действующую 14 дней, так что тело запроса остаётся небольшим. - Проверяйте баланс в коде.
GET /v1/balanceперед большой партией избавит от серии ошибок402. Пакеты кредитов — на странице цен.
О том, как снизить стоимость успешных задач, читайте в статье как сократить расходы на API для AI-видео. Полный справочник ошибок — в документации; получите API-ключ, чтобы попробовать.
Частые вопросы
Спишутся ли деньги, если генерация видео завершилась ошибкой?
Нет. Стоимость резервируется при отправке задачи и освобождается, если задача завершилась ошибкой, независимо от причины.
Какие ошибки стоит повторять?
При отправке повторяйте 429, 500 и 503 после задержки из Retry-After. Если задача завершилась ошибкой, повторяйте, когда error.retryable равно true: model_busy, model_unavailable, timeout и all_routes_failed.
Что означает content_policy?
Модель отклонила промпт или медиафайл либо не отдала то, что создала. Поле reason указывает правило: sexual, minors, copyright, real_person или other. Измените запрос, а не повторяйте его.
Как повторить запрос, не заплатив дважды?
Отправьте заголовок Idempotency-Key и используйте тот же ключ при повторной отправке после таймаута или ответа 503. Вы получите ту же генерацию, а не вторую.
Что означает 422 invalid_input?
Модель не принимает отправленное вами значение, например длительность, размер или число референсных изображений. В сообщении указано поле и допустимые значения.
Стройте на API, который никогда не списывает деньги за неудачу
Один API-ключ для всех моделей видео и изображений с автоматическим переходом между маршрутами. Предоплаченные кредиты от $10, списание только за успешные задачи.