<!-- Generated from openapi/v2.json (md/text-to-video.md) — do not edit. Run: npm run generate -->

[Interactive reference](https://api-docs.syllaby.io/) · [OpenAPI 3.1 spec](https://api-docs.syllaby.io/openapi/v2.json) · [Docs index (llms.txt)](https://api-docs.syllaby.io/llms.txt)

# Text to Video

Generate short AI video clips from a text prompt: list the models your plan allows, estimate the exact charge, create a clip, and poll it to completion.

## GET /text-to-video/models

**List text-to-video models** (operationId: `listTextToVideoModels`)

**Step 1 of 4 · Choose a model.**

Returns the text-to-video models available on **your subscription plan** — models outside the plan are omitted entirely (and rejected with `422` by the create and estimate endpoints).

Each model carries everything needed to assemble a valid create request:

- **`modifiers`** — the menus of accepted generation options, keyed by modifier name. Menu modifiers (`duration`, `resolution`, `aspect_ratio`, `audio`) list their allowed `options` and the `default` applied when you omit the field. Media modifiers (`first_frame`, `last_frame`, `image`, `video`) appear only when the model supports them, with the accepted kinds and `max_count`.
- **`pricing`** — per-second pricing tiers. The tier whose modifier values (e.g. `resolution`) match your request applies, and the clip costs `credits_per_second × duration`, rounded up. Use `GET /text-to-video/estimate` for the exact figure — it always equals the charge.

→ **Next:** Step 2 — *(optional)* estimate the cost with `GET /text-to-video/estimate`, or skip to Step 3 — create the clip with `POST /text-to-video`.

### Response `200`

```json
{
  "message": "Success.",
  "status": 200,
  "data": [
    {
      "id": 7,
      "slug": "veo-3-fast",
      "name": "Veo 3 Fast",
      "description": "Fast general-purpose clips with optional native audio.",
      "modifiers": {
        "duration": {
          "type": "select",
          "default": 8,
          "options": [
            4,
            6,
            8
          ]
        },
        "resolution": {
          "type": "select",
          "default": "720p",
          "options": [
            "720p",
            "1080p"
          ]
        },
        "aspect_ratio": {
          "type": "select",
          "default": "16:9",
          "options": [
            "16:9",
            "9:16"
          ]
        },
        "audio": {
          "type": "toggle",
          "default": "off"
        },
        "first_frame": {
          "type": "media",
          "accept": [
            "image"
          ],
          "max_count": 1,
          "required": false
        }
      },
      "pricing": [
        {
          "resolution": "720p",
          "credits_per_second": 4
        },
        {
          "resolution": "1080p",
          "credits_per_second": 6
        }
      ]
    }
  ]
}
```

### Errors

- `401` — Unauthenticated
- `403` — No active subscription. The v2 public API has no free tier — every faceless and preset endpoint (reads included) requires an active subscription. Unsubscribed, expired, or canceled callers are rejected with this `403` (code `SUBSCRIPTION-REQUIRED`) before any ownership, credit, or validation check. Only `GET /me`, `GET /credits/costs`, and `GET /credits/history` are reachable without one.
- `429` — Rate limit exceeded — requests are limited per API token (30 per minute by default). The response carries `Retry-After` (seconds to wait) and `X-RateLimit-Reset` (Unix timestamp when the window resets); back off until then and retry. Successful responses include `X-RateLimit-Limit` and `X-RateLimit-Remaining` so you can pace requests proactively.

Error shapes and examples are shared across endpoints — see the **Error responses** section in [getting-started](getting-started.md).

## GET /text-to-video/estimate

**Estimate clip credits** (operationId: `estimateTextToVideoCredits`)

**Step 2 of 4 · Estimate** *(optional)*.

Dry-run credit estimate for a text-to-video clip, compared against your available balance.

**The estimate equals the charge — exactly.** Defaults are applied exactly as `POST /text-to-video` applies them (any omitted modifier falls back to the model's `default`), and the same pricing formula runs, so `required` is the precise amount the create call will deduct for the same `model` and modifiers. The charge is synchronous, so there is no drift — unlike the faceless render estimate, which approximates per-second renders.

⚠️ Modifiers are **flat query keys** (`?model=veo-3-fast&duration=4`), not nested — the create endpoint's `modifiers[duration]` form is ignored here, and the estimate silently falls back to the engine defaults.

Responds with `422` when the `model` is unknown, inactive, or not available on your plan.

→ **Next:** Step 3 — create the clip with `POST /text-to-video`.

### Parameters

- `model` (query, string, required) — Slug of the text-to-video model (from `GET /text-to-video/models`). Example: `veo-3-fast`
- `duration` (query, integer) — Clip length in seconds. Defaults to the model's `duration` default — pass the value you will send on create so the figures match. Example: `8`
- `resolution` (query, string) — Output resolution. Defaults to the model's `resolution` default. Example: `1080p`
- `aspect_ratio` (query, string) — Output aspect ratio. Defaults to the model's `aspect_ratio` default. Example: `16:9`
- `audio` (query, string) — Native-audio setting. Defaults to the model's `audio` default. Example: `off`

### Response `200`

```json
{
  "message": "Success.",
  "status": 200,
  "data": {
    "required": 32,
    "available": 120,
    "sufficient": true
  }
}
```

### Errors

- `401` — Unauthenticated
- `403` — No active subscription. The v2 public API has no free tier — every faceless and preset endpoint (reads included) requires an active subscription. Unsubscribed, expired, or canceled callers are rejected with this `403` (code `SUBSCRIPTION-REQUIRED`) before any ownership, credit, or validation check. Only `GET /me`, `GET /credits/costs`, and `GET /credits/history` are reachable without one.
- `422` — The `model` is unknown, inactive, or not available on your plan — the response does not distinguish "does not exist" from "plan-blocked".
- `429` — Rate limit exceeded — requests are limited per API token (30 per minute by default). The response carries `Retry-After` (seconds to wait) and `X-RateLimit-Reset` (Unix timestamp when the window resets); back off until then and retry. Successful responses include `X-RateLimit-Limit` and `X-RateLimit-Remaining` so you can pace requests proactively.

Error shapes and examples are shared across endpoints — see the **Error responses** section in [getting-started](getting-started.md).

## POST /text-to-video

**Create a text-to-video clip** (operationId: `createTextToVideo`)

**Step 3 of 4 · Generate.**

💳 Charges credits. The response `meta.credits` shows the cost and your remaining balance — `cost` is the **exact** amount charged (the charge is synchronous, and always equals the estimate endpoint's figure for the same parameters).

Generates a short AI video clip from a text prompt using the chosen model. Returns `202 Accepted` immediately with the same shape as `GET /text-to-video/{id}` while generation runs in the background.

**Validation happens before the charge.** Modifier values must come from the model's menus, and media modifiers are accepted only when the model supports them — violations are `422`s. Media URLs (`first_frame`, `last_frame`, `image`, `video`) are downloaded and validated (type, size, frame constraints) **before** any credits are charged: an unreachable URL, a disallowed type, or an oversized file fails the request with `422` and **nothing is charged**. Each `image[]` / `video[]` entry must be a non-null https URL string — a JSON `null` element is rejected with `422` on the offending key (e.g. `modifiers.image.0`).

**Requires an active subscription.** Unsubscribed accounts are rejected with `403` `SUBSCRIPTION-REQUIRED` before any credit check; the public API has no free tier. Subscribed accounts with an insufficient balance get `402` `INSUFFICIENT-CREDITS` carrying the `required` and `available` figures. If generation later fails, the charge is refunded automatically.

**To track progress:** poll `GET /text-to-video/{id}` with the **`id` from this response**, every 5–10 seconds. The playable file appears at `data.asset.url` when `data.asset.status` is `success`.

→ **Next:** Step 4 — track progress by polling `GET /text-to-video/{id}` with the `id` from this response.

### Request body

Required. The model, prompt, and any generation modifiers for the clip. Media modifiers are HTTPS URLs — fetched and validated before the charge.

- `model` (string, required) — Slug of the text-to-video model to use (from `GET /text-to-video/models`). Unknown models — and models outside your plan — are rejected with `422`.
- `prompt` (string, required) — Text prompt describing the clip — between 5 and 1,500 words.
- `title` (string) — Optional title for the clip. Defaults to "Untitled video <current date/time>".
- `modifiers` (object) — Generation modifiers. All fields are optional — an omitted menu modifier falls back to the model's `default`. Values must come from the model's menus, and media modifiers are accepted only when the model supports them (`422` otherwise).
  - `duration` (integer) — Clip length in seconds — one of the model's `duration` options. The credit cost scales with it.
  - `resolution` (string) — Output resolution — one of the model's `resolution` options (e.g. "720p", "1080p"). Pricing tiers often key off this.
  - `aspect_ratio` (string) — Output aspect ratio — one of the model's `aspect_ratio` options (e.g. "16:9", "9:16").
  - `audio` (string) — Whether the model generates native audio — one of the model's `audio` options.
  - `first_frame` (string) — URL of an image (jpeg/png/webp, max 10 MB) to pin as the clip's first frame. Must be an http(s) URL on a publicly reachable host (standard ports, no credentials) — private/internal addresses are rejected. The file is downloaded and validated before any credits are charged, so a bad URL costs nothing.
  - `last_frame` (string) — URL of an image (jpeg/png/webp, max 10 MB) to pin as the clip's last frame. Must be an http(s) URL on a publicly reachable host (standard ports, no credentials) — private/internal addresses are rejected. The file is downloaded and validated before any credits are charged, so a bad URL costs nothing.
  - `image` (array of string) — URLs of reference images (jpeg/png/webp, max 10 MB each) to guide generation. The model's `image` menu caps how many are accepted.
  - `video` (array of string) — URLs of reference videos (mp4/quicktime/webm, max 25 MB each) to guide generation. The model's `video` menu caps how many are accepted.

Example request:

```json
{
  "model": "veo-3-fast",
  "prompt": "A flagship smartphone rotating slowly on a marble pedestal, studio lighting, shallow depth of field, macro details on the camera array.",
  "title": "Phone hero shot",
  "modifiers": {
    "duration": 8,
    "resolution": "1080p",
    "aspect_ratio": "16:9",
    "first_frame": "https://cdn.example.com/frames/phone-front.png"
  }
}
```

### Response `202`

```json
{
  "message": "Success.",
  "status": 202,
  "data": {
    "id": 42,
    "title": "Phone hero shot",
    "type": "text_to_video",
    "status": "rendering",
    "asset": {
      "status": "draft",
      "url": null,
      "failure": null
    },
    "created_at": "2026-01-01T12:00:00.000Z",
    "updated_at": "2026-01-01T12:00:00.000Z"
  },
  "meta": {
    "credits": {
      "cost": 48,
      "remaining": 72
    }
  }
}
```

### Errors

- `401` — Unauthenticated
- `402` — Insufficient credits — the API has no free-clip allowance, so every clip is charged. `error.required` and `error.available` carry the figures (the same numbers the estimate endpoint reports).
- `403` — Forbidden — two distinct causes share this status: 1. **No active subscription** (code `SUBSCRIPTION-REQUIRED`, message "An active subscription is required.") — checked first by the active-subscription gate, before any other logic. The v2 public API has no free tier. 2. **Plan-limit failure** (code `REACH-PLAN-STORAGE-LIMIT`) — the subscription is active but the account's storage is full. (Insufficient credits are reported as `402` — see above.)
- `422` — Validation error
- `429` — Rate limit exceeded — requests are limited per API token (30 per minute by default). The response carries `Retry-After` (seconds to wait) and `X-RateLimit-Reset` (Unix timestamp when the window resets); back off until then and retry. Successful responses include `X-RateLimit-Limit` and `X-RateLimit-Remaining` so you can pace requests proactively.

Error shapes and examples are shared across endpoints — see the **Error responses** section in [getting-started](getting-started.md).

## GET /text-to-video/{id}

**Get a text-to-video clip** (operationId: `getTextToVideo`)

**Step 4 of 4 · Track progress.**

Returns a single text-to-video clip you own — this is the polling endpoint. Use the **`id` from the create response**; polling every 5–10 seconds is plenty.

**Where to look:** the deliverable lives on `data.asset`, and its `status` is the field to branch on — match these **exact strings**:
- `draft` — just created, generation not started yet (the create call's `202` response carries this).
- `processing` — the provider is generating the clip.
- `success` — terminal. Then, and only then, `data.asset.url` is non-null and playable.
- `failed` — terminal. `data.asset.failure` carries the reason, and the charge has been refunded automatically.

The clip-level `status` tracks the overall video record (`rendering` → `completed`/`failed`).

Responds with a uniform `404` when the id does not exist, belongs to another account, is not a text-to-video clip, or is not numeric — the response never distinguishes those cases.

→ **Done** when `data.asset.status` is `success` — the clip is at `data.asset.url`. On `failed`, the charge is auto-refunded; adjust and create again.

### Parameters

- `id` (path, integer, required) — Identifier of the text-to-video clip — the `id` field from the create response. Example: `42`

### Response `200`

```json
{
  "message": "Success.",
  "status": 200,
  "data": {
    "id": 42,
    "title": "Phone hero shot",
    "type": "text_to_video",
    "status": "completed",
    "asset": {
      "status": "success",
      "url": "https://cdn.syllaby.dev/clips/42/clip.mp4",
      "failure": null
    },
    "created_at": "2026-01-01T12:00:00.000Z",
    "updated_at": "2026-01-01T12:00:00.000Z"
  }
}
```

### Errors

- `401` — Unauthenticated
- `403` — No active subscription. The v2 public API has no free tier — every faceless and preset endpoint (reads included) requires an active subscription. Unsubscribed, expired, or canceled callers are rejected with this `403` (code `SUBSCRIPTION-REQUIRED`) before any ownership, credit, or validation check. Only `GET /me`, `GET /credits/costs`, and `GET /credits/history` are reachable without one.
- `404` — Not found — returned uniformly when the id does not exist, belongs to another account, is not a text-to-video clip, or is not numeric. The response never distinguishes those cases.
- `429` — Rate limit exceeded — requests are limited per API token (30 per minute by default). The response carries `Retry-After` (seconds to wait) and `X-RateLimit-Reset` (Unix timestamp when the window resets); back off until then and retry. Successful responses include `X-RateLimit-Limit` and `X-RateLimit-Remaining` so you can pace requests proactively.

Error shapes and examples are shared across endpoints — see the **Error responses** section in [getting-started](getting-started.md).
