<!-- Generated from openapi/v2.json (md/text-to-scene.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-Scene

A storyboard editor: generate, reorder, chain, and render scenes into a video.

## Models, formats & pricing

Text-to-Scene renders clips with the same engines as Text-to-Video. Fetch the
live list — with the `id` to send as `clip_engine_id` and per-engine credit
pricing — from [`GET /faceless/options?only=clip_engines`](#tag/faceless-videos/GET/faceless/options).
Each engine's supported aspect ratios, resolutions, and durations are listed in
the `modifiers` of [`GET /text-to-video/models`](#tag/text-to-video/GET/text-to-video/models) —
match entries by `slug`.

Snapshot of the current lineup (the endpoints above are the live source of truth;
`clip_engine_id` values vary per environment, so always take the `id` from
`/faceless/options`):

| Model | Slug | Aspect ratios | Resolutions | Clip duration (s) | Credits / sec |
|---|---|---|---|---|---|
| Veo 3.1 | `veo-3-1` | 16:9, 9:16 | 720p, 1080p | 4, 6, 8 | 6 – 11 |
| Veo 3.1 Fast | `veo-3-1-fast` | 16:9, 9:16 | 720p, 1080p | 4, 6, 8 | 3 – 4 |
| Omni Flash 1.1 | `omni-flash-1-1` | 16:9, 9:16 | 360p, 720p, 1080p, 4k | 3 – 10 | 1 – 9 |
| Veo 3 | `veo-3` | 16:9, 9:16 | 720p, 1080p | 4, 6, 8 | 6 – 11 |
| Veo 3 Fast | `veo-3-fast` | 16:9, 9:16 | 720p, 1080p | 4, 6, 8 | 3 – 4 |
| Seedance 2 | `seedance-2` | 16:9, 9:16, 1:1, 4:3, 3:4, 21:9, auto | 480p, 720p, 1080p | 4 – 15 | 2.28 – 12.16 |
| Seedance 2 Fast | `seedance-2-fast` | 16:9, 9:16, 1:1, 4:3, 3:4, 21:9, auto | 480p, 720p | 4 – 15 | 1.82 – 3.9 |
| Seedance 2 Mini | `seedance-2-mini` | 16:9, 9:16, 1:1, 4:3, 3:4, 21:9, auto | 480p, 720p | 4 – 15 | 2 – 3 |
| Seedance 1.5 Pro | `seedance-1-5-pro` | 16:9, 9:16, auto | 720p, 1080p | 5, 8, 12 | 0.68 – 3.17 |
| Seedance Pro | `seedance-1-pro` | 16:9, 9:16, auto | 720p, 1080p | 5, 8, 12 | 1.34 – 3.18 |
| Kling 3.0 Omni | `kling-3-omni` | 16:9, 9:16, 1:1, auto | 720p, 1080p | 3 – 15 | 5 – 8 |
| Kling O1 | `kling-o1` | 16:9, 9:16, 1:1, auto | 720p, 1080p | 3 – 10 | 3 – 5 |
| Kling 2.6 Pro | `kling-2-6-pro` | 16:9, 9:16, 1:1, auto | 1080p | 5, 10 | 2 – 4 |
| Minimax Hailuo | `minimax-hailuo-02` | 16:9, 9:16, 1:1 | 512P, 768P, 1080P | 6, 10 | 1.33 |
| Minimax Hailuo 2.3 | `minimax-hailuo-2-3` | 16:9, 9:16, 1:1, auto | 768P, 1080P | 6, 10 | 1.33 – 2 |
| Grok Imagine Video | `grok-imagine-video` | 16:9, 9:16, 3:4, 4:3, 1:1, 3:2, 2:3, auto | 480p, 720p | 1 – 15 | 2 |
| Happy Horse | `happy-horse-1-0` | 16:9, 9:16, 1:1, 4:3, 3:4, auto | 720p, 1080p | 3 – 15 | 4 – 8 |

Where a credit range is shown, the exact rate depends on the selected
resolution and/or audio toggle — each engine's `cost` array in
`/faceless/options?only=clip_engines` lists the exact rate per combination.
The storyboard `settings.aspect_ratio` and `settings.resolution` must be
values the chosen engine supports. For the exact credit total of a storyboard,
call `GET /text-to-scene/{storyboard}/estimate` before rendering.

## POST /text-to-scene

**Create a storyboard** (operationId: `createTextToScene`)

Creates a new Text-to-Scene storyboard in draft state. Choose `mode`: `wizard` (guided prompt-to-storyboard generation) or `manual` (build scenes yourself). Keep the returned `id`/`uuid` for follow-up calls.

**Requires an active subscription.**

### Request body

Required.

- `title` (string, required) — Display name of the storyboard (max 255 characters).
- `mode` (string, required) — Authoring mode: `wizard` (guided prompt-to-storyboard generation) or `manual` (you build scenes yourself). Allowed values: `wizard`, `manual`.

Example request:

```json
{
  "title": "Deep-sea documentary",
  "mode": "wizard"
}
```

### Response `201`

```json
{
  "message": "Success.",
  "status": 201,
  "data": {
    "id": 1,
    "uuid": "sb_abc123",
    "status": "wizard",
    "prompt": "A documentary about deep-sea creatures.",
    "settings": {
      "audio": true,
      "aspect_ratio": "16:9",
      "resolution": "1080p"
    },
    "duration": [
      5,
      6,
      5
    ],
    "video": {
      "id": 10,
      "user_id": 1,
      "idea_id": null,
      "scheduler_id": null,
      "title": "Deep-sea documentary",
      "type": "text_to_scene",
      "url": null,
      "status": "rendering",
      "retries": 0,
      "hash": "abc123",
      "synced_at": null,
      "metadata": {
        "ai_labels": true,
        "custom_description": null
      },
      "failure": null,
      "created_at": "2026-01-01T12:00:00.000000Z",
      "updated_at": "2026-01-01T12:00:00.000000Z"
    },
    "scenes": [
      {
        "id": 12,
        "uuid": "scn_abc123",
        "order": 0,
        "video_prompt": "Wide shot of a glowing coral reef.",
        "duration": 5,
        "status": "draft",
        "error": null,
        "created_at": "2026-01-01T12:00:00.000000Z",
        "updated_at": "2026-01-01T12:00:00.000000Z"
      }
    ],
    "created_at": "2026-01-01T12:00:00.000000Z",
    "updated_at": "2026-01-01T12:00:00.000000Z"
  }
}
```

### 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` — 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-scene/{id}

**Get a storyboard** (operationId: `getTextToScene`)

Returns a single storyboard you own, including its scenes and embedded render state under `data.video`. Responds with `404` when the id does not exist or belongs to another account.

### Parameters

- `id` (path, string, required) — Identifier of the faceless video. Example: `1`

### Response `200`

```json
{
  "message": "Success.",
  "status": 200,
  "data": {
    "id": 1,
    "uuid": "sb_abc123",
    "status": "ready",
    "prompt": "A documentary about deep-sea creatures.",
    "settings": {
      "audio": true,
      "aspect_ratio": "16:9",
      "resolution": "1080p"
    },
    "duration": [
      5,
      6,
      5
    ],
    "video": {
      "id": 10,
      "user_id": 1,
      "idea_id": null,
      "scheduler_id": null,
      "title": "Deep-sea documentary",
      "type": "text_to_scene",
      "url": null,
      "status": "rendering",
      "retries": 0,
      "hash": "abc123",
      "synced_at": null,
      "metadata": {
        "ai_labels": true,
        "custom_description": null
      },
      "failure": null,
      "created_at": "2026-01-01T12:00:00.000000Z",
      "updated_at": "2026-01-01T12:00:00.000000Z"
    },
    "scenes": [
      {
        "id": 12,
        "uuid": "scn_abc123",
        "order": 0,
        "video_prompt": "Wide shot of a glowing coral reef.",
        "duration": 5,
        "status": "draft",
        "error": null,
        "created_at": "2026-01-01T12:00:00.000000Z",
        "updated_at": "2026-01-01T12:00:00.000000Z"
      }
    ],
    "created_at": "2026-01-01T12:00:00.000000Z",
    "updated_at": "2026-01-01T12:00:00.000000Z"
  }
}
```

### 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
- `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).

## PATCH /text-to-scene/{id}

**Update a storyboard** (operationId: `updateTextToScene`)

Updates the storyboard's render settings (audio, aspect ratio, resolution). Only the fields you send are changed.

`aspect_ratio` and `resolution` must come from the storyboard's clip engine's supported options — a value outside them returns `422` (e.g. `The selected aspect ratio is not supported by this engine.`).

### Parameters

- `id` (path, string, required) — Identifier of the faceless video. Example: `1`

### Request body

Optional.

- `settings` (object) — Render settings for the storyboard. Only the fields you send are changed.
  - `audio` (boolean) — Whether the rendered video includes generated audio.
  - `aspect_ratio` (string) — Output aspect ratio (e.g. "16:9", "9:16"). Must be a value the chosen clip engine supports — see its `modifiers` in `GET /text-to-video/models`.
  - `resolution` (string) — Output resolution (e.g. "720p", "1080p"). Must be a value the chosen clip engine supports — see its `modifiers` in `GET /text-to-video/models`.

Example request:

```json
{
  "settings": {
    "audio": true,
    "aspect_ratio": "16:9",
    "resolution": "1080p"
  }
}
```

### Response `200`

```json
{
  "message": "Success.",
  "status": 200,
  "data": {
    "id": 1,
    "uuid": "sb_abc123",
    "status": "ready",
    "prompt": "A documentary about deep-sea creatures.",
    "settings": {
      "audio": true,
      "aspect_ratio": "16:9",
      "resolution": "1080p"
    },
    "duration": [
      5,
      6,
      5
    ],
    "video": {
      "id": 10,
      "user_id": 1,
      "idea_id": null,
      "scheduler_id": null,
      "title": "Deep-sea documentary",
      "type": "text_to_scene",
      "url": null,
      "status": "rendering",
      "retries": 0,
      "hash": "abc123",
      "synced_at": null,
      "metadata": {
        "ai_labels": true,
        "custom_description": null
      },
      "failure": null,
      "created_at": "2026-01-01T12:00:00.000000Z",
      "updated_at": "2026-01-01T12:00:00.000000Z"
    },
    "scenes": [
      {
        "id": 12,
        "uuid": "scn_abc123",
        "order": 0,
        "video_prompt": "Wide shot of a glowing coral reef.",
        "duration": 5,
        "status": "draft",
        "error": null,
        "created_at": "2026-01-01T12:00:00.000000Z",
        "updated_at": "2026-01-01T12:00:00.000000Z"
      }
    ],
    "created_at": "2026-01-01T12:00:00.000000Z",
    "updated_at": "2026-01-01T12:00:00.000000Z"
  }
}
```

### 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
- `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).

## POST /text-to-scene/{storyboard}/context

**Generate context questions** (operationId: `generateTextToSceneContext`)

Generates dynamic creative-context questions from the supplied video idea to steer storyboard generation. Returns exactly three question sets under `data` — `purpose`, `style`, and `audience`, each with four tailored options. Free — no credits charged.

### Parameters

- `storyboard` (path, integer, required) — The storyboard ID Example: `1`

### Request body

Required.

- `prompt` (string, required) — Prompt to expand into narrative context for the storyboard (max 5000 characters).

Example request:

```json
{
  "prompt": "A gripping documentary about the creatures of the deep ocean."
}
```

### Response `200`

```json
{
  "message": "Success.",
  "status": 200,
  "data": [
    {
      "key": "purpose",
      "question": "What is the main goal of this documentary?",
      "options": [
        "Educate curious viewers",
        "Raise ocean awareness",
        "Entertain with wonder",
        "Inspire conservation"
      ]
    },
    {
      "key": "style",
      "question": "What visual and emotional treatment fits best?",
      "options": [
        "Moody cinematic slow-burn",
        "Bright nature-doc clarity",
        "Tense thriller pacing",
        "Dreamy ambient drift"
      ]
    },
    {
      "key": "audience",
      "question": "Who is this for and where will they watch?",
      "options": [
        "Science fans on YouTube",
        "Students on TikTok",
        "Educators on LinkedIn",
        "General viewers on Reels"
      ]
    }
  ]
}
```

### Errors

- `401` — Unauthenticated
- `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 ownership or precondition logic. The v2 public API has no free tier. 2. **Authorization / ownership or precondition failure** — the subscription is active but the action is not allowed: the resource belongs to another account, or a render precondition is unmet (e.g. `A script is required before rendering.`, `Voice was not provided.`, the video is busy, or storage is full).
- `404` — Not found
- `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).

## POST /text-to-scene/{storyboard}/prompt

**Generate the storyboard from its prompt** (operationId: `generateTextToScenePrompt`)

💳 Charges credits. Kicks off asynchronous generation of the storyboard (subjects, hero image, scene prompts) from its source prompt. Returns HTTP 202; poll `GET /text-to-scene/{id}` and read `data.status`. Send the body as `application/json`, or as `multipart/form-data` to attach reference image files.

`clip_engine_id` must be a **text-to-video (clip) engine** — pick an id from `GET /faceless/options` (`clip_engines`); an image-engine or unknown id returns `422`.

### Parameters

- `storyboard` (path, integer, required) — The storyboard ID Example: `1`

### Request body

Required.

- `prompt` (string, required) — The prompt describing the video to generate (max 5000 characters).
- `clip_engine_id` (integer, required) — Identifier of the text-to-video clip engine to use (see faceless options `clip_engines`).
- `duration` (object, required) — Target duration range for the generated video, in seconds.
  - `min` (integer, required) — Minimum total duration in seconds.
  - `max` (integer, required) — Maximum total duration in seconds.
- `settings` (object) — Optional render settings for the storyboard.
  - `audio` (boolean) — Whether the rendered video includes generated audio.
  - `aspect_ratio` (string) — Output aspect ratio (e.g. "16:9", "9:16"). Must be a value the chosen clip engine supports — see its `modifiers` in `GET /text-to-video/models`.
  - `resolution` (string) — Output resolution (e.g. "720p", "1080p"). Must be a value the chosen clip engine supports — see its `modifiers` in `GET /text-to-video/models`.
- `references` (array of string) — Optional reference images to steer the visual style — up to 5. Send as file parts via `multipart/form-data`.
- `context` (array of object) — Optional guided context Q&A pairs that shape the generation — up to 3.
  - `key` (string, required) — The context dimension this answer addresses. Allowed values: `purpose`, `style`, `audience`.
  - `question` (string, required) — The context question (max 500 characters).
  - `answer` (string, required) — The answer to the context question (max 500 characters).

Example request:

```json
{
  "prompt": "A documentary about deep-sea creatures.",
  "clip_engine_id": 12,
  "duration": {
    "min": 30,
    "max": 60
  }
}
```

### Response `202`

```json
{
  "message": "Success.",
  "status": 202,
  "data": {
    "id": 1,
    "uuid": "sb_abc123",
    "status": "generating:prompts",
    "prompt": "A documentary about deep-sea creatures.",
    "settings": {
      "audio": true,
      "aspect_ratio": "16:9",
      "resolution": "1080p"
    },
    "duration": [
      5,
      6,
      5
    ],
    "video": {
      "id": 10,
      "user_id": 1,
      "idea_id": null,
      "scheduler_id": null,
      "title": "Deep-sea documentary",
      "type": "text_to_scene",
      "url": null,
      "status": "rendering",
      "retries": 0,
      "hash": "abc123",
      "synced_at": null,
      "metadata": {
        "ai_labels": true,
        "custom_description": null
      },
      "failure": null,
      "created_at": "2026-01-01T12:00:00.000000Z",
      "updated_at": "2026-01-01T12:00:00.000000Z"
    },
    "scenes": [
      {
        "id": 12,
        "uuid": "scn_abc123",
        "order": 0,
        "video_prompt": "Wide shot of a glowing coral reef.",
        "duration": 5,
        "status": "draft",
        "error": null,
        "created_at": "2026-01-01T12:00:00.000000Z",
        "updated_at": "2026-01-01T12:00:00.000000Z"
      }
    ],
    "created_at": "2026-01-01T12:00:00.000000Z",
    "updated_at": "2026-01-01T12:00:00.000000Z"
  },
  "meta": {
    "credits": {
      "cost": 15,
      "remaining": 885
    }
  }
}
```

### Errors

- `401` — Unauthenticated
- `402` — Insufficient credits. The request is authenticated and valid, but the account balance is too low — top up and retry.
- `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 ownership or precondition logic. The v2 public API has no free tier. 2. **Authorization / ownership or precondition failure** — the subscription is active but the action is not allowed: the resource belongs to another account, or a render precondition is unmet (e.g. `A script is required before rendering.`, `Voice was not provided.`, the video is busy, or storage is full).
- `404` — Not found
- `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.
- `413` — The request body exceeded the server upload limit. This is checked before validation, so the response names no specific field. Send fewer or smaller files — a single file over the documented per-field limit returns `422` on that key instead.
- `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).

## POST /text-to-scene/{storyboard}/scenes

**Add a scene** (operationId: `createTextToSceneScene`)

Adds a new scene to the storyboard at the given order with a video prompt and duration.

### Parameters

- `storyboard` (path, integer, required) — The storyboard ID Example: `1`

### Request body

Optional.

- `order` (integer) — Zero-based position of the scene within the storyboard.
- `raw_video_prompt` (string) — The scene's video prompt (max 5000 characters).
- `duration` (integer) — Scene duration in seconds. Validated against the storyboard's clip engine: engines with a fixed duration list accept only those values (e.g. Veo 3.1 accepts 4, 6, or 8), range engines accept any integer between their min and max. Discover each engine's duration modifier via `GET /faceless/options?only=clip_engines` or `GET /text-to-video/models`.

Example request:

```json
{
  "order": 0,
  "raw_video_prompt": "Wide shot of a glowing coral reef.",
  "duration": 5
}
```

### Response `201`

```json
{
  "message": "Success.",
  "status": 201,
  "data": {
    "id": 12,
    "uuid": "scn_abc123",
    "order": 0,
    "video_prompt": "Wide shot of a glowing coral reef.",
    "duration": 5,
    "status": "draft",
    "error": null,
    "created_at": "2026-01-01T12:00:00.000000Z",
    "updated_at": "2026-01-01T12:00:00.000000Z"
  }
}
```

### Errors

- `401` — Unauthenticated
- `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 ownership or precondition logic. The v2 public API has no free tier. 2. **Authorization / ownership or precondition failure** — the subscription is active but the action is not allowed: the resource belongs to another account, or a render precondition is unmet (e.g. `A script is required before rendering.`, `Voice was not provided.`, the video is busy, or storage is full).
- `404` — Not found
- `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).

## PATCH /text-to-scene/{storyboard}/scenes/{scene}

**Update a scene** (operationId: `updateTextToSceneScene`)

Updates a scene's order, video prompt, or duration. Only the fields you send are changed.

### Parameters

- `storyboard` (path, integer, required) — The storyboard ID Example: `1`
- `scene` (path, integer, required) — The scene ID Example: `12`

### Request body

Optional.

- `order` (integer) — Zero-based position of the scene within the storyboard.
- `raw_video_prompt` (string) — The scene's video prompt (max 5000 characters).
- `duration` (integer) — Scene duration in seconds. Validated against the storyboard's clip engine: engines with a fixed duration list accept only those values (e.g. Veo 3.1 accepts 4, 6, or 8), range engines accept any integer between their min and max. Discover each engine's duration modifier via `GET /faceless/options?only=clip_engines` or `GET /text-to-video/models`.

Example request:

```json
{
  "raw_video_prompt": "Close-up of a translucent jellyfish pulsing with light.",
  "duration": 6
}
```

### Response `200`

```json
{
  "message": "Success.",
  "status": 200,
  "data": {
    "id": 12,
    "uuid": "scn_abc123",
    "order": 0,
    "video_prompt": "Wide shot of a glowing coral reef.",
    "duration": 5,
    "status": "draft",
    "error": null,
    "created_at": "2026-01-01T12:00:00.000000Z",
    "updated_at": "2026-01-01T12:00:00.000000Z"
  }
}
```

### Errors

- `401` — Unauthenticated
- `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 ownership or precondition logic. The v2 public API has no free tier. 2. **Authorization / ownership or precondition failure** — the subscription is active but the action is not allowed: the resource belongs to another account, or a render precondition is unmet (e.g. `A script is required before rendering.`, `Voice was not provided.`, the video is busy, or storage is full).
- `404` — Not found
- `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).

## DELETE /text-to-scene/{storyboard}/scenes/{scene}

**Delete a scene** (operationId: `deleteTextToSceneScene`)

Deletes a scene from the storyboard. Returns HTTP 202 while related cleanup runs asynchronously.

### Parameters

- `storyboard` (path, integer, required) — The storyboard ID Example: `1`
- `scene` (path, integer, required) — The scene ID Example: `12`

### Response `202`

```json
{
  "message": "Success.",
  "status": 202,
  "data": {
    "message": "Accepted"
  }
}
```

### Errors

- `401` — Unauthenticated
- `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 ownership or precondition logic. The v2 public API has no free tier. 2. **Authorization / ownership or precondition failure** — the subscription is active but the action is not allowed: the resource belongs to another account, or a render precondition is unmet (e.g. `A script is required before rendering.`, `Voice was not provided.`, the video is busy, or storage is full).
- `404` — Not found
- `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-scene/{storyboard}/scenes/{scene}/status

**Get a scene's status** (operationId: `getTextToSceneSceneStatus`)

Returns the current clip-generation status of a single scene — a lightweight endpoint for polling while a scene's clip is generating.

### Parameters

- `storyboard` (path, integer, required) — The storyboard ID Example: `1`
- `scene` (path, integer, required) — The scene ID Example: `12`

### Response `200`

```json
{
  "message": "Success.",
  "status": 200,
  "data": {
    "id": 12,
    "status": "clip:generating",
    "error": null
  }
}
```

### Errors

- `401` — Unauthenticated
- `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 ownership or precondition logic. The v2 public API has no free tier. 2. **Authorization / ownership or precondition failure** — the subscription is active but the action is not allowed: the resource belongs to another account, or a render precondition is unmet (e.g. `A script is required before rendering.`, `Voice was not provided.`, the video is busy, or storage is full).
- `404` — Not found
- `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).

## PUT /text-to-scene/{storyboard}/scenes/sort

**Reorder scenes** (operationId: `sortTextToSceneScenes`)

Moves a scene to follow another scene (or to the start when `after_id` is null), reordering the storyboard.

### Parameters

- `storyboard` (path, integer, required) — The storyboard ID Example: `1`

### Request body

Required.

- `scene_id` (integer, required) — Identifier of the scene to move.
- `after_id` (integer|null) — Identifier of the scene it should follow, or null to move it to the start.

Example request:

```json
{
  "scene_id": 12,
  "after_id": 8
}
```

### Response `200`

```json
{
  "message": "Success.",
  "status": 200,
  "data": {
    "message": "Scenes sorted successfully"
  }
}
```

### Errors

- `401` — Unauthenticated
- `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 ownership or precondition logic. The v2 public API has no free tier. 2. **Authorization / ownership or precondition failure** — the subscription is active but the action is not allowed: the resource belongs to another account, or a render precondition is unmet (e.g. `A script is required before rendering.`, `Voice was not provided.`, the video is busy, or storage is full).
- `404` — Not found
- `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).

## PUT /text-to-scene/{storyboard}/scenes/{scene}/frames

**Assign scene frames** (operationId: `assignTextToSceneFrames`)

Sets (or clears) the first and/or last frame assets for a scene. Pass null to clear a frame.

### Parameters

- `storyboard` (path, integer, required) — The storyboard ID Example: `1`
- `scene` (path, integer, required) — The scene ID Example: `12`

### Request body

Optional.

- `first_frame_id` (integer|null) — Asset id to use as the scene's first frame, or null to clear it.
- `last_frame_id` (integer|null) — Asset id to use as the scene's last frame, or null to clear it.

Example request:

```json
{
  "first_frame_id": 101,
  "last_frame_id": 102
}
```

### Response `200`

```json
{
  "message": "Success.",
  "status": 200,
  "data": {
    "id": 12,
    "uuid": "scn_abc123",
    "order": 0,
    "video_prompt": "Wide shot of a glowing coral reef.",
    "duration": 5,
    "status": "draft",
    "error": null,
    "created_at": "2026-01-01T12:00:00.000000Z",
    "updated_at": "2026-01-01T12:00:00.000000Z"
  }
}
```

### Errors

- `401` — Unauthenticated
- `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 ownership or precondition logic. The v2 public API has no free tier. 2. **Authorization / ownership or precondition failure** — the subscription is active but the action is not allowed: the resource belongs to another account, or a render precondition is unmet (e.g. `A script is required before rendering.`, `Voice was not provided.`, the video is busy, or storage is full).
- `404` — Not found
- `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).

## PUT /text-to-scene/{storyboard}/scenes/{scene}/chain

**Chain a scene** (operationId: `chainTextToSceneScene`)

Chains this scene to another scene so the referenced scene's last frame seeds this scene's first frame, keeping motion continuous.

### Parameters

- `storyboard` (path, integer, required) — The storyboard ID Example: `1`
- `scene` (path, integer, required) — The scene ID Example: `12`

### Request body

Required.

- `scene_id` (integer, required) — Identifier of the scene to chain to (its last frame seeds this scene's first frame).

Example request:

```json
{
  "scene_id": 11
}
```

### Response `200`

```json
{
  "message": "Success.",
  "status": 200,
  "data": {
    "id": 12,
    "uuid": "scn_abc123",
    "order": 0,
    "video_prompt": "Wide shot of a glowing coral reef.",
    "duration": 5,
    "status": "draft",
    "error": null,
    "created_at": "2026-01-01T12:00:00.000000Z",
    "updated_at": "2026-01-01T12:00:00.000000Z"
  }
}
```

### Errors

- `401` — Unauthenticated
- `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 ownership or precondition logic. The v2 public API has no free tier. 2. **Authorization / ownership or precondition failure** — the subscription is active but the action is not allowed: the resource belongs to another account, or a render precondition is unmet (e.g. `A script is required before rendering.`, `Voice was not provided.`, the video is busy, or storage is full).
- `404` — Not found
- `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).

## DELETE /text-to-scene/{storyboard}/scenes/{scene}/chain

**Unchain a scene** (operationId: `unchainTextToSceneScene`)

Removes the chaining link from a scene so its first frame is no longer seeded by another scene.

### Parameters

- `storyboard` (path, integer, required) — The storyboard ID Example: `1`
- `scene` (path, integer, required) — The scene ID Example: `12`

### Response `200`

```json
{
  "message": "Success.",
  "status": 200,
  "data": {
    "id": 12,
    "uuid": "scn_abc123",
    "order": 0,
    "video_prompt": "Wide shot of a glowing coral reef.",
    "duration": 5,
    "status": "draft",
    "error": null,
    "created_at": "2026-01-01T12:00:00.000000Z",
    "updated_at": "2026-01-01T12:00:00.000000Z"
  }
}
```

### Errors

- `401` — Unauthenticated
- `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 ownership or precondition logic. The v2 public API has no free tier. 2. **Authorization / ownership or precondition failure** — the subscription is active but the action is not allowed: the resource belongs to another account, or a render precondition is unmet (e.g. `A script is required before rendering.`, `Voice was not provided.`, the video is busy, or storage is full).
- `404` — Not found
- `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).

## POST /text-to-scene/{storyboard}/moodboard/upload

**Add moodboard references** (operationId: `uploadTextToSceneMoodboard`)

Attaches reference imagery to the storyboard's moodboard to steer its visual style. Send as `multipart/form-data` with one or more `files[]` image parts (jpeg, png, jpg, webp; each 512×512–4096×4096, max 10 MB). Returns the moodboard entries.

### Parameters

- `storyboard` (path, integer, required) — The storyboard ID Example: `1`

### Request body

Required. Reference images to attach to the storyboard moodboard. Send as `multipart/form-data` with one or more `files[]` image parts.

- `files` (array of string, required) — One or more reference image files (jpeg, png, jpg, webp; each between 512×512 and 4096×4096 pixels, max 10 MB). Sent as repeated `files[]` multipart parts.

Example request:

```json
{
  "files": [
    "<binary image file>"
  ]
}
```

### Response `201`

```json
{
  "message": "Success.",
  "status": 201,
  "data": [
    {
      "id": 1,
      "asset_id": 5,
      "source": "upload",
      "type": "ai-image",
      "status": "success",
      "url": "https://cdn.syllaby.dev/moodboard/1.png",
      "created_at": "2026-01-01T12:00:00.000000Z"
    }
  ]
}
```

### Errors

- `401` — Unauthenticated
- `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 ownership or precondition logic. The v2 public API has no free tier. 2. **Authorization / ownership or precondition failure** — the subscription is active but the action is not allowed: the resource belongs to another account, or a render precondition is unmet (e.g. `A script is required before rendering.`, `Voice was not provided.`, the video is busy, or storage is full).
- `404` — Not found
- `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.
- `413` — The request body exceeded the server upload limit. This is checked before validation, so the response names no specific field. Send fewer or smaller files — a single file over the documented per-field limit returns `422` on that key instead.
- `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).

