# Tavio API v2.0.0

Tavio — единое API для AI-генерации. OpenAI-совместимые эндпоинты для текста, изображений, аудио (TTS) и видео.

## Аутентификация

Все эндпоинты требуют API-ключ в формате Bearer-токена:

```
Authorization: Bearer sk-your-api-key
```

## Асинхронный режим

Все эндпоинты генерации поддерживают `async: true`. При включении:
1. Эндпоинт сразу возвращает `202 Accepted` с `status_url`
2. Опрашивайте `status_url` из ответа для отслеживания прогресса (рекомендуемый интервал: каждые 5 секунд)
3. Опционально настройте `webhook_url` для получения callback по завершении

### Формат ответа при поллинге статуса

При опросе `status_url` ответ имеет следующий формат:

```json
{
  "task_id": "uuid",
  "type": "image | audio | llm | video",
  "status": "waiting | processing | completed | error",
  "status_label": "Processing",
  "progress": 0-100,
  "result": { },
  "error": "сообщение об ошибке",
  "credits_cost": 123,
  "created_at": "ISO-8601",
  "completed_at": "ISO-8601",
  "meta": { "credits_spent": 123 }
}
```

- `result` — присутствует только при `status=completed`. Формат совпадает с синхронным ответом `200` для того же эндпоинта.
- `error` — присутствует только при `status=error`.

## Вебхуки

Вместо поллинга можно передать `webhook_url` (и опционально `webhook_secret`) в любом запросе генерации. При завершении или ошибке мы отправим `POST`-запрос на указанный URL.

### Payload вебхука

```json
{
  "event": "task.completed | task.failed",
  "task_id": "uuid",
  "type": "image | audio | llm | video",
  "status": "completed | failed",
  "result": { },
  "credits_cost": 123,
  "error": null,
  "created_at": "ISO-8601",
  "completed_at": "ISO-8601"
}
```

### Заголовки вебхука

| Заголовок | Описание |
|-----------|----------|
| `X-Tavio-Task-Id` | ID задачи |
| `X-Tavio-Event` | `task.completed` или `task.failed` |
| `X-Tavio-Timestamp` | Unix-секунды на момент подписи (только если указан `webhook_secret`) |
| `X-Tavio-Signature` | Hex HMAC-SHA256 подпись над canonical-строкой (только если указан `webhook_secret`) |

### Проверка подписи

Если вы указали `webhook_secret`, верифицируйте подпись по canonical-схеме:

1. Отклоните запрос, если `X-Tavio-Timestamp` отличается от текущего времени сервера более чем на **300 секунд** (защита от replay).
2. Составьте canonical-строку:
   ```
   {METHOD}\n{PATH_AND_QUERY}\n{TIMESTAMP}\n{SHA256_HEX(BODY)}
   ```
   где `METHOD` = `POST`, `PATH_AND_QUERY` — pathname + search вашего URL (например `/cb?source=tavio`), `TIMESTAMP` — точное значение заголовка `X-Tavio-Timestamp`, `SHA256_HEX(BODY)` — нижнерегистровый hex SHA-256 от сырых байтов тела запроса.
3. Вычислите `HMAC-SHA256(webhook_secret, canonical)` и hex-закодируйте.
4. Сравните с `X-Tavio-Signature` через constant-time сравнение.

Подпись покрывает path, query, timestamp и хэш тела — модификация query-параметров или повтор за пределами окна 300с не пройдут проверку.

### Доставка и повторные попытки

- **4 попытки** всего (1 основная + 3 повтора)
- Интервалы между повторами: **1с → 4с → 16с**
- **10 секунд таймаут** на каждую попытку
- Повтор при: 5xx ошибках, 429, сетевых/таймаут ошибках
- **Без повторов** при 4xx ошибках (кроме 429) — считаются постоянными
- Если все попытки неудачны, вебхук помечается как недоставленный. Результат задачи доступен через `GET /v1/tasks/{taskId}`.

## Биллинг кредитов

Каждый запрос резервирует кредиты перед генерацией и подтверждает после завершения. Проверяйте `meta.credits_spent` в ответах.
- При успешной генерации — зарезервированные кредиты списываются.
- При ошибке генерации — зарезервированные кредиты автоматически возвращаются.
- Можно отменить задачу через `DELETE /v1/tasks/{taskId}` для немедленного возврата.

## Коды ошибок

Все ошибки возвращаются в формате:

```json
{
  "error": {
    "message": "Описание ошибки",
    "type": "категория_ошибки",
    "code": "ERROR_CODE"
  }
}
```

| Код | HTTP | Описание |
|-----|------|----------|
| `VALIDATION_ERROR` | 400 | Некорректные параметры запроса |
| `UNAUTHORIZED` | 401 | Отсутствует или неверный API-ключ |
| `INSUFFICIENT_CREDITS` | 402 | Недостаточно кредитов на балансе |
| `RATE_LIMITED` | 429 | Превышен лимит запросов |
| `MODEL_NOT_FOUND` | 404 | Модель не найдена или недоступна |
| `CONTENT_SAFETY` | 400 | Контент нарушает правила использования |
| `GENERATION_FAILED` | 500 | Ошибка генерации |
| `TIMEOUT` | 500 | Таймаут запроса |
| `PROVIDER_ERROR` | 502 | Внешний сервис временно недоступен |
| `PROVIDER_OVERLOADED` | 503 | Внешний провайдер перегружен — повторите через некоторое время |
| `INTERNAL_ERROR` | 500 | Внутренняя ошибка сервера |

