NezhaGateNezhaGate

Как отправлять задачи изображений и видео, опрашивать их или получать результат вебхуком, скачивать результат, что означает каждый статус и поле и как устроены возвраты.

Какие эндпоинты асинхронные

Генерация изображений и видео выполняется асинхронными задачами: вызов сразу возвращает id задачи, а сама работа идёт в фоне. Чат синхронный и задач не использует.

МетодПутьЧто делает
POST/v1/images/generationsИзображение из текста. Сразу возвращает id задачи (HTTP 202).
POST/v1/images/editsИзображение из изображения. То же самое делает image в предыдущем вызове.
GET/v1/images/jobs/{id}Статус и результат задачи изображения.
POST/v1/videos/generationsСоздание видео. Сразу возвращает id задачи (HTTP 202).
GET/v1/videos/jobs/{id}Статус и результат видеозадачи.

Как это работает

  1. Отправка: вызовите эндпоинт генерации — сразу придёт HTTP 202 с id задачи (вида img_...; у видеозадач тот же префикс).
  2. Ожидание: опрашивайте эндпоинт задачи по этому id или добавьте при отправке callback_url — тогда результат придёт к вам сам.
  3. Готово: status становится succeeded, ссылка на результат — в data[0].url; если статус стал failed, причина указана в error, а зарезервированные под задачу кредиты уже вернулись на баланс.
  4. Скачивание: ссылки на результаты хранятся 60 дней; всё, что нужно сохранить, скопируйте в своё хранилище.
curl · Отправить задачу
curl https://nezhagate.com/v1/videos/generations \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "seedance-2.0", "prompt": "a paper boat drifting down a rainy street", "duration": 5, "size": "16:9"}'

# HTTP 202
{"id": "img_9c1e...7a", "object": "video.generation.job", "status": "queued", "created": 1791199400, "model": "seedance-2.0"}
curl · Опросить задачу
curl https://nezhagate.com/v1/videos/jobs/img_9c1e...7a -H "Authorization: Bearer YOUR_API_KEY"

Статусы задачи

СтатусЧто значит
queuedВ очереди. В ответе есть queue_position (сколько задач впереди) и eta_seconds (ожидаемое время ожидания).
processingГенерируется.
succeededГотово. Результат в data[0].url, детали оплаты в usage.
failedОшибка. Причина в error; зарезервированные кредиты возвращаются полностью.

succeeded и failed — финальные статусы: после них задача больше не меняется.

Поля ответа

ПолеЧто значит
idID задачи: по нему опрашивают задачу, он же приходит в вебхуках.
objectimage.generation.job для изображений, video.generation.job для видео.
statusqueued / processing / succeeded / failed.
createdВремя отправки задачи в секундах Unix.
modelID модели, указанный при отправке.
queue_positionТолько в статусе queued: сколько задач впереди вашей.
eta_secondsТолько в статусе queued: примерно через сколько секунд задача начнётся.
data[].urlТолько в статусе succeeded: ссылка на результат (изображение или mp4), хранится 60 дней.
usageТолько в статусе succeeded: что оплачено. Для изображений — images (сколько изображений выдано) и resolution, для видео — clips.
errorТолько в статусе failed: message, type, code и param; ориентируйтесь на code.
Ответ по готовности · video
{
  "id": "img_9c1e...7a",
  "object": "video.generation.job",
  "status": "succeeded",
  "created": 1791199400,
  "model": "seedance-2.0",
  "data": [{"url": "https://img.nezhagate.com/i/5d41402abc....mp4"}],
  "usage": {"clips": 1, "model": "seedance-2.0"}
}
failed
{
  "id": "img_9c1e...7a",
  "object": "video.generation.job",
  "status": "failed",
  "created": 1791199400,
  "model": "seedance-2.0",
  "error": {"message": "...", "type": "invalid_request_error", "code": "content_policy", "param": null}
}

Советы по опросу

  • Опрашивайте изображения каждые 2–3 секунды, видео — каждые 5–10 секунд. Опрос бесплатный.
  • Прекращайте опрос, как только увидите succeeded или failed.
  • Задайте в программе общий лимит ожидания: изображения обычно готовы за время от нескольких десятков секунд до нескольких минут, видео — от одной-двух минут до часа с лишним (длинные ролики и очередь увеличивают время); типичное время указано в документации каждой модели. Когда лимит истёк, не отправляйте задачу заново: она никуда не делась, продолжите опрос позже.
  • Задача принадлежит аккаунту, который её отправил: прочитать её может любой ключ этого аккаунта, а любой другой аккаунт получит 404 job_not_found. Записи о задачах хранятся 3 дня; после этого по id задачи тоже вернётся 404, а ссылка на результат продолжит работать до конца своих 60 дней.
  • Если отправка оборвалась по таймауту на вашей стороне и id задачи вы не получили, перед повторной отправкой проверьте в журнале запросов в консоли, создана ли задача, — так вы не заплатите дважды.

