> ## Documentation Index
> Fetch the complete documentation index at: https://docs.widerouter.com/llms.txt
> Use this file to discover all available pages before exploring further.

# API асинхронных задач

> Отправьте задачу генерации, получите идентификатор задачи менее чем за секунду, а затем опрашивайте статус или получите обратный вызов, когда изображения будут готовы.

API асинхронных задач состоит из двух эндпоинтов и одного конверта запроса. Вы отправляете задачу,
сразу получаете идентификатор, а генерация выполняется на стороне WideRouter.
Пока модель работает, вашему запросу не нужно оставаться подключённым.

<Info>
  Используйте этот API, если в противном случае вам пришлось бы удерживать HTTP-соединение открытым в течение
  20–50 секунд. Если у вас уже есть рабочая синхронная интеграция, ознакомьтесь с разделом
  [Выбор синхронного или асинхронного режима](#choosing-sync-or-async) перед миграцией.
</Info>

## Эндпоинты

| Метод  | Путь                 | Назначение                                                   |
| ------ | -------------------- | ------------------------------------------------------------ |
| `POST` | `/v1/tasks/submit`   | Создаёт задачу. Немедленно возвращает идентификатор задачи.  |
| `GET`  | `/v1/task/{task_id}` | Возвращает статус задачи и после завершения — её результаты. |

Обратите внимание: при создании путь имеет форму множественного числа, а при **чтении — единственного числа**. Эндпоинтов для получения списка, отмены или удаления нет.

## Создание задачи

Тело запроса всегда представляет собой один и тот же конверт с тремя ключами:
`model`, `input` и необязательный `callback_url`.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.widerouter.com/v1/tasks/submit \
    -H "Authorization: Bearer $WIDEROUTER_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "model": "gemini-3-pro-image",
      "input": {
        "prompt": "a small ceramic teapot on a light-grey studio backdrop",
        "aspect_ratio": "16:9",
        "image_size": "2K"
      }
    }'
  ```

  ```python Python theme={null}
  import os, requests

  resp = requests.post(
      "https://api.widerouter.com/v1/tasks/submit",
      headers={"Authorization": f"Bearer {os.environ['WIDEROUTER_API_KEY']}"},
      json={
          "model": "gemini-3-pro-image",
          "input": {
              "prompt": "a small ceramic teapot on a light-grey studio backdrop",
              "aspect_ratio": "16:9",
              "image_size": "2K",
          },
      },
      timeout=30,
  )
  task_id = resp.json()["id"]
  ```

  ```javascript Node theme={null}
  const resp = await fetch("https://api.widerouter.com/v1/tasks/submit", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.WIDEROUTER_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      model: "gemini-3-pro-image",
      input: {
        prompt: "a small ceramic teapot on a light-grey studio backdrop",
        aspect_ratio: "16:9",
        image_size: "2K",
      },
    }),
  });
  const { id } = await resp.json();
  ```
</CodeGroup>

Ответ содержит три поля и приходит значительно меньше чем за секунду:

```json theme={null}
{
  "id": "task_dksUgWLYX4jIVueCACD8KwJzjR5JhRsI",
  "status": "queued",
  "created_at": 1788104112
}
```

### Поля конверта

| Поле           | Тип    | Обязательное | Примечания                                                         |
| -------------- | ------ | ------------ | ------------------------------------------------------------------ |
| `model`        | строка | да           | См. [поддерживаемые модели](#supported-models).                    |
| `input`        | объект | да           | Должен быть JSON-объектом. Строка или массив отклоняются.          |
| `callback_url` | строка | нет          | Должен быть URL `https`. См. раздел [Обратные вызовы](#callbacks). |

Неизвестные ключи на уровне конверта в настоящее время принимаются и игнорируются — не
полагайтесь на это и не указывайте там параметры генерации. Их следует указывать в `input`.

### Объект `input`

`input` содержит параметры генерации, и **набор принимаемых им полей зависит
от модели**. Конверт вокруг него фиксирован, а содержимое — нет.

Проверка внутри `input` выполняется строго: любой нераспознанный моделью ключ
сразу отклоняется, поэтому опечатка будет явно обнаружена, а не останется незамеченной:

```json theme={null}
{ "error": { "code": "invalid_params", "message": "unknown field", "param": "input.negative_prompt" } }
```

<Card title="Серия Nano Banana" icon="layers" href="/ru/models/nano-banana/overview">
  Полный список полей, уровни разрешения, соотношения сторон и редактирование изображений.
</Card>

## Опрос результата

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.widerouter.com/v1/task/task_dksUgWLYX4jIVueCACD8KwJzjR5JhRsI \
    -H "Authorization: Bearer $WIDEROUTER_API_KEY"
  ```

  ```python Python theme={null}
  import os, time, requests

  def wait(task_id, timeout=600):
      headers = {"Authorization": f"Bearer {os.environ['WIDEROUTER_API_KEY']}"}
      deadline = time.time() + timeout
      while time.time() < deadline:
          task = requests.get(
              f"https://api.widerouter.com/v1/task/{task_id}",
              headers=headers, timeout=30,
          ).json()
          if task["status"] in ("completed", "failed"):
              return task
          time.sleep(3)
      raise TimeoutError(task_id)
  ```