Сообщения об ошибках локализованы на основе заголовка `Accept-Language` (`en`, `ru`).

---
*[English documentation](/docs/en)*

---

## Models

> Доступные модели и цены

### GET /v1/models
**Список всех моделей**

Возвращает полный OpenAI-совместимый плоский список всех моделей Tavio (chat, image, TTS) одним вызовом. Кэшируется на edge на 60 секунд. Авторизация не требуется.

*Авторизация не требуется*

**Ответы:**

- **200**: Список всех доступных моделей

```json
{
  "object": "list",
  "data": [{
    "id": "string",
    "object": "model",
    "owned_by": "string"
  }]
}
```
- **400**: Некорректные параметры запроса
- **401**: Отсутствует или неверный API-ключ
- **402**: Недостаточно кредитов на балансе
- **429**: Превышен лимит запросов
- **500**: Внутренняя ошибка сервера

---

### GET /v1/chat/models
**Список моделей чата**

Возвращает все доступные LLM-модели с ценами. Авторизация не требуется.

*Авторизация не требуется*

**Ответы:**

- **200**: Список доступных моделей чата

```json
{
  "object": "list",
  "data": [{
    "id": "string",
    "object": "model",
    "owned_by": "string"
  }]
}
```
- **400**: Некорректные параметры запроса
- **401**: Отсутствует или неверный API-ключ
- **402**: Недостаточно кредитов на балансе
- **429**: Превышен лимит запросов
- **500**: Внутренняя ошибка сервера

---

### GET /v1/images/models
**Список моделей изображений**

Возвращает все доступные модели генерации изображений с ценами. Авторизация не требуется.

*Авторизация не требуется*

**Ответы:**

- **200**: Список доступных моделей изображений

```json
{
  "object": "list",
  "data": [{
    "id": "string",
    "object": "model",
    "owned_by": "string"
  }]
}
```
- **400**: Некорректные параметры запроса
- **401**: Отсутствует или неверный API-ключ
- **402**: Недостаточно кредитов на балансе
- **429**: Превышен лимит запросов
- **500**: Внутренняя ошибка сервера

---

### GET /v1/responses/models
**Список моделей, доступных через /v1/responses**

Возвращает подмножество LLM-моделей, у которых есть конфигурация для `/v1/responses`. Остальные модели доступны только через `/v1/chat/completions`.

*Авторизация не требуется*

**Ответы:**

- **200**: Список моделей с поддержкой Responses API

```json
{
  "object": "list",
  "data": [{
    "id": "string",
    "object": "model",
    "price_per_1m": 0
  }]
}
```
- **400**: Некорректные параметры запроса
- **401**: Отсутствует или неверный API-ключ
- **402**: Недостаточно кредитов на балансе
- **429**: Превышен лимит запросов
- **500**: Внутренняя ошибка сервера

---

## Chat

> Чат-комплишены (совместимо с OpenAI)

### POST /v1/chat/completions
**Создать чат-комплишен**

OpenAI-совместимый чат-комплишен. Поддерживает tools, response_format, reasoning_effort, асинхронный режим и стриминг через `stream: true` (SSE, см. docs/api/chat-completions-streaming.md).

**Тело запроса (обязательно):**

```json
{
  "model": "string",
  "messages": [{
    "role": "system",
    "content": {},
    "name": "string",
    "tool_calls": [{
      "id": "string",
      "type": "function",
      "function": {
        "name": "string",
        "arguments": "string"
      },
      "extra_content": {}
    }],
    "tool_call_id": "string"
  }],
  "max_tokens": 0,
  "max_completion_tokens": 0,
  "temperature": 0,
  "top_p": 0,
  "n": 0,
  "stream": false,
  "stop": {},
  "presence_penalty": 0,
  "frequency_penalty": 0,
  "logit_bias": {},
  "seed": 0,
  "user": "string",
  "response_format": {
    "type": "text",
    "json_schema": {}
  },
  "tools": [{
    "type": "function",
    "function": {
      "name": "string",
      "description": "string",
      "parameters": {},
      "strict": false
    }
  }],
  "tool_choice": {},
  "parallel_tool_calls": false,
  "reasoning_effort": "low",
  "async": false,
  "webhook_url": "string",
  "webhook_secret": "string"
}
```

| Поле | Тип | Обяз. | Описание |
|-------|------|------|-------------|
| `model` | string | Да |  |
| `messages` | object[] | Да |  |
| `max_tokens` | integer |  | Upper bound for generated tokens. If omitted (and no per-model override is set), Tavio applies a fallback of 10000 tokens — used both as the upstream cap and for upfront credit reservation. |
| `max_completion_tokens` | integer |  | Reasoning-model variant of max_tokens. Same default fallback (10000) applies if neither field is provided. |
| `temperature` | number |  |  |
| `top_p` | number |  |  |
| `n` | integer |  |  |
| `stream` | boolean |  |  |
| `stop` | object |  |  |
| `presence_penalty` | number |  |  |
| `frequency_penalty` | number |  |  |
| `logit_bias` | object |  |  |
| `seed` | integer |  |  |
| `user` | string |  |  |
| `response_format` | object |  |  |
| `tools` | object[] |  |  |
| `tool_choice` | object |  |  |
| `parallel_tool_calls` | boolean |  |  |
| `reasoning_effort` | `low` \| `medium` \| `high` |  |  |
| `async` | boolean |  |  |
| `webhook_url` | string |  |  |
| `webhook_secret` | string |  |  |

