Документация

Как устроена задача

Изображение, видео и речь делаются от десятков секунд до нескольких минут — держать всё это время открытым соединение нечем. Поэтому генерация оформляется задачей, и путь всегда один и тот же из трёх шагов: поставить задачу, дождаться её, забрать файл. Так же устроен вызов у поставщика, так что код, написанный под его createTask, работает у нас сменой адреса и ключа.

POST https://api.gen202.com/api/v1/jobs/createTask
GET https://api.gen202.com/api/v1/jobs/recordInfo?taskId=…

Генерация занимает десятки секунд, поэтому изображение не приходит в ответ на запрос. Первый вызов ставит задачу и сразу отвечает её номером, второй по этому номеру отдаёт состояние задачи, а когда она готова — ссылку на результат. Ключ передаётся в обоих запросах заголовком Authorization: Bearer sk-… и выпускается в кабинете. На один ключ шлюз пропускает 30 постановок задачи и 300 запросов состояния в минуту.

Незавершённых задач на аккаунте может быть не больше 50 — свыше этого числа постановка отвечает 429.

Пример вызова

task.sh
# 1. Поставить задачу — в ответе придёт её номер
TASK=$(curl -s https://api.gen202.com/api/v1/jobs/createTask \
  -H "Authorization: Bearer sk-ваш-ключ" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "nano-banana/2-1",
    "input": {
      "prompt": "Рыжий кот в скафандре на фоне Земли. Сцена из фильма",
      "aspect_ratio": "1:1",
      "resolution": "1K"
    }
  }' | jq -r '.data.taskId')

# 2. Спрашивать состояние, пока задача не закончится: waiting, queuing и
#    generating означают «работа идёт», success и fail — окончательные
while true; do
  RECORD=$(curl -s "https://api.gen202.com/api/v1/jobs/recordInfo?taskId=$TASK" \
    -H "Authorization: Bearer sk-ваш-ключ")
  STATE=$(echo "$RECORD" | jq -r '.data.state')
  case "$STATE" in success|fail) break;; esac
  sleep 3
done

# 3. Забрать результат. Ссылка на файл лежит в resultJson — это строка JSON
#    внутри JSON, поэтому её разбирают вторым разбором (fromjson)
echo "$RECORD" | jq -r '.data |
  if .state == "fail"
  then "отказ \(.failCode): \(.failMsg)"
  else .resultJson | fromjson | .resultUrls[0]
  end'
task.py
import json
import time

import requests

headers = {"Authorization": "Bearer sk-ваш-ключ"}

# 1. Поставить задачу — в ответе придёт её номер
created = requests.post(
    "https://api.gen202.com/api/v1/jobs/createTask",
    headers=headers,
    json={
      "model": "nano-banana/2-1",
      "input": {
        "prompt": "Рыжий кот в скафандре на фоне Земли. Сцена из фильма",
        "aspect_ratio": "1:1",
        "resolution": "1K"
      }
    },
).json()
task_id = created["data"]["taskId"]

# 2. Спрашивать состояние, пока задача не закончится: waiting, queuing и
#    generating означают «работа идёт», success и fail — окончательные
while True:
    task = requests.get(
        "https://api.gen202.com/api/v1/jobs/recordInfo",
        headers=headers,
        params={"taskId": task_id},
    ).json()["data"]
    if task["state"] in ("success", "fail"):
        break
    time.sleep(3)

# 3. Разобрать итог. У неудачи причина в failCode и failMsg
if task["state"] == "fail":
    raise SystemExit("отказ " + task["failCode"] + ": " + task["failMsg"])

# resultJson — строка JSON внутри JSON, поэтому разбор второй
result = json.loads(task["resultJson"])
print(result["resultUrls"][0])
print("списано кредитов:", task["creditsConsumed"])

Пример проходит весь путь: ставит задачу, повторяет запрос состояния раз в 3 секунды, пока задача не закончится, и разбирает ответ до ссылки на файл. Разборов два, потому что поле resultJson — строка JSON внутри JSON. В теле первого запроса стоят обязательные поля модели и значения из её перечислений, поэтому он проходит проверку шлюза как есть. Подставить остаётся только свой ключ. Пример на оболочке разбирает ответ через jq; на Python своего ничего не нужно, кроме requests.

Состояния задачи

Состояние лежит в поле state ответа /api/v1/jobs/recordInfo. Первые три означают, что работа идёт: запрос надо повторить примерно через 3 секунды — чаще спрашивать нечего, столько же ждёт между опросами сам шлюз. Два последних состояния окончательные: после них задача не меняется.

