# Generations API

Создание генераций, опрос статуса, список задач и загрузка референсов для image-to-image

Generations API — основной эндпоинт IskraGen. Одна и та же схема запроса работает для всех моделей: картинки, видео, озвучка, музыка. Тип медиа и набор параметров определяются выбранной моделью.

Все запросы авторизуются API-ключом (см. [Authentication](/docs/authentication)):

```
Authorization: Bearer isk_live_<prefix>_<secret>
```

> **Базовый URL:** `https://api.iskragen.ru/v1`. Канонический публичный путь — `/v1/*`. Внутри приложения те же роуты смонтированы под `/api/v1/*` — это внутренняя деталь, в интеграциях используйте `/v1/*`.

## Модель асинхронная

Генерация — **асинхронная операция**. `POST /v1/generations` не ждёт готового медиа: он ставит задачу в очередь, сразу списывает стоимость (hold) и возвращает **HTTP 202** с `id` и статусом `PENDING`. Готовый результат вы получаете одним из двух способов:

- **Polling** — опрашивайте `GET /v1/generations/:id`, пока `status` не станет `SUCCEEDED` или `FAILED`.
- **[Webhooks](/docs/webhooks)** — для длинных задач (видео, музыка) подпишитесь на `generation.succeeded` / `generation.failed` и не опрашивайте вручную.

Жизненный цикл статуса:

```
PENDING → PROCESSING → SUCCEEDED
                    ↘ FAILED   (средства возвращаются на баланс автоматически)
```

| Статус | Значение |
| --- | --- |
| `PENDING` | Задача принята и стоит в очереди. |
| `PROCESSING` | Провайдер выполняет генерацию. |
| `SUCCEEDED` | Готово. Результат в `outputUrls`. |
| `FAILED` | Окончательная ошибка (после всех retry). Списанные средства возвращены. |

---

## POST /v1/generations

Создать генерацию.

### Тело запроса

| Поле | Тип | Обяз. | Описание |
| --- | --- | --- | --- |
| `modelSlug` | string | да | Публичный slug модели IskraGen (например `gpt-image-2-text-to-image`). Источник правды — каталог (раздел «Discovery» ниже) и «ID модели» в карточке модели. |
| `prompt` | string | да | Текстовый промпт, 1–4000 символов. |
| `negativePrompt` | string | нет | До 2000 символов. |
| `params` | object | нет | Параметры, специфичные для модели (например `aspect_ratio`, `resolution`, `duration`, `input_urls`). Мёрджатся поверх `defaultParams` модели. См. раздел «aspect_ratio vs width/height» ниже. |
| `width` | integer | нет | 256–4096. Только для legacy пиксельных моделей (fal/together). Для актуального каталога используйте `params.aspect_ratio` + `params.resolution`. |
| `height` | integer | нет | 256–4096. См. `width`. |
| `durationSec` | integer | нет | 1–600. Длительность для видео/аудио, если модель принимает секунды. |
| `seed` | integer | нет | ≥ 0. Для воспроизводимости. |
| `idempotencyKey` | string | нет | 1–64 символа. Повторный запрос с тем же ключом вернёт ту же генерацию, а не создаст новую. Предпочтительный способ — заголовок `Idempotency-Key` (см. ниже). |

### Заголовки

| Заголовок | Обяз. | Описание |
| --- | --- | --- |
| `Idempotency-Key` | нет | 1–64 символа. Повторный запрос с тем же ключом вернёт ту же генерацию. Если заголовок не передан, используется поле `idempotencyKey` из тела запроса; если переданы оба и значения различаются — приоритет у заголовка. Значение длиннее 64 символов или пустое → `400`. |

```bash
curl -X POST https://api.iskragen.ru/v1/generations \
  -H "Authorization: Bearer $ISKRAGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-42-retry-1" \
  -d '{ "modelSlug": "gpt-image-2-text-to-image", "prompt": "Кот-космонавт" }'
```

### Ответ `202 Accepted`

```json
{
  "id": "0192f0c4-8a1e-7b3d-9f21-2c5e6a4b7d90",
  "status": "PENDING",
  "costRub": 6
}
```

- `costRub` — стоимость, уже зарезервированная (hold) с баланса в момент запроса. При `FAILED` возвращается автоматически.
- `id` — UUIDv7, передавайте его в `GET /v1/generations/:id`.

### Пример

```bash
curl -X POST https://api.iskragen.ru/v1/generations \
  -H "Authorization: Bearer $ISKRAGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "modelSlug": "gpt-image-2-text-to-image",
    "prompt": "Кот-космонавт на фоне Сатурна, фотореализм",
    "params": { "aspect_ratio": "1:1", "resolution": "1K" }
  }'
```