**Ответы:**

- **200**: Результат чат-комплишена

```json
{
  "id": "chatcmpl-abc123",
  "object": "chat.completion",
  "created": 0,
  "model": "string",
  "choices": [{
    "index": 0,
    "message": {
      "role": "string",
      "content": "string",
      "tool_calls": [{
        "id": "string",
        "type": "function",
        "function": {...},
        "extra_content": {}
      }],
      "extra_content": {}
    },
    "finish_reason": "stop"
  }],
  "usage": {
    "prompt_tokens": 0,
    "completion_tokens": 0,
    "total_tokens": 0,
    "prompt_tokens_details": {
      "cached_tokens": 0,
      "audio_tokens": 0
    },
    "completion_tokens_details": {
      "reasoning_tokens": 0,
      "accepted_prediction_tokens": 0,
      "rejected_prediction_tokens": 0
    },
    "cache_creation_input_tokens": 0,
    "cache_read_input_tokens": 0
  },
  "meta": {
    "credits_spent": 0
  }
}
```
- **202**: Асинхронная задача принята (при async: true)

```json
{
  "task_id": "string",
  "type": "llm",
  "status": "processing",
  "created_at": "2024-01-01T00:00:00Z",
  "status_url": "string",
  "retry_after": 0,
  "estimated_time": 0,
  "chunks_count": 0
}
```
- **400**: Некорректные параметры запроса
- **401**: Отсутствует или неверный API-ключ
- **402**: Недостаточно кредитов на балансе
- **429**: Превышен лимит запросов
- **500**: Внутренняя ошибка сервера

---

## Responses

> OpenAI-совместимый Responses API — stateless, синхронный. Поддерживает reasoning-модели и structured outputs.

### POST /v1/responses
**OpenAI-совместимый Responses API (stateless, синхронный)**

OpenAI-совместимый Responses API. Вариант Tavio — **stateless**: параметры `previous_response_id`, `store: true`, `background: true`, `stream: true` и `async: true` отклоняются с `400`. Передавайте полную историю диалога в `input[]` в каждом запросе. Поддерживаются reasoning-модели и structured outputs.

**Тело запроса (обязательно):**

```json
{
  "model": "string",
  "input": {},
  "instructions": "string",
  "temperature": 0,
  "top_p": 0,
  "max_output_tokens": 0,
  "reasoning": {
    "effort": "minimal",
    "summary": "auto"
  },
  "text": {
    "format": {},
    "verbosity": "low"
  },
  "tools": [{
    "type": "function",
    "name": "string",
    "description": "string",
    "parameters": {},
    "strict": false
  }],
  "tool_choice": {},
  "parallel_tool_calls": false,
  "metadata": {},
  "user": "string",
  "previous_response_id": {},
  "store": false,
  "background": false,
  "stream": false,
  "async": false,
  "webhook_url": {},
  "webhook_secret": {}
}
```

| Поле | Тип | Обяз. | Описание |
|-------|------|------|-------------|
| `model` | string | Да |  |
| `input` | object | Да |  |
| `instructions` | string |  |  |
| `temperature` | number |  |  |
| `top_p` | number |  |  |
| `max_output_tokens` | integer |  |  |
| `reasoning` | object |  |  |
| `text` | object |  |  |
| `tools` | object[] |  |  |
| `tool_choice` | object |  |  |
| `parallel_tool_calls` | boolean |  |  |
| `metadata` | object |  |  |
| `user` | string |  |  |
| `previous_response_id` | object |  |  |
| `store` | `false` |  |  |
| `background` | `false` |  |  |
| `stream` | `false` |  |  |
| `async` | `false` |  |  |
| `webhook_url` | object |  |  |
| `webhook_secret` | object |  |  |

**Ответы:**

- **200**: Ответ успешно сгенерирован

```json
{
  "id": "resp_abc123",
  "object": "response",
  "created_at": 0,
  "status": "completed",
  "model": "string",
  "output": [{
    "type": "string",
    "id": "string",
    "status": "string",
    "role": "assistant",
    "content": [{}],
    "call_id": "string",
    "name": "string",
    "arguments": "string"
  }],
  "usage": {
    "input_tokens": 0,
    "output_tokens": 0,
    "total_tokens": 0,
    "input_tokens_details": {
      "cached_tokens": 0
    },
    "output_tokens_details": {
      "reasoning_tokens": 0
    }
  },
  "metadata": {},
  "incomplete_details": {
    "reason": "string"
  },
  "meta": {
    "credits_spent": 0
  }
}
```
- **400**: Некорректные параметры запроса
- **401**: Отсутствует или неверный API-ключ
- **402**: Недостаточно кредитов на балансе
- **429**: Превышен лимит запросов
- **500**: Внутренняя ошибка сервера

---

### GET /v1/responses/models
**Список моделей, доступных через /v1/responses**

Возвращает подмножество LLM-моделей, у которых есть конфигурация для `/v1/responses`. Остальные модели доступны только через `/v1/chat/completions`.

*Авторизация не требуется*

**Ответы:**

- **200**: Список моделей с поддержкой Responses API

