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

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

Изображение, видео и речь делаются от десятков секунд до нескольких минут — держать всё это время открытым соединение нечем. Поэтому генерация оформляется задачей, и путь всегда один и тот же из трёх шагов: поставить задачу, дождаться её, забрать файл. Так же устроен вызов у поставщика, так что код, написанный под его 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",
    "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",
      "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, удержанные кредиты возвращены целиком.

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

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

Ссылка на готовый файл живёт 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, ни на адреса домашних и служебных сетей, — и перенаправлениям не следует: адрес назначения должен принимать запрос сам.

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