</CodeGroup>

Завершённая задача выглядит так:

```json theme={null}
{
  "id": "task_dksUgWLYX4jIVueCACD8KwJzjR5JhRsI",
  "model": "gemini-3-pro-image",
  "status": "completed",
  "created_at": 1788104112,
  "started_at": 1788104113,
  "completed_at": 1788104131,
  "expires_at": 1788190531,
  "outputs": [
    "https://r2cdn.agisuitepro.com/o/2026/08/30/task_dksUgWLYX4jIVueCACD8KwJzjR5JhRsI_0.jpg"
  ],
  "counts": { "requested": 1, "succeeded": 1, "failed": 0 }
}
```

### Поля задачи

| Поле           | Присутствует когда    | Примечания                                                           |
| -------------- | --------------------- | -------------------------------------------------------------------- |
| `id`           | всегда                | Идентификатор задачи.                                                |
| `model`        | всегда при чтении     | Возвращается без изменений; отсутствует в ответе на создание.        |
| `status`       | всегда                | `queued`, `in_progress`, `completed`, `failed`.                      |
| `created_at`   | всегда                | Секунды Unix, UTC.                                                   |
| `started_at`   | из `in_progress`      | Когда обработчик получил задачу.                                     |
| `completed_at` | в конечных состояниях | Включает `failed`.                                                   |
| `expires_at`   | `completed`           | `created_at` плюс 24 часа.                                           |
| `outputs`      | `completed`           | Массив URL изображений, по одному на каждое запрошенное изображение. |
| `counts`       | в конечных состояниях | `requested`, `succeeded`, `failed`.                                  |
| `error`        | `failed`              | Объект с `code` и `message`.                                         |

Ответ расширяется по мере продвижения задачи — `outputs` и `expires_at` просто
отсутствуют, пока задача ещё выполняется. Читайте поля с учётом возможного
отсутствия, а не предполагая фиксированную структуру, и сначала проверяйте
`status`.

### Поток состояний

```
queued ──► in_progress ──► completed
                       └─► failed
```

Оба конечных состояния являются окончательными, а эндпоинт чтения идемпотентен:
повторные чтения завершённой задачи возвращают побайтно идентичный JSON.

### Как долго ждать

Отправка занимает доли секунды и сохраняет такую скорость под нагрузкой:
измеренный p50 — 0,88 с, p95 — 0,92 с для 30 задач при 12 параллельных
запросах. Основное время занимает сама генерация — примерно 12–50 секунд в
зависимости от модели и разрешения, из которых 0–11 секунд приходится на
нахождение в очереди. Показатели для каждой модели приведены на её странице.

<Warning>
  Выполняйте опрос каждые 2–3 секунды, а не в плотном цикле. Один запрос чтения
  сам по себе занимает около 0,9 с в оба конца, поэтому более частый опрос ничего
  не даёт и лишь расходует лимит запросов.