```json
{
  "object": "list",
  "data": [{
    "id": "string",
    "object": "model",
    "price_per_1m": 0
  }]
}
```
- **400**: Некорректные параметры запроса
- **401**: Отсутствует или неверный API-ключ
- **402**: Недостаточно кредитов на балансе
- **429**: Превышен лимит запросов
- **500**: Внутренняя ошибка сервера

---

## Moderation

> OpenAI-совместимая модерация контента

### POST /v1/moderations
**Классификация контента (OpenAI-совместимая)**

OpenAI-совместимый Moderations API — определяет, является ли текст потенциально вредоносным, по категориям (ненависть, харассмент, самоповреждение, сексуальный контент, насилие и т. д.). `input` принимает одну строку или массив строк (до 100 элементов). Ответ повторяет формат OpenAI: каждый результат содержит `flagged`, булевы значения по категориям и оценки уверенности по категориям. Только синхронный режим.

**Тело запроса (обязательно):**

```json
{
  "input": {},
  "model": "string"
}
```

| Поле | Тип | Обяз. | Описание |
|-------|------|------|-------------|
| `input` | object | Да |  |
| `model` | string |  |  |

**Ответы:**

- **200**: Результат классификации модерации

```json
{
  "id": "modr-abc123",
  "model": "omni-moderation-latest",
  "results": [{
    "flagged": false,
    "categories": {},
    "category_scores": {},
    "category_applied_input_types": {}
  }],
  "meta": {
    "credits_spent": 0
  }
}
```
- **400**: Некорректные параметры запроса
- **401**: Отсутствует или неверный API-ключ
- **402**: Недостаточно кредитов на балансе
- **429**: Превышен лимит запросов
- **500**: Внутренняя ошибка сервера

---

## Images

> Генерация изображений

### POST /v1/images/generations
**Сгенерировать изображения**

Генерация изображений из текстового промпта или из чат-стиля мультимодальных сообщений.

## Формат запроса — выберите один

Эндпоинт принимает **две взаимоисключающие** формы запроса. Смешивать нельзя — получите `400 VALIDATION_ERROR`.

### 1. Простая форма (рекомендуется)

Каноничная OpenAI-совместимая форма. Используйте её, если у вас нет готовой чат-структуры.

```json
{
  "model": "gemini-3-pro-image-preview",
  "prompt": "Рыжий кот на синем стуле",
  "urls": ["https://cdn.example.com/style-ref.jpg"],
  "size": "1024x1024",
  "quality": "hd"
}
```

- `prompt` — обязательный текстовый промпт.
- `urls` — опционально, до **10** ссылок на референсные изображения для image-to-image / remix (у конкретной модели лимит может быть ниже — некоторые принимают только 3 или 5).

### 2. Чат-форма

Совместимая с OpenAI-мультимодалкой форма. Подходит, если у вас уже есть массив `messages[]` с `image_url` частями (например, из чат-интерфейса).

```json
{
  "model": "gemini-3-pro-image-preview",
  "size": "1536x2048",
  "quality": "hd",
  "messages": [{
    "role": "user",
    "content": [
      { "type": "text", "text": "Сделай этого кота ярко-зелёным" },
      { "type": "image_url", "image_url": { "url": "https://cdn.example.com/cat.jpg" } }
    ]
  }]
}
```

- `messages` обязательно содержит хотя бы одно сообщение с ролью `user` и **непустым `text`-партом** — именно этот текст уходит провайдеру как промпт.
- `image_url` части внутри сообщений трактуются как референсные изображения (та же роль, что у `urls[]` в простой форме), максимум **10** суммарно (или меньше, если у модели заявлен свой лимит).
- Учитываются только сообщения с `role: user`. `system` / `assistant` игнорируются.

## Правила валидации

Запрос отклоняется с `400 VALIDATION_ERROR` если:

- присутствуют одновременно `prompt` и `messages`,
- присутствуют одновременно `urls` и `messages`,
- используется `messages`, но ни в одном `user`-сообщении нет текстовой части,
- не указаны ни `prompt`, ни `messages`.

## Формат ответа

Обе формы запроса возвращают **одинаковый формат ответа** — плоский массив `data`. Сервер сам нормализует результат чат-формы к этому виду, чтобы у клиента был один путь обработки.

```json
{
  "created": 1776252778,
  "data": [
    {
      "url": "https://cdn.tavio.tech/...",
      "b64_json": "iVBORw0KGgoAAAANSUhEUgAA...",
      "revised_prompt": "A red cat..."
    }
  ],
  "meta": { "credits_spent": 400 }
}
```

`b64_json` возвращается только когда провайдер отдаёт base64 напрямую (типично для простой формы). Для чат-формы изображение всегда уже загружено на наш CDN — в ответе будет только `url`.

## `size` — желаемые размеры

`size` задаётся в формате `WIDTHxHEIGHT` в пикселях (например `1024x1024`, `1920x1080`, `2160x3840`). Система внутри выводит из него **две** вещи:

1. **Форму** (квадрат / горизонтальная / вертикальная) — из соотношения `width / height`.
2. **Тир разрешения** (≈1K / 2K / 4K) — из длинной стороны в пикселях. Ориентировочно: ≥3400 px → 4K, ≥1700 px → 2K, иначе 1K.

## `quality`

`standard` (по умолчанию) или `hd`. `hd` бампит тир разрешения на одну ступень (1K → 2K, 2K → 4K).