Вебхуки вместо опроса

Добавьте при отправке callback_url — как только задача завершится, успешно или с ошибкой, шлюз отправит на этот URL ровно то же, что возвращает эндпоинт опроса; запрос подписан, а при неудачной доставке повторяется. Общий адрес для всего аккаунта можно также задать в консоли, в разделе «Настройки → Вебхуки».

JSON · callback_url
{"model": "seedance-2.0", "prompt": "...", "duration": 5, "callback_url": "https://example.com/webhooks/nezhagate"}

Документация по вебхукам →

Оплата

  • При отправке цена задачи резервируется на балансе; если средств не хватает, вернётся 402, и задача не будет создана.
  • При успехе списывается то, что выдано: за изображения — по числу выданных картинок, за видео — по заказанной длительности или ролику (столько же, сколько было зарезервировано); всё, что зарезервировано сверх этого, возвращается автоматически. При неудаче резерв возвращается полностью.
  • Опрос и доставка вебхуков бесплатны.

Сколько хранятся результаты

data[0].url ведёт в наше хранилище; через 60 дней файл удаляется автоматически. Чтобы пользоваться результатом дольше, скачайте его в своё хранилище или CDN.

Почему задача завершается ошибкой

За неудачную задачу деньги всегда возвращаются полностью. Вот коды, которые чаще всего встречаются у неудачных задач:

codeКогда возникаетЧто делать
content_policyВидео: промпт, референс или сгенерированный видеоряд либо звук не прошли проверку контента (если отклонён звук, param равен audio).Перепишите промпт или замените референс и только потом отправляйте снова: те же входные данные будут отклонены повторно.
moderation_blockedИзображение: система безопасности поставщика модели отклонила промпт или референсное изображение (код может быть и content_policy_violation).Перепишите промпт или замените референс; повторная отправка без изменений обычно снова отклоняется.
render_failedВидео: на этот раз ролик не получился; обычно это разовый сбой.Повторная отправка того же запроса обычно проходит успешно.
invalid_materialВидео: поставщик обнаружил проблему во входных данных. Это общая ошибка, и часто она разовая.Сначала отправьте задачу ещё раз без изменений; если ошибка повторяется, проверьте референсы и длину промпта.
render_timeoutВидео: очередь или рендер заняли слишком много времени, и поставщик отказался от задачи.Отправьте задачу заново.
wait_timeoutВидео: ролик не пришёл за максимальное время ожидания, которое допускает линия.Отправьте задачу заново.
upstream_unstableВидео: во время рендера оборвалось соединение.Отправьте задачу заново.
result_fetch_failedВидео создано, но на нашей стороне его так и не удалось скачать или сохранить, несмотря на повторные попытки.Отправьте задачу заново.
invalid_requestВидео: линия не принимает такое сочетание параметров (например, длительности, пропорции и разрешения).Измените параметры согласно message и отправьте снова.
upstream_errorЛюбая другая ошибка: перепробованы все линии, задача ждала 15 минут и так и не запустилась, поставщик отказал без объяснения причины и т. п.; что именно произошло, указано в message.Обычно повторная отправка проходит успешно.

Полный список кодов ошибок →

Полный пример

Отправка, опрос и скачивание в одном скрипте, с общим лимитом ожидания и обработкой ошибок. Замените YOUR_API_KEY и запускайте.

Python
import time
import requests

BASE = "https://nezhagate.com/v1"
HEADERS = {"Authorization": "Bearer YOUR_API_KEY"}


def run_job(kind, payload, every=5, give_up=5400):
    """kind is "images" or "videos". Returns the finished job (succeeded or failed)."""
    job = requests.post(f"{BASE}/{kind}/generations", headers=HEADERS, json=payload, timeout=120)
    job.raise_for_status()                       # 4xx here: fix the request; nothing was billed
    job_id = job.json()["id"]
    deadline = time.time() + give_up
    while time.time() < deadline:
        time.sleep(every)
        r = requests.get(f"{BASE}/{kind}/jobs/{job_id}", headers=HEADERS, timeout=30).json()
        if r["status"] in ("succeeded", "failed"):
            return r
    raise TimeoutError(f"{job_id} still running; poll it again later, it is not lost")


job = run_job("videos", {"model": "seedance-2.0", "prompt": "a paper boat drifting down a rainy street",
                         "duration": 5, "size": "16:9"})
if job["status"] == "succeeded":
    url = job["data"][0]["url"]
    with open("clip.mp4", "wb") as f:            # keep your own copy: links expire after 60 days
        f.write(requests.get(url, timeout=300).content)
else:
    print(job["error"]["code"], job["error"]["message"])