## POST /text-to-scene/{storyboard}/moodboard/generate

**Generate a moodboard image** (operationId: `generateTextToSceneMoodboard`)

💳 Charges credits. Generates a moodboard image from the supplied prompt (optionally enhanced) to steer the storyboard's visual style. Returns HTTP 202.

### Parameters

- `storyboard` (path, integer, required) — The storyboard ID Example: `1`

### Request body

Required.

- `prompt` (string, required) — Prompt describing the moodboard imagery to generate (max 5000 characters).
- `enhance` (boolean) — Whether to enhance the prompt before generating.
- `references` (array of integer) — Optional asset ids to use as visual references.

Example request:

```json
{
  "prompt": "Warm cinematic color palette with deep ocean blues.",
  "enhance": true
}
```

### Response `202`

```json
{
  "message": "Success.",
  "status": 202,
  "data": {
    "id": 1,
    "asset_id": 5,
    "source": "generated",
    "type": "ai-image",
    "status": "success",
    "url": "https://cdn.syllaby.dev/moodboard/1.png",
    "created_at": "2026-01-01T12:00:00.000000Z"
  },
  "meta": {
    "credits": {
      "cost": 1,
      "remaining": 899
    }
  }
}
```

### Errors

- `401` — Unauthenticated
- `402` — Insufficient credits. The request is authenticated and valid, but the account balance is too low — top up and retry.
- `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 ownership or precondition logic. The v2 public API has no free tier. 2. **Authorization / ownership or precondition failure** — the subscription is active but the action is not allowed: the resource belongs to another account, or a render precondition is unmet (e.g. `A script is required before rendering.`, `Voice was not provided.`, the video is busy, or storage is full).
- `404` — Not found
- `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).