В зависимости от выбранной модели точные пиксели могут быть использованы как есть либо сопоставлены ближайшей поддерживаемой форме и тиру. Реальные размеры итоговой картинки смотрите в метаданных ответа.

**Примеры**:
- `size: "1024x1024"` → квадрат, 1K
- `size: "1024x1024", quality: "hd"` → квадрат, 2K (HD-бамп)
- `size: "1920x1080"` → горизонталка, 2K (Full HD)
- `size: "1536x2048", quality: "hd"` → вертикалка, 4K (HD-бамп от 2K)
- `size: "2160x3840"` → вертикалка, 4K (вертикальный 4K UHD)

Поддерживает асинхронный режим и доставку результата через webhook для долгих генераций.

**Тело запроса (обязательно):**

```json
{
  "model": "string",
  "prompt": "string",
  "messages": [{
    "role": "system",
    "content": {}
  }],
  "n": 0,
  "size": "string",
  "quality": "standard",
  "urls": ["string"],
  "async": false,
  "webhook_url": "string",
  "webhook_secret": "string"
}
```

| Поле | Тип | Обяз. | Описание |
|-------|------|------|-------------|
| `model` | string |  | Model ID to use. See GET /v1/images/models for the list. |
| `prompt` | string |  | Text description of the image. Either `prompt` (plain form) or `messages` (chat form) is required — not both. |
| `messages` | object[] |  | Chat-shaped alternative to `prompt`. At least one user message must contain a non-empty text part. `image_url` parts are treated as reference images. Mutually exclusive with `prompt` and `urls`. |
| `n` | integer |  | Number of images to generate. Currently only 1 is supported. |
| `size` | string |  | Requested pixel dimensions in "WIDTHxHEIGHT" format (e.g. "1024x1024", "1920x1080", "2160x3840"). The system derives both shape (square/landscape/portrait) and resolution tier (1K/2K/4K) from this value. See endpoint description for the tier table. |
| `quality` | `standard` \| `hd` |  | "standard" (default) or "hd". "hd" bumps the resolution tier by one step (1K→2K, 2K→4K). |
| `urls` | string[] |  | Up to 10 reference image URLs for image-to-image / remix. A per-model cap may be lower — see `models.param_config.endpoints.image.params.urls.maxItems`. Plain form only — mutually exclusive with `messages`. |
| `async` | boolean |  | If true, returns 202 immediately with a status URL to poll; otherwise blocks until the image is ready. |
| `webhook_url` | string |  | HTTPS callback URL to receive the result when async is true. |
| `webhook_secret` | string |  | Secret used to sign the webhook payload (HMAC-SHA256). |

**Ответы:**

- **200**: Сгенерированные изображения с CDN-ссылками

```json
{
  "created": 0,
  "data": [{
    "url": "string",
    "b64_json": "string",
    "revised_prompt": "string"
  }],
  "meta": {
    "credits_spent": 0
  }
}
```
- **202**: Асинхронная задача принята (при async: true)

```json
{
  "task_id": "string",
  "type": "llm",
  "status": "processing",
  "created_at": "2024-01-01T00:00:00Z",
  "status_url": "string",
  "retry_after": 0,
  "estimated_time": 0,
  "chunks_count": 0
}
```
- **400**: Некорректные параметры запроса
- **401**: Отсутствует или неверный API-ключ
- **402**: Недостаточно кредитов на балансе
- **429**: Превышен лимит запросов
- **500**: Внутренняя ошибка сервера

---

## Audio

> Озвучка текста

### POST /v1/audio/speech
**Озвучка текста (стандартная)**

Преобразование текста в речь. Синхронные запросы (≤4000 символов) возвращают аудио бинарным телом ответа; для более длинного текста используйте async-режим ("async": true) — вернётся 202, обработка идёт чанками параллельно.

**Тело запроса (обязательно):**

```json
{
  "model": "string",
  "input": "string",
  "voice": "string",
  "template_uuid": "string",
  "speed": 0,
  "response_format": "mp3",
  "chunk_size": 0,
  "split_output": false,
  "async": false,
  "webhook_url": "string",
  "webhook_secret": "string",
  "voice_settings": {
    "stability": 0,
    "similarity_boost": 0,
    "style": 0,
    "use_speaker_boost": false
  },
  "split_type": "smart",
  "chunk_pause": 0,
  "speech_config": {}
}
```

| Поле | Тип | Обяз. | Описание |
|-------|------|------|-------------|
| `model` | string |  |  |
| `input` | string | Да |  |
| `voice` | string |  |  |
| `template_uuid` | string |  |  |
| `speed` | number |  |  |
| `response_format` | `mp3` \| `wav` \| `ogg` |  |  |
| `chunk_size` | integer |  |  |
| `split_output` | boolean |  |  |
| `async` | boolean |  |  |
| `webhook_url` | string |  |  |
| `webhook_secret` | string |  |  |
| `voice_settings` | object |  |  |
| `split_type` | `smart` \| `sentences` \| `paragraphs` \| `max_length` |  |  |
| `chunk_pause` | number |  |  |
| `speech_config` | object |  |  |

**Ответы:**

- **200**: Сгенерированная речь в виде бинарного аудио-тела (audio/mpeg). Идентификатор задачи и списанные кредиты — в заголовках X-Tavio-*.
- **202**: Асинхронная задача принята (при async: true)