</Warning>

## Скачивание результатов

`outputs` содержит обычные URL `https` без подписи и строки запроса, обслуживаемые
CDN доставки WideRouter — это другое имя хоста, отличное от API. Отсюда следуют
два вывода:

<Steps>
  <Step title="Они не требуют аутентификации">
    Не отправляйте им свой API-ключ и считайте сам URL секретом.
    Любой, у кого есть ссылка, может получить изображение, пока она действует.
  </Step>

  <Step title="Они истекают через 24 часа">
    `expires_at` всегда равен `created_at` плюс 86400. Скопируйте всё, что нужно
    сохранить, в собственное хранилище — не сохраняйте URL результатов как постоянные ссылки.
  </Step>
</Steps>

<Warning>
  **При скачивании отправляйте заголовок `User-Agent`.** CDN находится за WAF,
  который отвечает простым `403` на запросы без `User-Agent` и на запросы со значением по умолчанию
  `Python-urllib/3.x`. Это выглядит в точности как истёкшая ссылка, но таковой не является.
  Проверено, работает: браузеры, `curl`, `requests`, `axios`, `okhttp`, Java, Go,
  Postman. Не работает: обычный `urllib.request.urlopen(url)` без заголовков.
</Warning>

```python theme={null}
import urllib.request

req = urllib.request.Request(url, headers={"User-Agent": "my-app/1.0"})
with urllib.request.urlopen(req, timeout=120) as r:
    data = r.read()
```

Изображения возвращаются в формате JPEG и содержат манифест учётных данных контента C2PA. Измеренный
размер файлов: около 0.4–0.7 МБ при `1K`, 2.4–3.0 МБ при `2K` и 7.5–8.2 МБ при `4K`.
Читайте `Content-Type` из ответа, вместо того чтобы предполагать расширение файла.

## Обратные вызовы

Укажите `callback_url` при создании, и WideRouter отправит на него завершённую задачу, поэтому
вы можете полностью отказаться от опроса.

```json theme={null}
{
  "model": "gemini-3-pro-image",
  "input": { "prompt": "a paper crane" },
  "callback_url": "https://your-app.example/hooks/widerouter"
}
```

URL должен быть `https`. Любое другое значение — `http`, имя хоста без схемы или нестроковое значение —
отклоняется при отправке с ошибкой `invalid_callback_url`, поэтому опечатки обнаруживаются сразу,
а не приводят к тому, что данные незаметно никогда не будут доставлены.

WideRouter отправляет объект задачи как `application/json` с заголовками, по которым можно
маршрутизировать запрос до разбора тела:

| Заголовок        | Значение             |
| ---------------- | -------------------- |
| `X-Wide-Event`   | `task.completed`     |
| `X-Wide-Task-Id` | идентификатор задачи |
| `Content-Type`   | `application/json`   |

Тело побайтно идентично тому, что `GET /v1/task/{task_id}` возвращает в этот
момент: те же поля и те же значения, поэтому один обработчик может обслуживать оба пути.

При тестировании доставка выполнялась немедленно: обратные вызовы для трёх задач поступили в течение
секунды после того, как задача достигала `completed`. Если ваш эндпоинт отвечает кодом `5xx`,
WideRouter повторяет попытку; наблюдались попытки примерно через 0, 10 и 70 секунд.

<Warning>
  **Обратные вызовы не подписываются.** Заголовка HMAC нет, и URL — единственное
  подтверждение того, что запрос поступил от WideRouter. Считайте полезную нагрузку подсказкой,
  а не источником достоверных данных: используйте длинный непредсказуемый путь в вашем
  `callback_url` и заставьте обработчик заново прочитать `GET /v1/task/{task_id}`, прежде чем выполнять
  какие-либо важные действия.
</Warning>

<Info>
  Обратный вызов — это оптимизация задержки, а не гарантия доставки. Сохраняйте резервный
  вариант с опросом для задач, обратный вызов которых не поступил, и сделайте обработчик
  идемпотентным — используйте идентификатор задачи `id` в качестве ключа,
  поскольку из-за повторных попыток одна и та же задача может поступить более одного раза.
