# Tavio API v2.0.0

Tavio — unified AI generation API. OpenAI-compatible endpoints for text, images, audio (TTS), and video generation.

## Authentication

All endpoints require an API key passed as a Bearer token:

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

## Async Mode

All generation endpoints support `async: true`. When enabled:
1. The endpoint returns `202 Accepted` immediately with a `status_url`
2. Poll the `status_url` from the response to track progress (recommended interval: every 5 seconds)
3. Optionally configure `webhook_url` to receive a callback when done

### Status polling response

When you poll `status_url`, the response has this format:

```json
{
  "task_id": "uuid",
  "type": "image | audio | llm | video",
  "status": "waiting | processing | completed | error",
  "status_label": "Processing",
  "progress": 0-100,
  "result": { },
  "error": "error message",
  "credits_cost": 123,
  "created_at": "ISO-8601",
  "completed_at": "ISO-8601",
  "meta": { "credits_spent": 123 }
}
```

- `result` — present only when `status=completed`. Shape matches the sync `200` response for the same endpoint.
- `error` — present only when `status=error`.

## Webhooks

Instead of polling, you can pass `webhook_url` (and optionally `webhook_secret`) in any generation request. When the task completes or fails, we send a `POST` request to your URL.

### Webhook 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"
}
```

### Webhook headers

| Header | Description |
|--------|-------------|
| `X-Tavio-Task-Id` | Task ID |
| `X-Tavio-Event` | `task.completed` or `task.failed` |
| `X-Tavio-Timestamp` | Unix seconds at signing time (only if `webhook_secret` was provided) |
| `X-Tavio-Signature` | Hex HMAC-SHA256 signature over the canonical string (only if `webhook_secret` was provided) |

### Signature verification

If you provided `webhook_secret`, verify the signature against this canonical scheme:

1. Reject the request if `X-Tavio-Timestamp` is more than **300 seconds** off from your server's current time (replay protection).
2. Build the canonical string:
   ```
   {METHOD}\n{PATH_AND_QUERY}\n{TIMESTAMP}\n{SHA256_HEX(BODY)}
   ```
   where `METHOD` = `POST`, `PATH_AND_QUERY` is the request URL's pathname plus search (e.g. `/cb?source=tavio`), `TIMESTAMP` is the raw `X-Tavio-Timestamp` value, and `SHA256_HEX(BODY)` is the lowercase hex SHA-256 of the raw request body bytes.
3. Compute `HMAC-SHA256(webhook_secret, canonical)` and hex-encode it.
4. Use a constant-time comparison against `X-Tavio-Signature`.

The signature covers the URL path, query string, timestamp and body hash — query parameter tampering or replay outside the 300s window will fail verification.

### Delivery & retries

- **4 attempts** total (1 initial + 3 retries)
- Retry delays: **1s → 4s → 16s**
- **10 second timeout** per attempt
- Retries on: 5xx errors, 429, network/timeout errors
- **No retries** on 4xx errors (except 429) — these are treated as permanent failures
- If all attempts fail, the webhook is marked as undelivered. The task result is still available via `GET /v1/tasks/{taskId}`.

## Credit Billing

Each request reserves credits before generation and confirms after completion. Check `meta.credits_spent` in responses.
- If generation succeeds — reserved credits are confirmed (deducted).
- If generation fails — reserved credits are automatically refunded.
- You can cancel a pending task via `DELETE /v1/tasks/{taskId}` to get an immediate refund.

## Error Codes

All error responses follow this format:

```json
{
  "error": {
    "message": "Human-readable description",
    "type": "error_category",
    "code": "ERROR_CODE"
  }
}
```

| Code | HTTP | Description |
|------|------|-------------|
| `VALIDATION_ERROR` | 400 | Invalid request parameters |
| `UNAUTHORIZED` | 401 | Missing or invalid API key |
| `INSUFFICIENT_CREDITS` | 402 | Not enough credits on balance |
| `RATE_LIMITED` | 429 | Too many requests, retry later |
| `MODEL_NOT_FOUND` | 404 | Model not found or unavailable |
| `CONTENT_SAFETY` | 400 | Content violates usage policies |
| `GENERATION_FAILED` | 500 | Generation process failed |
| `TIMEOUT` | 500 | Request timed out |
| `PROVIDER_ERROR` | 502 | Upstream service temporarily unavailable |
| `PROVIDER_OVERLOADED` | 503 | Upstream provider temporarily overloaded — retry after delay |
| `INTERNAL_ERROR` | 500 | Internal server error |

Error messages are localized based on the `Accept-Language` header (`en`, `ru`).

---
*[Документация на русском](/docs/ru)*

---

## Models

> Available models and pricing

### GET /v1/models
**List all models**

Returns the full OpenAI-compatible flat list of every Tavio model (chat, image, TTS) in one call. Cached at the edge for 60 seconds. No authentication required.

*No authentication required*

**Responses:**

- **200**: List of all available models

```json
{
  "object": "list",
  "data": [{
    "id": "string",
    "object": "model",
    "owned_by": "string"
  }]
}
```
- **400**: Invalid request parameters
- **401**: Missing or invalid API key
- **402**: Insufficient credit balance
- **429**: Rate limit exceeded
- **500**: Internal server error

---

### GET /v1/chat/models
**List chat models**

Returns all available LLM models with pricing. No authentication required.

*No authentication required*

**Responses:**

- **200**: List of available chat models

```json
{
  "object": "list",
  "data": [{
    "id": "string",
    "object": "model",
    "owned_by": "string"
  }]
}
```
- **400**: Invalid request parameters
- **401**: Missing or invalid API key
- **402**: Insufficient credit balance
- **429**: Rate limit exceeded
- **500**: Internal server error

---

### GET /v1/images/models
**List image models**

Returns all available image generation models with pricing. No authentication required.

*No authentication required*

**Responses:**

- **200**: List of available image models

```json
{
  "object": "list",
  "data": [{
    "id": "string",
    "object": "model",
    "owned_by": "string"
  }]
}
```
- **400**: Invalid request parameters
- **401**: Missing or invalid API key
- **402**: Insufficient credit balance
- **429**: Rate limit exceeded
- **500**: Internal server error

---

### GET /v1/responses/models
**List models exposed via /v1/responses**

Returns the subset of LLM models that expose a `/v1/responses` configuration. Models without it are reachable only via `/v1/chat/completions`.

*No authentication required*

**Responses:**

- **200**: List of Responses-API-capable models

```json
{
  "object": "list",
  "data": [{
    "id": "string",
    "object": "model",
    "price_per_1m": 0
  }]
}
```
- **400**: Invalid request parameters
- **401**: Missing or invalid API key
- **402**: Insufficient credit balance
- **429**: Rate limit exceeded
- **500**: Internal server error

---

## Chat

> OpenAI-compatible chat completions

### POST /v1/chat/completions
**Create chat completion**

OpenAI-compatible chat completion. Supports tools, response_format, reasoning_effort, async mode, and streaming via `stream: true` (SSE, see docs/api/chat-completions-streaming.md).

**Request body (required):**

```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"
}
```

| Field | Type | Req. | Description |
|-------|------|------|-------------|
| `model` | string | Yes |  |
| `messages` | object[] | Yes |  |
| `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 |  |  |