```json
{
  "task_id": "string",
  "type": "llm",
  "status": "processing",
  "created_at": "2024-01-01T00:00:00Z",
  "status_url": "string",
  "retry_after": 0,
  "estimated_time": 0,
  "chunks_count": 0
}
```
- **400**: Некорректные параметры запроса
- **401**: Отсутствует или неверный API-ключ
- **402**: Недостаточно кредитов на балансе
- **413**: Текст превышает синхронный лимит (4000 символов). Для длинного текста укажите "async": true.
- **429**: Превышен лимит запросов
- **500**: Внутренняя ошибка сервера

---

### POST /v1/audio/text-to-speech/{voice_id}
**Озвучка текста (voice_id)**

TTS-эндпоинт с voice_id в URL. Поддерживает voice_settings для тонкой настройки.

**Параметры:**

| Имя | Расположение | Тип | Обяз. | Описание |
|------|------|------|----------|-------------|
| `voice_id` | path | string | Да | Идентификатор голоса |

**Тело запроса (обязательно):**

```json
{
  "text": "string",
  "model_id": "string",
  "template_uuid": "string",
  "voice_settings": {
    "stability": 0,
    "similarity_boost": 0,
    "style": 0,
    "use_speaker_boost": false
  },
  "output_format": "string",
  "chunk_size": 0,
  "chunk_pause": 0
}
```

| Поле | Тип | Обяз. | Описание |
|-------|------|------|-------------|
| `text` | string | Да |  |
| `model_id` | string |  |  |
| `template_uuid` | string |  |  |
| `voice_settings` | object |  |  |
| `output_format` | string |  |  |
| `chunk_size` | integer |  |  |
| `chunk_pause` | number |  |  |

**Ответы:**

- **200**: Сгенерированное аудио с CDN-ссылкой

```json
{
  "id": "string",
  "audio_url": "string",
  "duration": 0,
  "characters": 0,
  "characters_generated": 0,
  "partial": false,
  "meta": {
    "credits_spent": 0
  }
}
```
- **400**: Некорректные параметры запроса
- **401**: Отсутствует или неверный API-ключ
- **402**: Недостаточно кредитов на балансе
- **429**: Превышен лимит запросов
- **500**: Внутренняя ошибка сервера

---

### POST /v1/audio/speech/estimate
**Оценка стоимости озвучки**

Оценка стоимости в кредитах, количества чанков и длительности перед генерацией. Не резервирует кредиты и не запускает генерацию.

**Тело запроса (обязательно):**

```json
{
  "model": "string",
  "input": "string",
  "voice": "string",
  "chunk_size": 0,
  "split_type": "smart"
}
```

| Поле | Тип | Обяз. | Описание |
|-------|------|------|-------------|
| `model` | string | Да |  |
| `input` | string | Да |  |
| `voice` | string |  |  |
| `chunk_size` | integer |  |  |
| `split_type` | `smart` \| `sentences` \| `paragraphs` \| `max_length` |  |  |

**Ответы:**

- **200**: Оценка стоимости

```json
{
  "model": "string",
  "characters": 0,
  "estimated_tokens": 0,
  "chunks_count": 0,
  "estimated_duration_seconds": 0
}
```
- **400**: Некорректные параметры запроса
- **401**: Отсутствует или неверный API-ключ
- **402**: Недостаточно кредитов на балансе
- **429**: Превышен лимит запросов
- **500**: Внутренняя ошибка сервера

---

### POST /v1/audio/speech/{taskId}/retry
**Повторить проваленные чанки TTS**

Повторная генерация проваленных чанков предыдущей TTS-задачи. Оплата только за повторяемые чанки. Пустое тело — повтор всех проваленных, или укажите индексы чанков.

**Параметры:**

| Имя | Расположение | Тип | Обяз. | Описание |
|------|------|------|----------|-------------|
| `taskId` | path | string | Да | ID задачи из ответа 202 |

**Тело запроса (опционально):**

```json
{
  "chunks": [0]
}
```

| Поле | Тип | Обяз. | Описание |
|-------|------|------|-------------|
| `chunks` | integer[] |  |  |

**Ответы:**

- **200**: Результат повторной генерации

```json
{
  "id": "string",
  "retry_id": "string",
  "audio_url": "string",
  "duration": 0,
  "characters": 0,
  "characters_generated": 0,
  "partial": false,
  "total_chunks": 0,
  "completed_chunks": 0,
  "failed_chunks": [0],
  "chunks": [{
    "index": 0,
    "url": "string",
    "status": "string",
    "duration": 0,
    "error": "string"
  }],
  "can_retry": false,
  "meta": {
    "credits_spent": 0
  }
}
```
- **400**: Некорректные параметры запроса
- **401**: Отсутствует или неверный API-ключ
- **402**: Недостаточно кредитов на балансе
- **404**: Задача или метаданные чанков не найдены
- **429**: Превышен лимит запросов
- **500**: Внутренняя ошибка сервера

---

## Voice

> Асинхронная озвучка длинных текстов

### POST /voice/tasks
**Создать задачу озвучки (по чанкам)**

Озвучка длинных текстов с разбивкой на чанки и параллельной генерацией. Возвращает URL для опроса статуса и скачивания результата.

**Тело запроса (обязательно):**

```json
{
  "text": "string",
  "model_id": "string",
  "voice_id": "string",
  "template_uuid": "string",
  "voice_settings": {
    "stability": 0,
    "similarity_boost": 0,
    "speed": 0,
    "use_speaker_boost": false
  },
  "chunk_size": 0,
  "split_output": false,
  "async": false,
  "webhook_url": "string",
  "webhook_secret": "string",
  "chunk_pause": 0
}
```