---

## GET /v1/generations/:id

Получить генерацию по `id`. Используется для polling'а.

### Ответ `200 OK`

```json
{
  "id": "0192f0c4-8a1e-7b3d-9f21-2c5e6a4b7d90",
  "userId": "0192...",
  "modelId": "gpt-image-2-text-to-image",
  "modelName": "GPT Image 2",
  "mediaType": "IMAGE",
  "prompt": "Кот-космонавт на фоне Сатурна, фотореализм",
  "params": { "aspect_ratio": "1:1", "resolution": "1K" },
  "status": "SUCCEEDED",
  "outputUrls": ["https://s3.twcstorage.ru/.../result.webp"],
  "costRub": 6,
  "mediaPriceRub": 6,
  "chargeStatus": "SETTLED",
  "errorMessage": null,
  "errorCode": null,
  "startedAt": "2026-07-01T12:00:40.000Z",
  "finishedAt": "2026-07-01T12:00:42.000Z",
  "createdAt": "2026-07-01T12:00:39.000Z"
}
```

| Поле | Тип | Описание |
| --- | --- | --- |
| `status` | string | `PENDING` \| `PROCESSING` \| `SUCCEEDED` \| `FAILED`. |
| `outputUrls` | string[] | Ссылки на результат (S3). Пусто, пока `status` ≠ `SUCCEEDED`. |
| `errorCode` / `errorMessage` | string \| null | Заполняются при `FAILED`. |
| `mediaPriceRub` | number \| null | Реальная цена медиа (когда доступна). |
| `chargeStatus` | string \| null | Статус списания (`HELD` / `SETTLED` / `REFUNDED`). |
| `startedAt` / `finishedAt` | string \| null | ISO-8601 UTC. |

### Пример polling'а (Node.js)

```js
async function waitForResult(id) {
  for (;;) {
    const res = await fetch(`https://api.iskragen.ru/v1/generations/${id}`, {
      headers: { Authorization: `Bearer ${process.env.ISKRAGEN_API_KEY}` },
    });
    const gen = await res.json();
    if (gen.status === "SUCCEEDED") return gen.outputUrls;
    if (gen.status === "FAILED") throw new Error(`${gen.errorCode}: ${gen.errorMessage}`);
    await new Promise((r) => setTimeout(r, 2000)); // 2 сек между опросами
  }
}
```

---

## GET /v1/generations

Список генераций пользователя, cursor-пагинация (новые первыми).

### Query-параметры

| Параметр | Тип | По умолчанию | Описание |
| --- | --- | --- | --- |
| `limit` | integer | 20 | 1–100. |
| `cursor` | string | — | Значение `nextCursor` из предыдущей страницы. |
| `status` | string | — | Фильтр: `PENDING` \| `PROCESSING` \| `SUCCEEDED` \| `FAILED`. |
| `modelId` | string | — | Один или несколько `modelId` через запятую. |

### Ответ `200 OK`

```json
{
  "items": [ { "id": "0192...", "status": "SUCCEEDED", "outputUrls": ["..."], "createdAt": "..." } ],
  "nextCursor": "0192f0c4-8a1e-7b3d-9f21-2c5e6a4b7d90"
}
```

Когда `nextCursor` равен `null` — страниц больше нет.

---

## Discovery: slug и параметры

Список моделей, их публичные slug'и, дефолтные параметры и JSON-схему параметров отдаёт **публичный каталог** (без авторизации):

```bash
curl https://api.iskragen.ru/v1/catalog
```

```json
{
  "total": 22,
  "models": [
    {
      "slug": "gpt-image-2-text-to-image",
      "displayName": "GPT Image 2",
      "mediaType": "IMAGE",
      "sellPriceRub": 6,
      "supportsImg2Img": true,
      "defaultParams": { "aspect_ratio": "auto", "resolution": "1K" },
      "paramsSchema": { "...": "..." }
    }
  ]
}
```

- `slug` → это значение и есть `modelSlug` для `POST /v1/generations`.
- `defaultParams` — что подставится, если вы не передали поле в `params`.
- `paramsSchema` — допустимые ключи и значения `params` для конкретной модели.

Те же данные человекочитаемо есть в каталоге [/models](/models) и в карточке каждой модели.

---

## aspect_ratio vs width/height

Это частый источник путаницы. Правило простое:

- **Актуальный каталог (KIE-модели)** задаёт размер через `params`:
  - `params.aspect_ratio` — соотношение сторон (`"1:1"`, `"16:9"`, `"9:16"`, `"4:3"`, `"3:4"`, `"21:9"`, `"5:4"`, `"3:2"`, `"4:5"`, `"2:3"`, `"auto"`).
  - `params.resolution` — качество (`"1K"`, `"2K"`, `"4K"`). Влияет на цену.
  - Верхнеуровневые `width`/`height` этими моделями **игнорируются**.
- **Legacy пиксельные модели (fal/together)** используют верхнеуровневые `width`/`height` в пикселях.

Что поддерживает конкретная модель — смотрите в `paramsSchema` из каталога или в карточке модели. `params` всегда переопределяют `defaultParams`; опущенные поля берутся из дефолтов модели.

> **`aspect_ratio: "auto"` → только `1K`.** В режиме `auto` доступно единственное разрешение `1K`; комбинация `auto` + `2K`/`4K` не поддерживается. Задайте явное соотношение сторон, если нужно 2K/4K.

---

## Image-to-image: передача референса

Референс не передаётся в теле `POST /v1/generations` напрямую. Схема в два шага: сначала файл загружается в хранилище IskraGen через presigned URL, затем его ссылка передаётся в `params`.

### Шаг 1 — получить presigned URL

```bash
curl -X POST https://api.iskragen.ru/v1/uploads/presign-input \
  -H "Authorization: Bearer $ISKRAGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "contentType": "image/png", "size": 812345 }'
