<!-- 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 **paid** subscription. The v2 public API is paid-only — every faceless and preset endpoint (reads included) requires an active paid subscription, and trials are not eligible. Two codes distinguish the cause on this `403`: `PAID-SUBSCRIPTION-REQUIRED` (subscribed but on a trial — access unlocks automatically once the subscription becomes paid) and `SUBSCRIPTION-REQUIRED` (never subscribed, expired, or canceled). Rejected before any ownership, credit, or validation check. Only `GET /me`, `GET /credits/costs`, and `GET /credits/history` are reachable without a paid subscription.
- `405` — The URL exists but not for this HTTP method — e.g. `GET /presets/faceless/{preset}`, which is only defined for `PATCH` and `DELETE`. Check the verb against the endpoint reference; the `Allow` header lists the methods this path accepts.
- `409` — Email address not verified. The token is valid and the account exists, but the user has not confirmed their email address. This gate fronts every v2 endpoint. Have the user complete verification, then retry.
- `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 **paid** subscription. The v2 public API is paid-only — every faceless and preset endpoint (reads included) requires an active paid subscription, and trials are not eligible. Two codes distinguish the cause on this `403`: `PAID-SUBSCRIPTION-REQUIRED` (subscribed but on a trial — access unlocks automatically once the subscription becomes paid) and `SUBSCRIPTION-REQUIRED` (never subscribed, expired, or canceled). Rejected before any ownership, credit, or validation check. Only `GET /me`, `GET /credits/costs`, and `GET /credits/history` are reachable without a paid subscription.
- `405` — The URL exists but not for this HTTP method — e.g. `GET /presets/faceless/{preset}`, which is only defined for `PATCH` and `DELETE`. Check the verb against the endpoint reference; the `Allow` header lists the methods this path accepts.
- `409` — Email address not verified. The token is valid and the account exists, but the user has not confirmed their email address. This gate fronts every v2 endpoint. Have the user complete verification, then retry.
- `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"
  }
}
```

### 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.)
- `405` — The URL exists but not for this HTTP method — e.g. `GET /presets/faceless/{preset}`, which is only defined for `PATCH` and `DELETE`. Check the verb against the endpoint reference; the `Allow` header lists the methods this path accepts.
- `409` — Email address not verified. The token is valid and the account exists, but the user has not confirmed their email address. This gate fronts every v2 endpoint. Have the user complete verification, then retry.
- `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 **paid** subscription. The v2 public API is paid-only — every faceless and preset endpoint (reads included) requires an active paid subscription, and trials are not eligible. Two codes distinguish the cause on this `403`: `PAID-SUBSCRIPTION-REQUIRED` (subscribed but on a trial — access unlocks automatically once the subscription becomes paid) and `SUBSCRIPTION-REQUIRED` (never subscribed, expired, or canceled). Rejected before any ownership, credit, or validation check. Only `GET /me`, `GET /credits/costs`, and `GET /credits/history` are reachable without a paid subscription.
- `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.
- `405` — The URL exists but not for this HTTP method — e.g. `GET /presets/faceless/{preset}`, which is only defined for `PATCH` and `DELETE`. Check the verb against the endpoint reference; the `Allow` header lists the methods this path accepts.
- `409` — Email address not verified. The token is valid and the account exists, but the user has not confirmed their email address. This gate fronts every v2 endpoint. Have the user complete verification, then retry.
- `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).