| Поле | Тип | Обяз. | Описание |
|-------|------|------|-------------|
| `text` | string | Да |  |
| `model_id` | string |  |  |
| `voice_id` | string |  |  |
| `template_uuid` | string |  |  |
| `voice_settings` | object |  |  |
| `chunk_size` | integer |  |  |
| `split_output` | boolean |  |  |
| `async` | boolean |  |  |
| `webhook_url` | string |  |  |
| `webhook_secret` | string |  |  |
| `chunk_pause` | number |  |  |

**Ответы:**

- **200**: Задача озвучки создана

```json
{
  "task_id": "string",
  "status_url": "string",
  "result_url": "string"
}
```
- **400**: Некорректные параметры запроса
- **401**: Отсутствует или неверный API-ключ
- **402**: Недостаточно кредитов на балансе
- **429**: Превышен лимит запросов
- **500**: Внутренняя ошибка сервера

---

### POST /voice/synthesize
**Создать задачу озвучки (расширенная)**

Расширенная асинхронная озвучка. Поддерживает voice_settings, split_type и split_output.

**Тело запроса (обязательно):**

```json
{
  "text": "string",
  "model_id": "string",
  "voice_id": "string",
  "template_uuid": "string",
  "voice_settings": {
    "stability": 0,
    "similarity_boost": 0,
    "speed": 0,
    "use_speaker_boost": false
  },
  "chunk_size": 0,
  "split_type": "smart",
  "split_output": false,
  "async": false,
  "webhook_url": "string",
  "webhook_secret": "string",
  "chunk_pause": 0
}
```

| Поле | Тип | Обяз. | Описание |
|-------|------|------|-------------|
| `text` | string | Да |  |
| `model_id` | string |  |  |
| `voice_id` | string |  |  |
| `template_uuid` | string |  |  |
| `voice_settings` | object |  |  |
| `chunk_size` | integer |  |  |
| `split_type` | `smart` \| `sentences` \| `paragraphs` \| `max_length` |  |  |
| `split_output` | boolean |  |  |
| `async` | boolean |  |  |
| `webhook_url` | string |  |  |
| `webhook_secret` | string |  |  |
| `chunk_pause` | number |  |  |

**Ответы:**

- **200**: Задача озвучки создана

```json
{
  "task_id": "string",
  "status_url": "string",
  "result_url": "string"
}
```
- **400**: Некорректные параметры запроса
- **401**: Отсутствует или неверный API-ключ
- **402**: Недостаточно кредитов на балансе
- **429**: Превышен лимит запросов
- **500**: Внутренняя ошибка сервера

---

### GET /voice/tasks/{fullId}/status
**Статус озвучки по чанкам**

Проверка статуса асинхронной TTS-задачи по чанкам. Требуется access token из ответа на создание задачи.

*Авторизация не требуется*

**Параметры:**

| Имя | Расположение | Тип | Обяз. | Описание |
|------|------|------|----------|-------------|
| `fullId` | path | string | Да | ID задачи из ответа на создание |
| `token` | query | string | Да | Токен доступа из ответа 202 |

**Ответы:**

- **200**: Статус и результат задачи

```json
{
  "status": "pending",
  "progress": 0,
  "chunks_total": 0,
  "chunks_done": 0,
  "error": "string"
}
```
- **401**: Отсутствует или неверный API-ключ
- **404**: Задача не найдена или неверный токен

---

### GET /voice/tasks/{fullId}/result
**Скачать результат озвучки по чанкам**

Скачивание сгенерированного аудио. Доступно только когда статус задачи — "ending" или "completed".

*Авторизация не требуется*

**Параметры:**

| Имя | Расположение | Тип | Обяз. | Описание |
|------|------|------|----------|-------------|
| `fullId` | path | string | Да | ID задачи из ответа на создание |
| `token` | query | string | Да | Токен доступа из ответа 202 |

**Ответы:**

- **200**: Аудио-результат со ссылками на чанки

```json
{
  "audio_url": "string",
  "chunks": [{
    "index": 0,
    "url": "string",
    "duration": 0
  }],
  "total_duration": 0,
  "characters": 0
}
```
- **202**: Задача ещё не завершена
- **401**: Отсутствует или неверный API-ключ
- **404**: Задача не найдена или неверный токен

---

### GET /voice/status/{taskId}
**Статус расширенной озвучки**

Проверка статуса расширенной асинхронной TTS-задачи.

*Авторизация не требуется*

**Параметры:**

| Имя | Расположение | Тип | Обяз. | Описание |
|------|------|------|----------|-------------|
| `taskId` | path | string | Да | ID задачи из ответа 202 |
| `token` | query | string | Да | Токен доступа из ответа 202 |

**Ответы:**

- **200**: Статус и результат задачи

```json
{
  "status": "pending",
  "progress": 0,
  "chunks_total": 0,
  "chunks_done": 0,
  "error": "string"
}
```
- **401**: Отсутствует или неверный API-ключ
- **404**: Задача не найдена или неверный токен

---

### GET /voice/download/{taskId}
**Скачать результат расширенной озвучки**

Скачивание манифеста сгенерированного аудио для расширенной TTS-задачи.

*Авторизация не требуется*

**Параметры:**