**Responses:**

- **200**: Chat completion result

```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 task accepted (when 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**: Invalid request parameters
- **401**: Missing or invalid API key
- **402**: Insufficient credit balance
- **429**: Rate limit exceeded
- **500**: Internal server error

---

## Responses

> OpenAI-compatible Responses API — stateless, sync. Supports reasoning models and structured outputs.

### POST /v1/responses
**OpenAI-compatible Responses API (stateless, sync)**

OpenAI-compatible Responses API. The Tavio variant is **stateless**: `previous_response_id`, `store: true`, `background: true`, `stream: true`, and `async: true` are rejected with `400`. Send the full conversation history in `input[]` on every request. Reasoning models and structured outputs are supported.

**Request body (required):**

```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": {}
}
```

| Field | Type | Req. | Description |
|-------|------|------|-------------|
| `model` | string | Yes |  |
| `input` | object | Yes |  |
| `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 |  |  |

**Responses:**

- **200**: Response generated successfully

```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**: Invalid request parameters
- **401**: Missing or invalid API key
- **402**: Insufficient credit balance
- **429**: Rate limit exceeded
- **500**: Internal server error

---

### GET /v1/responses/models
**List models exposed via /v1/responses**

Returns the subset of LLM models that expose a `/v1/responses` configuration. Models without it are reachable only via `/v1/chat/completions`.

*No authentication required*

**Responses:**

- **200**: List of Responses-API-capable models

```json
{
  "object": "list",
  "data": [{
    "id": "string",
    "object": "model",
    "price_per_1m": 0
  }]
}
```
- **400**: Invalid request parameters
- **401**: Missing or invalid API key
- **402**: Insufficient credit balance
- **429**: Rate limit exceeded
- **500**: Internal server error

---

## Moderation

> OpenAI-compatible content moderation

### POST /v1/moderations
**Classify content (OpenAI-compatible)**

OpenAI-compatible Moderations API — classify whether text is potentially harmful across categories (hate, harassment, self-harm, sexual, violence, etc.). `input` accepts a single string or an array of strings (up to 100 items). The response mirrors OpenAI: each result carries `flagged`, per-category booleans, and per-category confidence scores. Synchronous only.

**Request body (required):**

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

| Field | Type | Req. | Description |
|-------|------|------|-------------|
| `input` | object | Yes |  |
| `model` | string |  |  |

**Responses:**

- **200**: Moderation classification result

```json
{
  "id": "modr-abc123",
  "model": "omni-moderation-latest",
  "results": [{
    "flagged": false,
    "categories": {},
    "category_scores": {},
    "category_applied_input_types": {}
  }],
  "meta": {
    "credits_spent": 0
  }
}
```
- **400**: Invalid request parameters
- **401**: Missing or invalid API key
- **402**: Insufficient credit balance
- **429**: Rate limit exceeded
- **500**: Internal server error

---

## Images

> Image generation

### POST /v1/images/generations
**Generate images**

Generate images from a text prompt or from chat-style multimodal messages.

## Request shape — pick one

The endpoint accepts **two mutually exclusive** request shapes. Pick one; mixing them is rejected with `400 VALIDATION_ERROR`.

### 1. Plain form (recommended)

Canonical OpenAI-compatible shape. Use this unless your client is already chat-shaped.

```json
{
  "model": "gemini-3-pro-image-preview",
  "prompt": "A red cat sitting on a blue chair",
  "urls": ["https://cdn.example.com/style-ref.jpg"],
  "size": "1024x1024",
  "quality": "hd"
}
```

- `prompt` — required text description.
- `urls` — optional, up to **10** reference image URLs for image-to-image / remix (a per-model cap may be lower — some models accept only 3 or 5).

### 2. Chat form

OpenAI-multimodal-compatible shape. Use this when you already have a `messages[]` array with `image_url` parts (e.g. from a chat UI).

```json
{
  "model": "gemini-3-pro-image-preview",
  "size": "1536x2048",
  "quality": "hd",
  "messages": [{
    "role": "user",
    "content": [
      { "type": "text", "text": "Make this cat bright green" },
      { "type": "image_url", "image_url": { "url": "https://cdn.example.com/cat.jpg" } }
    ]
  }]
}
```

- `messages` must contain at least one `user` message with a **non-empty `text` part** — that text is the prompt sent to the provider.
- `image_url` parts inside messages are treated as reference images (same role as `urls[]` in plain form), capped at **10** total (or less if the model declares a lower limit).
- Only `user`-role messages are consulted. `system` / `assistant` messages are ignored.

## Validation rules

A request is rejected with `400 VALIDATION_ERROR` when:

- both `prompt` and `messages` are present,
- both `urls` and `messages` are present,
- `messages` is used without any user text part,
- neither `prompt` nor `messages` is provided.

## Response shape

Both request forms return **the same response shape** — a flat `data` array of image items. The server always normalizes chat-form results to this shape, so clients do not need two code paths.

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

`b64_json` is present only when the provider returns base64 inline (typical for plain form). For chat-form results the image is always already uploaded to our CDN, so only `url` is populated.

## `size` — requested pixel dimensions

`size` uses `WIDTHxHEIGHT` in pixels (e.g. `1024x1024`, `1920x1080`, `2160x3840`). The system derives two things from it internally:

1. **Shape** (square / landscape / portrait) — from the `width / height` ratio.
2. **Resolution tier** (≈1K / 2K / 4K) — from the longest side in pixels. Rough thresholds: ≥3400 px → 4K, ≥1700 px → 2K, otherwise 1K.

## `quality`

`standard` (default) or `hd`. `hd` bumps the resolution tier by one step (1K → 2K, 2K → 4K).

Depending on the selected model, the exact pixel dimensions you send may be honored verbatim or mapped to the nearest supported shape and tier. Check the response metadata for the actual dimensions.

**Examples**:
- `size: "1024x1024"` → square, 1K
- `size: "1024x1024", quality: "hd"` → square, 2K (HD-bumped)
- `size: "1920x1080"` → landscape, 2K (Full HD)
- `size: "1536x2048", quality: "hd"` → portrait, 4K (HD-bumped from 2K)
- `size: "2160x3840"` → portrait, 4K (vertical 4K UHD)

Supports async mode and webhook delivery for long-running generations.

**Request body (required):**

```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"
}
```

| Field | Type | Req. | Description |
|-------|------|------|-------------|
| `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). |