## POST /text-to-scene/{storyboard}/clips

**Generate scene clips** (operationId: `generateTextToSceneClips`)

💳 Charges credits. Queues clip generation for the given scenes (optionally enhancing each scene's prompt first). Returns HTTP 202; poll each scene's status or `GET /text-to-scene/{id}`.

### Parameters

- `storyboard` (path, integer, required) — The storyboard ID Example: `1`

### Request body

Required.

- `scenes` (array of object, required) — The scenes to generate clips for (at least one).
  - `id` (integer, required) — Identifier of a scene belonging to this storyboard.
  - `enhance` (boolean, required) — Whether to enhance the scene's prompt before generating its clip.

Example request:

```json
{
  "scenes": [
    {
      "id": 12,
      "enhance": true
    }
  ]
}
```

### Response `202`

```json
{
  "message": "Success.",
  "status": 202,
  "data": [
    {
      "id": 12,
      "uuid": "scn_abc123",
      "order": 0,
      "video_prompt": "Wide shot of a glowing coral reef.",
      "duration": 5,
      "status": "clip:queued",
      "error": null,
      "created_at": "2026-01-01T12:00:00.000000Z",
      "updated_at": "2026-01-01T12:00:00.000000Z"
    }
  ],
  "meta": {
    "credits": {
      "cost": 30,
      "remaining": 870
    }
  }
}
```

### Errors

- `401` — Unauthenticated
- `402` — Insufficient credits. The request is authenticated and valid, but the account balance is too low — top up and retry.
- `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 ownership or precondition logic. The v2 public API has no free tier. 2. **Authorization / ownership or precondition failure** — the subscription is active but the action is not allowed: the resource belongs to another account, or a render precondition is unmet (e.g. `A script is required before rendering.`, `Voice was not provided.`, the video is busy, or storage is full).
- `404` — Not found
- `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-scene/{storyboard}/estimate

**Estimate storyboard render credits** (operationId: `estimateTextToScene`)

Estimates the credits required to render the storyboard and compares them against your available balance.

### Parameters

- `storyboard` (path, integer, required) — The storyboard ID Example: `1`

### Response `200`

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

### Errors

- `401` — Unauthenticated
- `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 ownership or precondition logic. The v2 public API has no free tier. 2. **Authorization / ownership or precondition failure** — the subscription is active but the action is not allowed: the resource belongs to another account, or a render precondition is unmet (e.g. `A script is required before rendering.`, `Voice was not provided.`, the video is busy, or storage is full).
- `404` — Not found
- `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).