```

Ответ `200`:

```json
{
  "url": "https://s3.twcstorage.ru/...&X-Amz-Signature=...",
  "key": "users/<userId>/inputs/<uuid>.png",
  "finalUrl": "https://s3.twcstorage.ru/.../inputs/<uuid>.png",
  "expiresIn": 900
}
```

- `contentType` — MIME файла (`image/jpeg`, `image/png`, `image/webp`).
- `size` — размер в байтах (лимит модели обычно 10 МБ).
- `url` живёт `expiresIn` секунд (15 минут).

### Шаг 2 — загрузить файл (PUT)

```bash
curl -X PUT "$PRESIGNED_URL" \
  -H "Content-Type: image/png" \
  --data-binary @reference.png
```

### Шаг 3 — создать генерацию, передав `finalUrl` в `params`

Имя поля зависит от модели (смотрите `paramsSchema` в каталоге / карточку модели). Для image-to-image моделей это обычно `input_urls` (массив ссылок):

```bash
curl -X POST https://api.iskragen.ru/v1/generations \
  -H "Authorization: Bearer $ISKRAGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "modelSlug": "gpt-image-2-image-to-image",
    "prompt": "Тот же кот, но в стиле акварели",
    "params": {
      "input_urls": ["https://s3.twcstorage.ru/.../inputs/<uuid>.png"],
      "aspect_ratio": "1:1",
      "resolution": "1K"
    }
  }'
```

Дальше — обычный polling `GET /v1/generations/:id`.

---

## Ошибки

Ошибки возвращаются в едином формате (подробнее — [Errors](/docs/errors)):

```json
{
  "error": {
    "code": "INSUFFICIENT_BALANCE",
    "message": "Insufficient balance",
    "requestId": "req_...",
    "timestamp": "2026-07-01T12:00:00.000Z"
  }
}
```

Коды, специфичные для Generations API:

| HTTP | `code` | Когда |
| --- | --- | --- |
| 400 | `VALIDATION_ERROR` | Некорректное тело: пустой `prompt`, `width`/`height` вне диапазона и т.п. |
| 401 | `AUTHENTICATION_ERROR` | Нет/неверный API-ключ. |
| 402 | `INSUFFICIENT_BALANCE` | Не хватает средств. В `details` — `required` и `available`. |
| 403 | `MODEL_NOT_AVAILABLE` | Модель существует, но недоступна для новых генераций (не в публичном каталоге). |
| 404 | `NOT_FOUND` | Модель по `modelSlug` не найдена, либо `id` генерации чужой/несуществующий. |
| 429 | `RATE_LIMIT_ERROR` | Превышен лимит **5 одновременных** генераций (`PENDING`+`PROCESSING`). Дождитесь завершения текущих. |

## Что дальше

- **[Webhooks](/docs/webhooks)** — уведомления вместо polling'а для длинных задач.
- **[Errors](/docs/errors)** — полный формат ошибок и коды.
- **[Rate limits](/docs/rate-limits)** — лимиты запросов и конкурентности.
- **[Interactive API Reference](/reference)** — все эндпоинты с тестированием в браузере.