**Responses:**

- **200**: Generated images with CDN URLs

```json
{
  "created": 0,
  "data": [{
    "url": "string",
    "b64_json": "string",
    "revised_prompt": "string"
  }],
  "meta": {
    "credits_spent": 0
  }
}
```
- **202**: Async task accepted (when 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**: Invalid request parameters
- **401**: Missing or invalid API key
- **402**: Insufficient credit balance
- **429**: Rate limit exceeded
- **500**: Internal server error

---

## Audio

> Text-to-speech

### POST /v1/audio/speech
**Text-to-speech (standard)**

Convert text to speech. Synchronous requests (≤4000 chars) return the audio as a binary body; longer text must use async mode (set "async": true), which returns 202 and processes via chunked parallel generation.

**Request body (required):**

```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": {}
}
```

| Field | Type | Req. | Description |
|-------|------|------|-------------|
| `model` | string |  |  |
| `input` | string | Yes |  |
| `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 |  |  |

**Responses:**

- **200**: Generated speech returned as a binary audio body (audio/mpeg). Task id and credits spent are in the X-Tavio-* response headers.
- **202**: Async task accepted (when 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**: Invalid request parameters
- **401**: Missing or invalid API key
- **402**: Insufficient credit balance
- **413**: Input exceeds the synchronous limit (4000 characters). Set "async": true for longer text.
- **429**: Rate limit exceeded
- **500**: Internal server error

---

### POST /v1/audio/text-to-speech/{voice_id}
**Text-to-speech (voice_id)**

TTS endpoint with voice_id in the URL path. Supports voice_settings for fine-tuning.

**Parameters:**

| Name | In | Type | Required | Description |
|------|------|------|----------|-------------|
| `voice_id` | path | string | Yes | Voice identifier |

**Request body (required):**

```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
}
```

| Field | Type | Req. | Description |
|-------|------|------|-------------|
| `text` | string | Yes |  |
| `model_id` | string |  |  |
| `template_uuid` | string |  |  |
| `voice_settings` | object |  |  |
| `output_format` | string |  |  |
| `chunk_size` | integer |  |  |
| `chunk_pause` | number |  |  |

**Responses:**

- **200**: Generated audio with CDN URL

```json
{
  "id": "string",
  "audio_url": "string",
  "duration": 0,
  "characters": 0,
  "characters_generated": 0,
  "partial": false,
  "meta": {
    "credits_spent": 0
  }
}
```
- **400**: Invalid request parameters
- **401**: Missing or invalid API key
- **402**: Insufficient credit balance
- **429**: Rate limit exceeded
- **500**: Internal server error

---

### POST /v1/audio/speech/estimate
**Estimate TTS cost**

Estimate token cost, chunk count, and duration before generating. Does not reserve tokens or start generation.

**Request body (required):**

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

| Field | Type | Req. | Description |
|-------|------|------|-------------|
| `model` | string | Yes |  |
| `input` | string | Yes |  |
| `voice` | string |  |  |
| `chunk_size` | integer |  |  |
| `split_type` | `smart` \| `sentences` \| `paragraphs` \| `max_length` |  |  |

**Responses:**

- **200**: Cost estimation

```json
{
  "model": "string",
  "characters": 0,
  "estimated_tokens": 0,
  "chunks_count": 0,
  "estimated_duration_seconds": 0
}
```
- **400**: Invalid request parameters
- **401**: Missing or invalid API key
- **402**: Insufficient credit balance
- **429**: Rate limit exceeded
- **500**: Internal server error

---

### POST /v1/audio/speech/{taskId}/retry
**Retry failed TTS chunks**

Retry generation of failed chunks from a previous TTS task. Only pays for retried chunks. Omit body or send empty object to retry all failed chunks, or specify chunk indices.

**Parameters:**

| Name | In | Type | Required | Description |
|------|------|------|----------|-------------|
| `taskId` | path | string | Yes | Task ID from 202 response |

**Request body (optional):**

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

| Field | Type | Req. | Description |
|-------|------|------|-------------|
| `chunks` | integer[] |  |  |

**Responses:**

- **200**: Retry result with regenerated audio

```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**: Invalid request parameters
- **401**: Missing or invalid API key
- **402**: Insufficient credit balance
- **404**: Task or chunks metadata not found
- **429**: Rate limit exceeded
- **500**: Internal server error