stateЧто происходит
waiting Задача принята и стоит в очереди шлюза; поставщику она ещё не отправлена.
queuing Поставщик задачу принял и поставил в свою очередь.
generating Генерация идёт.
success Готово: ссылки на результат лежат в resultJson, в creditsConsumed — сколько списано.
fail Задача не удалась: причина в failCode и failMsg, удержанные кредиты возвращены целиком.

Задача идёт, пока модель не пришлёт результат или отказ. Причина неудачи приходит двумя полями: failCode из таблицы ниже и failMsg словами.

failCodeЧто произошло
501 Поставщик вернул отказ: генерация не удалась.
408 Модель не приняла задачу за 15 минут или не отвечала сутки.
404 Поставщик не знает такой задачи.
429 Поставщик отбил создание задачи по своему лимиту.
500 Поломка на нашей стороне; подробности остаются в журнале шлюза.

Слова модели в failMsg бывают непонятны, поэтому к части неудач шлюз добавляет поле failComment — наш комментарий по-русски о том, что произошло. У остальных неудач это поле пустое.

failMsg, примерfailComment
Image fetch failed. Модель не смогла скачать файл из нашего хранилища. Вероятно, это разовый сбой, повторите запрос. Если ошибка повторится — напишите в поддержку: hi@gen202.com
File type not supported По ссылке открывается страница сайта, а не файл. Загрузите сам файл или дайте прямую ссылку на него.
image #2: width and height must be between 300px and 6000px (got 262x377) (invalid_asset_material) Одна из картинок слишком маленькая. Seedance принимает картинки от 300 до 6000 пикселей по каждой стороне.
Seedance identified your task as video editing based on your prompt. Seedance понял запрос как редактирование присланного видео. В песочнице и чате выберите режим «Редактирование видео», в API поставьте пропорции adaptive и длительность −1.
Seedance identified your task as video extension based on your prompt. Seedance решил, что присланное видео нужно продлить. Для этого поставьте пропорции adaptive, а если продлевать не нужно — уберите эту просьбу из промта.
generate task timeout. Модель не успела создать результат и сама остановила задачу.
Internal Error Модель остановилась и не назвала причину. Возможно, она перегружена или временно недоступна. Или запрос отклонён из-за чувствительной темы: нагота и откровенные сцены, дети, известные люди, насилие, чужие персонажи и бренды.
The request failed because the input image may contain sensitive information. Модель отклонила загруженную картинку из-за чувствительной темы. Такие темы: нагота и откровенные сцены, дети, известные люди, насилие, чужие персонажи и бренды.
The generated content was filtered by the safety system. Модель отклонила запрос из-за чувствительной темы. Такие темы: нагота и откровенные сцены, дети, известные люди, насилие, чужие персонажи и бренды.
The model provider rejected or dropped the task creation request. Модель остановилась и не назвала причину. Возможно, она перегружена или временно недоступна. Или запрос отклонён из-за чувствительной темы: нагота и откровенные сцены, дети, известные люди, насилие, чужие персонажи и бренды.
No result from the provider within the task deadline. Модель либо не приняла задачу за 15 минут (у видео — за 60), либо не ответила за сутки.

Ссылка на готовый файл живёт 14 дней — столько его хранит поставщик. Файл, который нужен дольше, скачивайте к себе сразу после того, как задача пришла в состояние success.

Уведомление о готовности

Ждать результат можно не только опросом. Если при постановке задачи передать поле callBackUrl, шлюз сам постучится на этот адрес, когда задача закроется — успехом или отказом. Тело уведомления — то же самое, что отдаёт запрос состояния: конверт { code, msg, data }, а внутри data — та же запись задачи с state, resultJson и creditsConsumed. Разбирать уведомление тем же кодом, что и ответ опроса, — это и есть замысел: у поставщика устроено так же.

Запрос приходит методом POST с типом application/json; номер задачи продублирован заголовком X-Gen202-Task-Id. Принимающая сторона должна ответить кодом из двухсотых в течение 10 секунд. Любой другой ответ, обрыв и молчание считаются недоставкой: шлюз повторит попытку — всего их 4, с паузами 30 с, 2 мин, 8 мин. После последней попытки уведомление больше не шлётся, но состояние задачи всё это время доступно опросом, и он остаётся надёжным способом узнать исход.

Требования к адресу: http или https и имя, которое разрешается в адрес открытой сети. На внутренние адреса шлюз не ходит — ни на 127.0.0.1, ни на адреса домашних и служебных сетей, — и перенаправлениям не следует: адрес назначения должен принимать запрос сам.

Что лежит в ответе каждого из двух запросов и как достать из него ссылку на файл — в разделе Формат ответа. Чем отвечает шлюз, когда запрос не принят, — в разделе Отказы.