</Info>

## Если задача завершается с ошибкой

Задача `failed` по-прежнему является `200` на эндпоинте чтения. Ошибка находится в
теле, а не в HTTP-статусе:

```json theme={null}
{
  "id": "task_jodb26qdNoG5CuwzA23OukOgjeH5ktnh",
  "model": "gemini-3-pro-image",
  "status": "failed",
  "created_at": 1788104830,
  "started_at": 1788104831,
  "completed_at": 1788104831,
  "counts": { "requested": 1, "succeeded": 0, "failed": 1 },
  "error": { "code": "input_fetch_error", "message": "fetch input image failed: ..." }
}
```

Обратите внимание: здесь нет `outputs` и нет `expires_at`. Ошибки получения входных данных происходят быстро —
менее чем за секунду, — поскольку возникают до начала работы модели.

## Ошибки при отправке

Проверка выполняется до постановки чего-либо в очередь, поэтому `400` здесь ничего не стоит.

| HTTP | `code`                 | Значение                                                                           |
| ---- | ---------------------- | ---------------------------------------------------------------------------------- |
| 400  | `invalid_params`       | Некорректное поле. `param` точно указывает его имя, например `input.aspect_ratio`. |
| 400  | `invalid_callback_url` | `callback_url` не является URL `https`.                                            |
| 400  | `model_not_supported`  | Реальная модель, но она недоступна в асинхронном API.                              |
| 401  | —                      | Отсутствует заголовок `Authorization` или он содержит недопустимое значение.       |
| 404  | `task_not_found`       | Задачи с таким идентификатором нет или она не принадлежит вашему ключу.            |
| 503  | `model_not_found`      | Модель с таким именем отсутствует на платформе.                                    |

Ошибки указывают на одно поле за раз, а `param` использует полные пути, включая индексы массивов (`input.images[0]`), поэтому вы можете сразу сопоставить ошибку с вашим запросом.

## Какие модели здесь работают

Идентификаторы моделей сопоставляются **точно**. Псевдонимов нет, а имена с суффиксом `-preview` не принимаются. Отправка идентификатора, который не обслуживается асинхронным API, возвращает `model_not_supported` во время отправки, ещё до постановки чего-либо в очередь.

<Card title="Серия Nano Banana" icon="layers" href="/ru/models/nano-banana/overview">
  Модели изображений Google — доступность на каждом интерфейсе, параметры и измеренная задержка.
</Card>

## Выбор синхронного или асинхронного режима

Доступны оба варианта, и ни один из них не объявлен устаревшим.

|                                        | API асинхронных задач        | Синхронные API изображений                                                 |
| -------------------------------------- | ---------------------------- | -------------------------------------------------------------------------- |
| Клиент удерживает соединение           | нет, \~0,9 с                 | да, 12–50 с                                                                |
| Доставка результата                    | URL CDN, 24 ч                | base64 в теле ответа                                                       |
| Клиент отключается во время выполнения | задача всё равно завершается | результат теряется, запрос всё равно тарифицируется                        |
| Несколько изображений за один вызов    | `n` до 4                     | недоступно — `n` принимается и игнорируется, возвращается одно изображение |
| Обратный вызов                         | да                           | нет                                                                        |

Асинхронный режим — лучший вариант по умолчанию для всего, что выполняется за
бессерверной функцией, обратным прокси или мобильным клиентом, поскольку ни один
из этих вариантов не обеспечивает надёжное выполнение запроса длительностью
50 секунд. Синхронные вызовы остаются более простым решением для скрипта, которому
нужно просто получить данные.

## Следующие шаги

<CardGroup cols={2}>
  <Card title="серия Nano Banana" icon="layers" href="/ru/models/nano-banana/overview">
    Матрица параметров, уровни разрешения и различия между двумя моделями.
  </Card>

  <Card title="Быстрый старт" icon="rocket" href="/ru/quickstart">
    Один и тот же процесс от начала до конца примерно за пять минут.
  </Card>
</CardGroup>