---

## Voice

> Async long-form TTS

### POST /voice/tasks
**Create async TTS task (chunked)**

Long-form text-to-speech with chunking and parallel generation. Returns task URLs for status polling and result download.

**Request body (required):**

```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
}
```

| Field | Type | Req. | Description |
|-------|------|------|-------------|
| `text` | string | Yes |  |
| `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 |  |  |

**Responses:**

- **200**: Voice task created

```json
{
  "task_id": "string",
  "status_url": "string",
  "result_url": "string"
}
```
- **400**: Invalid request parameters
- **401**: Missing or invalid API key
- **402**: Insufficient credit balance
- **429**: Rate limit exceeded
- **500**: Internal server error

---

### POST /voice/synthesize
**Create async TTS task (extended)**

Extended async TTS with voice_settings, split_type, and split_output support.

**Request body (required):**

```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
}
```

| Field | Type | Req. | Description |
|-------|------|------|-------------|
| `text` | string | Yes |  |
| `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 |  |  |

**Responses:**

- **200**: Voice task created

```json
{
  "task_id": "string",
  "status_url": "string",
  "result_url": "string"
}
```
- **400**: Invalid request parameters
- **401**: Missing or invalid API key
- **402**: Insufficient credit balance
- **429**: Rate limit exceeded
- **500**: Internal server error