| Имя | Расположение | Тип | Обяз. | Описание |
|------|------|------|----------|-------------|
| `taskId` | path | string | Да | ID задачи из ответа 202 |
| `token` | query | string | Да | Токен доступа из ответа 202 |

**Ответы:**

- **200**: Манифест аудио

```json
{
  "audio_url": "string",
  "chunks": [{
    "index": 0,
    "url": "string",
    "duration": 0
  }],
  "total_duration": 0,
  "characters": 0
}
```
- **401**: Отсутствует или неверный API-ключ
- **404**: Задача не найдена или неверный токен

---

## Tasks

> Управление задачами — список, детали, статус и отмена

### GET /v1/tasks
**Список задач**

Список всех задач генерации для авторизованного пользователя. Поддерживает фильтрацию по типу и статусу, с пагинацией.

**Параметры:**

| Имя | Расположение | Тип | Обяз. | Описание |
|------|------|------|----------|-------------|
| `limit` | query | integer | Нет | Количество задач (1-100, по умолчанию 20) |
| `offset` | query | integer | Нет | Количество пропускаемых задач (по умолчанию 0) |
| `type` | query | `image` \| `video` \| `audio` \| `llm` | Нет | Фильтр по типу задачи |
| `status` | query | `pending` \| `processing` \| `completed` \| `failed` \| `cancelled` | Нет | Фильтр по статусу задачи |

**Ответы:**

- **200**: Пагинированный список задач с превью

```json
{
  "data": [{
    "id": "string",
    "type": "image",
    "status": "pending",
    "credits_cost": 0,
    "error": "string",
    "created_at": "2024-01-01T00:00:00Z",
    "completed_at": "2024-01-01T00:00:00Z",
    "model": "string",
    "prompt_preview": "string",
    "result_preview": "string",
    "has_result": false
  }],
  "pagination": {
    "total": 0,
    "limit": 0,
    "offset": 0
  }
}
```
- **400**: Некорректные параметры запроса
- **401**: Отсутствует или неверный API-ключ
- **402**: Недостаточно кредитов на балансе
- **429**: Превышен лимит запросов
- **500**: Внутренняя ошибка сервера

---

### GET /v1/tasks/{taskId}
**Детали задачи**

Полная информация о задаче генерации: исходные параметры, результат, файлы и название модели.

**Параметры:**

| Имя | Расположение | Тип | Обяз. | Описание |
|------|------|------|----------|-------------|
| `taskId` | path | string | Да | ID задачи из ответа 202 |

**Ответы:**

- **200**: Полные детали задачи

```json
{
  "data": {
    "id": "string",
    "type": "image",
    "status": "pending",
    "params": {},
    "result": {},
    "credits_cost": 0,
    "error": "string",
    "created_at": "2024-01-01T00:00:00Z",
    "completed_at": "2024-01-01T00:00:00Z",
    "model_name": "string",
    "files": [{
      "url": "string",
      "type": "string",
      "mime_type": "string",
      "duration_seconds": 0
    }]
  }
}
```
- **400**: Некорректные параметры запроса
- **401**: Отсутствует или неверный API-ключ
- **402**: Недостаточно кредитов на балансе
- **404**: Задача не найдена
- **429**: Превышен лимит запросов
- **500**: Внутренняя ошибка сервера

---

### DELETE /v1/tasks/{taskId}
**Отменить задачу**

Отмена выполняющейся асинхронной задачи. Зарезервированные кредиты будут возвращены. Требуется авторизация по API-ключу.

**Параметры:**

| Имя | Расположение | Тип | Обяз. | Описание |
|------|------|------|----------|-------------|
| `taskId` | path | string | Да | ID задачи из ответа 202 |

**Ответы:**

- **200**: Задача успешно отменена

```json
{
  "success": false,
  "task_id": "string",
  "credits_refunded": 0
}
```
- **401**: Отсутствует или неверный API-ключ
- **404**: Задача не найдена
- **409**: Задача уже завершена или отменена

---

## Account

> Состояние аккаунта — баланс, подписка, бакеты кредитов, реферал и текущие лимиты

### GET /v1/me
**Состояние аккаунта**

Единый агрегированный снимок аккаунта — баланс, подписка, бакеты кредитов, реферальная статистика и текущая утилизация лимитов.

**Ответы:**

- **200**: Агрегированный снимок аккаунта

```json
{
  "success": true,
  "data": {
    "user": {
      "id": "string",
      "name": "string"
    },
    "billing": {
      "balance_usd": 15,
      "total_credits_available": 1030,
      "has_active_subscription": true,
      "subscription": {},
      "effective_tier": {
        "limits": [{...}]
      },
      "credit_buckets": [{
        "id": "string",
        "source": "subscription_bonus",
        "credits_granted": 2200,
        "credits_remaining": 1030,
        "expires_at": {}
      }],
      "bucket_credits_total": 1030
    },
    "referral": {
      "referralCode": "97F4838D",
      "commissionRate": 0.1,
      "totalReferrals": 0,
      "paidReferrals": 0,
      "activeReferrals": 0,
      "totalEarnedUsd": 0,
      "pendingUsd": 0,
      "lifetimeEarnedUsd": 0,
      "releasingNext30dUsd": 0,
      "nextReleaseAt": {},
      "forecastEarn30dUsd": 0
    },
    "limits": {}
  }
}
```
- **401**: Отсутствует или неверный API-ключ
- **500**: Внутренняя ошибка сервера

---