## POST /text-to-scene/{storyboard}/render

**Render the storyboard** (operationId: `renderTextToScene`)

Assembles the finished storyboard's scene clips into a video asynchronously and returns HTTP 202. Rendering does **not** charge credits — clips are billed when they are generated (`POST /text-to-scene/{storyboard}/clips`). Every scene must have a completed clip first. Track progress by polling `GET /text-to-scene/{id}` and reading `data.video.status`.

**Preconditions (both fail with `422`):**
- **A scene is missing a completed clip** → `422` `All scenes must have completed clips before rendering.` — generate clips first and poll each scene to `clip:completed`.
- **Already rendering** → `422` `This storyboard is already rendering.` — wait for `data.video.status` to leave `rendering`.

### Parameters

- `storyboard` (path, integer, required) — The storyboard ID Example: `1`

### Response `202`

```json
{
  "message": "Success.",
  "status": 202,
  "data": {
    "id": 1,
    "uuid": "sb_abc123",
    "status": "processing",
    "prompt": "A documentary about deep-sea creatures.",
    "settings": {
      "audio": true,
      "aspect_ratio": "16:9",
      "resolution": "1080p"
    },
    "duration": [
      5,
      6,
      5
    ],
    "video": {
      "id": 10,
      "user_id": 1,
      "idea_id": null,
      "scheduler_id": null,
      "title": "Deep-sea documentary",
      "type": "text_to_scene",
      "url": null,
      "status": "rendering",
      "retries": 0,
      "hash": "abc123",
      "synced_at": null,
      "metadata": {
        "ai_labels": true,
        "custom_description": null
      },
      "failure": null,
      "created_at": "2026-01-01T12:00:00.000000Z",
      "updated_at": "2026-01-01T12:00:00.000000Z"
    },
    "scenes": [
      {
        "id": 12,
        "uuid": "scn_abc123",
        "order": 0,
        "video_prompt": "Wide shot of a glowing coral reef.",
        "duration": 5,
        "status": "draft",
        "error": null,
        "created_at": "2026-01-01T12:00:00.000000Z",
        "updated_at": "2026-01-01T12:00:00.000000Z"
      }
    ],
    "created_at": "2026-01-01T12:00:00.000000Z",
    "updated_at": "2026-01-01T12:00:00.000000Z"
  }
}
```

### Errors

- `401` — Unauthenticated
- `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 ownership or precondition logic. The v2 public API has no free tier. 2. **Authorization / ownership or precondition failure** — the subscription is active but the action is not allowed: the resource belongs to another account, or a render precondition is unmet (e.g. `A script is required before rendering.`, `Voice was not provided.`, the video is busy, or storage is full).
- `404` — Not found
- `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` — Render preconditions not met — a scene is missing a completed clip, or the storyboard is already rendering.
- `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).