---

### GET /voice/tasks/{fullId}/status
**Get chunked TTS task status**

Check the status of a chunked async TTS task. Requires the access token from the creation response.

*No authentication required*

**Parameters:**

| Name | In | Type | Required | Description |
|------|------|------|----------|-------------|
| `fullId` | path | string | Yes | Task ID from creation response |
| `token` | query | string | Yes | Access token from 202 response |

**Responses:**

- **200**: Task status and result

```json
{
  "status": "pending",
  "progress": 0,
  "chunks_total": 0,
  "chunks_done": 0,
  "error": "string"
}
```
- **401**: Missing or invalid API key
- **404**: Task not found or invalid token

---

### GET /voice/tasks/{fullId}/result
**Download chunked TTS result**

Download the generated audio result. Only available when task status is "ending" or "completed".

*No authentication required*

**Parameters:**

| Name | In | Type | Required | Description |
|------|------|------|----------|-------------|
| `fullId` | path | string | Yes | Task ID from creation response |
| `token` | query | string | Yes | Access token from 202 response |

**Responses:**

- **200**: Audio result with chunk URLs

```json
{
  "audio_url": "string",
  "chunks": [{
    "index": 0,
    "url": "string",
    "duration": 0
  }],
  "total_duration": 0,
  "characters": 0
}
```
- **202**: Task not yet complete
- **401**: Missing or invalid API key
- **404**: Task not found or invalid token

---

### GET /voice/status/{taskId}
**Get extended TTS task status**

Check the status of an extended async TTS task.

*No authentication required*

**Parameters:**

| Name | In | Type | Required | Description |
|------|------|------|----------|-------------|
| `taskId` | path | string | Yes | Task ID from 202 response |
| `token` | query | string | Yes | Access token from 202 response |

**Responses:**

- **200**: Task status and result

```json
{
  "status": "pending",
  "progress": 0,
  "chunks_total": 0,
  "chunks_done": 0,
  "error": "string"
}
```
- **401**: Missing or invalid API key
- **404**: Task not found or invalid token

---

### GET /voice/download/{taskId}
**Download extended TTS result**

Download the generated audio manifest for an extended async TTS task.

*No authentication required*

**Parameters:**

| Name | In | Type | Required | Description |
|------|------|------|----------|-------------|
| `taskId` | path | string | Yes | Task ID from 202 response |
| `token` | query | string | Yes | Access token from 202 response |

**Responses:**

- **200**: Audio manifest

```json
{
  "audio_url": "string",
  "chunks": [{
    "index": 0,
    "url": "string",
    "duration": 0
  }],
  "total_duration": 0,
  "characters": 0
}
```
- **401**: Missing or invalid API key
- **404**: Task not found or invalid token

---

## Tasks

> Task management — list, details, status polling, and cancellation

### GET /v1/tasks
**List tasks**

List all generation tasks for the authenticated user. Supports filtering by type and status, with pagination.

**Parameters:**

| Name | In | Type | Required | Description |
|------|------|------|----------|-------------|
| `limit` | query | integer | No | Number of tasks to return (1-100, default 20) |
| `offset` | query | integer | No | Number of tasks to skip (default 0) |
| `type` | query | `image` \| `video` \| `audio` \| `llm` | No | Filter by task type |
| `status` | query | `pending` \| `processing` \| `completed` \| `failed` \| `cancelled` | No | Filter by task status |

**Responses:**

- **200**: Paginated task list with previews

```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**: Invalid request parameters
- **401**: Missing or invalid API key
- **402**: Insufficient credit balance
- **429**: Rate limit exceeded
- **500**: Internal server error

---

### GET /v1/tasks/{taskId}
**Get task details**

Get full details of a generation task including original parameters, result, associated files, and model name.

**Parameters:**

| Name | In | Type | Required | Description |
|------|------|------|----------|-------------|
| `taskId` | path | string | Yes | Task ID from 202 response |

**Responses:**

- **200**: Full task details

```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**: Invalid request parameters
- **401**: Missing or invalid API key
- **402**: Insufficient credit balance
- **404**: Task not found
- **429**: Rate limit exceeded
- **500**: Internal server error

---

### DELETE /v1/tasks/{taskId}
**Cancel async task**

Cancel an in-progress async task. Reserved tokens will be refunded. Requires API key authentication.

**Parameters:**

| Name | In | Type | Required | Description |
|------|------|------|----------|-------------|
| `taskId` | path | string | Yes | Task ID from 202 response |

**Responses:**

- **200**: Task cancelled successfully

```json
{
  "success": false,
  "task_id": "string",
  "credits_refunded": 0
}
```
- **401**: Missing or invalid API key
- **404**: Task not found
- **409**: Task already completed or cancelled

---

## Account

> Account state — balance, subscription, credit buckets, referral, and live rate-limit usage

### GET /v1/me
**Current account state**

Returns a single aggregated snapshot of the authenticated account — balance, subscription, credit buckets, referral stats, and live rate-limit usage.

**Responses:**

- **200**: Aggregated account snapshot

```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**: Missing or invalid API key
- **500**: Internal server error

---
