[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) # Syllaby API — Getting started The Syllaby API lets you build Syllaby’s content-generation capabilities into your own apps and workflows. This **v2** release covers **faceless videos** and the resources around them — scripts, presets, assets, and account credits. More capabilities will be added over time. ## Typical flow (faceless video) **Standard flow (AI-generated or stock visuals):** 1. **Create** a draft video — `POST /faceless`. 2. **Add a script** — generate one with `PUT /faceless/{faceless}/scripts`, or supply your own. 3. **Configure** voice, genre, captions, and visuals — `PATCH /faceless/{faceless}`. Browse the available options at `GET /faceless/options`. 4. *(Optional)* **Estimate** the credit cost — `GET /faceless/{faceless}/estimate`. 5. **Render** — `POST /faceless/{faceless}/render` (returns `202`). 6. **Track progress** — poll `GET /faceless/{id}` and read `data.video.status` (`rendering` → `completed`/`failed`); the `video` object is always embedded. On failure, retry with `POST /faceless/{faceless}/retry`. See **Get your rendered video** below. 7. *(Optional)* **Export** a restyled cut — `POST /faceless/{faceless}/export`. **URL-based flow (build a video from a product/web page):** `create (type=url-based)` → `scrape-images` *(free)* → `scrape-script` *(charges `CONTENT_PROMPT_REQUESTED`, same as `PUT /scripts`)* → configure → render *(charges)* → poll. ## Build from a URL The `url-based` type lets you build a faceless video from the content of any publicly reachable web page (product listing, blog post, landing page) — Syllaby extracts images and can generate a matching script directly from the page. **Steps:** 1. **Create** — `POST /faceless` with `type: "url-based"`. The `type` is fixed at creation and cannot be changed later. 2. **Scrape images** — `POST /faceless/{faceless}/scrape-images` with `{ "url": "https://…" }`. **Free** — no credits charged. Extracts product/page images and attaches them to the video as ordered assets. 3. **Scrape script** — `POST /faceless/{faceless}/scrape-script` with the page URL plus generation options (`duration`, `style`, `tone`, `language`). **💳 Charges `CONTENT_PROMPT_REQUESTED` credits** (the same amount as `PUT /faceless/{faceless}/scripts`). Generates and stores a narration script written from the page content. You may skip this step and supply your own script via `PATCH /faceless/{faceless}` instead. 4. **Configure** — `PATCH /faceless/{faceless}` (voice, captions, transitions, etc.). 5. **Render** — `POST /faceless/{faceless}/render`. **💳 Charges render credits.** Requires that scrape-images has run at least once (otherwise returns `422 assets`). The scraped images become the visual slides. 6. **Poll** — `GET /faceless/{id}` until `data.video.status` is `completed`. > **Social and unsupported URLs are rejected.** URLs pointing to social-media platforms (YouTube, TikTok, Twitter/X, Instagram, Facebook, etc.) and any un-parseable or unreachable page return `422` on both scrape endpoints. ## Get your rendered video Rendering is asynchronous — the render call returns before the file exists. From request to playable file: 1. **Create** the faceless video — `POST /faceless`. Note the `id` field in the response. 2. *(Optional)* **Generate a script** — `PUT /faceless/{faceless}/scripts`. 3. **Render** — `POST /faceless/{faceless}/render`. Returns `202` immediately; `meta.credits` reports the charge. 4. **Poll** — `GET /faceless/{id}` on a sensible cadence (every 5–10 seconds is plenty). Every response embeds the render state under `data.video` — no `include` parameter needed. 5. **Done** — when `data.video.status` is `completed`, the playable file is at `data.video.url`. If it is `failed`, the reason is in `data.video.failure` (`code` and `message`); fix what you can and retry with `POST /faceless/{faceless}/retry`. > **Important — poll with the right identifier.** Use the faceless **`id`** from the create/render response, **not** `video_id`. They are different identifiers: `id` is the faceless video, `video_id` is its underlying video record. Calling `GET /faceless/{video_id}` returns `404`. ## Before you render — checklist Rendering charges credits and has a few preconditions. The render call validates them **before** anything is charged, so a missed step fails fast with a `402`/`403` (or `422`) and costs nothing. Before calling `POST /faceless/{faceless}/render`: 1. **Script set** — generate one with `PUT /faceless/{faceless}/scripts`, or supply your own via `PATCH /faceless/{faceless}`. Missing → **`403` `A script is required before rendering.`** 2. **Voice chosen** — set `voice_id` via `PATCH /faceless/{faceless}`. Missing → **`403` `Voice was not provided.`** (or `Voice not found.` for an unknown id). 3. **Options configured** — genre, captions, and visuals. Genre is **required** for `ai-visuals` / `ai-clips`; missing it → **`422` `A genre is required for AI visuals and AI clips.`** Browse valid ids/slugs at `GET /faceless/options`. 4. **Credits sufficient** — check the cost against your balance with `GET /faceless/{faceless}/estimate`. Too few → **`402` `INSUFFICIENT-CREDITS`** with `required` and `available` in the error body. 5. **For `url-based` videos** — run `POST /faceless/{faceless}/scrape-images` at least once before rendering. A url-based render with no scraped assets → **`422` `assets`** (the images become the visual slides; without them the render would produce an empty video). The video must also not be **busy** (already rendering or syncing) → **`403` `The video is processing currently. Please try again once finished`**. See each endpoint's *Preconditions & common errors* for the full list. ## Generate a clip from text (text-to-video) Short AI video clips from a text prompt, with optional reference frames. From prompt to playable file: 1. **Pick a model** — `GET /text-to-video/models`. Models are gated by your plan; each entry lists its modifier menus (duration, resolution, aspect ratio, audio, reference media) and per-second pricing. 2. *(Optional)* **Estimate** — `GET /text-to-video/estimate?model=` (plus any modifiers you will send). The figure is **exactly** what the create call charges for the same parameters — same defaults, same formula. Unlike the faceless render estimate, this is not an approximation. 3. **Create** — `POST /text-to-video` with the model, a prompt (5–1500 words), and any modifiers. Returns `202` immediately; `meta.credits.cost` is the exact amount charged. 4. **Poll** — `GET /text-to-video/{id}` with the **`id` from the create response**, every 5–10 seconds. 5. **Done** — when `data.asset.status` is `success`, the clip is at `data.asset.url`. If it is `failed`, the reason is in `data.asset.failure` and the charge is refunded automatically. `asset.status` is the field to branch on, and these are its **exact strings**: `draft` (just created — the create call's `202` response carries this) → `processing` (the provider is generating) → terminal `success` (then, and only then, `asset.url` is non-null) or `failed` (`asset.failure` carries the reason). ## Transcribe audio & video (speech-to-text) Accurate transcripts with word-level timestamps from any audio/video source — and transcript editing to correct captions before a render. Two flows: **Standalone transcription:** 1. **Create** — `POST /transcriptions` with a public https `url` (or an uploaded `file` via `multipart/form-data`). Returns `201` with a `pending` record; **charges 1 credit per started minute** once the source is probed (an unreadable or silent source fails before anything is charged). 2. **Poll** — `GET /transcriptions/{id}` every 5–10 seconds. `pending` → `processing` → terminal `completed` or `failed` (failures after the charge are refunded automatically). 3. **Done** — `text` carries the transcript and `words` the word-level timestamps. **Correct captions before render (faceless videos):** 1. **Read** — `GET /faceless/{faceless}/captions` once the video is transcribed. Segments carry their `index`, timings, and words; silence gaps are flagged (`gap: true`) and not editable. 2. **Edit** — `PUT /faceless/{faceless}/captions` with per-segment edits: `words` (one-for-one replacement, timestamps preserved) or `text` (full rewrite, words retimed within the segment window). 3. **Render/export** — the corrected captions are used by `POST /faceless/{faceless}/render` and `/export`. ## Generate speech from text (text-to-speech) Standalone narration: turn any script into a studio-quality mp3 with word-level timestamps. From script to audio: 1. **Pick a voice** — `GET /voices`. System voices plus your own voice clones (see the Voice Clones module), each with a free `preview` sample url. 2. **Create** — `POST /speech` with `voice_id` and `text` (up to 10,000 characters). **Charges 7 credits per started 1,000 characters.** Returns `201` immediately; `meta.credits.cost` is the exact amount charged. 3. **Poll** — `GET /speech/{id}` with the **`id` from the create response**, every 3–5 seconds. Short scripts resolve in seconds; long ones can take a few minutes. 4. **Done** — when `data.status` is `success`, the mp3 is at `data.url` and `data.words` carries word-level timestamps (ready-made caption input). On `failed`, `data.error` has the reason and the charge is refunded automatically. ## Create a launch clip from an idea A 20-second, 1080×1920 launch video from three inputs: you supply the product name, domain, and idea — the API writes the creative brief and renders the MP4. From idea to clip: 1. *(Optional)* **Estimate** — `GET /launch-clips/estimate`. The cost is a **flat 5 credits per clip**, independent of the idea. 2. **Create** — `POST /launch-clips` with `product_name`, `domain`, and `idea` (plus an optional `accent_color`). Returns `201` immediately; `meta.credits.cost` is the exact amount charged. 3. **Poll** — `GET /launch-clips/{id}` with the **`id` from the create response**, every 5–10 seconds. `planning` → `briefed` → `rendering` → terminal `completed` or `failed`. 4. **Done** — when `data.status` is `completed`, the MP4 is at `data.video.url`. On `failed`, `data.error` has the reason and the 5-credit charge is refunded automatically. ## Generate a stock-footage video (B-roll) B-roll videos assemble stock footage against a narration script. The flow mirrors the generic faceless flow with the `type` fixed to `b-roll`: 1. **Create** — `POST /b-roll` with a `title`. Keep the returned `id`. 2. **Add a script** — `PUT /b-roll/{faceless}/scripts` (or supply your own via `PATCH /b-roll/{id}`). 3. **Configure** — `PATCH /b-roll/{id}` to set voice, captions, transitions, and more. 4. *(Optional)* **Estimate** — `GET /b-roll/{faceless}/estimate`. 5. **Render** — `POST /b-roll/{faceless}/render`, then poll `GET /b-roll/{id}` until `data.video.status` is `completed`. ## Generate an AI-Visuals video AI-Visuals videos use AI-generated imagery for each scene. Same lifecycle as B-roll (`type` fixed to `ai-visuals`), plus per-scene asset control: 1. **Create → script → configure → render** — as for B-roll, under `/ai-visuals`. A `genre` is required at render time. 2. **Swap imagery** — `POST /ai-visuals/{faceless}/assets/upload` to add your own media (file or url). 3. **Regenerate a scene** — `POST /ai-visuals/{faceless}/assets/{asset}/regenerate` to re-roll a single scene image. ## Generate a video from a URL (URL-to-Video) URL-to-Video builds a video from a web page (`type` fixed to `url-based`). Scrape the page first, then render: 1. **Create** — `POST /url-to-video` with a `title`. 2. **Scrape images** — `POST /url-to-video/{faceless}/scrape-images` (free; required before rendering). 3. **Add a script** — `POST /url-to-video/{faceless}/scrape-script` (from the page) or `PUT /url-to-video/{faceless}/scripts` (from a topic). 4. **Configure → render** — `PATCH /url-to-video/{id}`, then `POST /url-to-video/{faceless}/render` and poll `GET /url-to-video/{id}`. ## Build a storyboard video (Text-to-Scene) Text-to-Scene is a storyboard editor: a video is a sequence of scenes you can generate, reorder, and chain for continuous motion. 1. **Create** — `POST /text-to-scene` (`mode` `wizard` or `manual`). Keep the returned `id`. 2. **Populate** — in `wizard` mode, `POST /text-to-scene/{storyboard}/prompt` generates scenes; in `manual` mode, add them with `POST /text-to-scene/{storyboard}/scenes`. 3. **Refine** — reorder (`scenes/sort`), assign frames (`scenes/{scene}/frames`), chain scenes (`scenes/{scene}/chain`), and steer style with the moodboard endpoints. 4. **Generate clips** — `POST /text-to-scene/{storyboard}/clips`, polling each scene's status. 5. **Render** — `POST /text-to-scene/{storyboard}/render`, then poll `GET /text-to-scene/{id}` until `data.video.status` is `completed`. ## Clone a voice Turn 1–3 audio samples into a reusable narration voice. 1. **Check slots** *(optional)* — `GET /voice-clones/slots`. If `available` is 0, you can buy another when `can_buy` is true (costs `cost` credits). 2. **Create** — `POST /voice-clones` (`multipart/form-data`) with `terms`, `name`, `gender`, and up to 3 `samples[]`. Add `purchase_addon: true` to buy a slot when yours are full. 3. **Poll** — `GET /voice-clones/{id}` until `data.status` is `completed`; the sample preview is at `data.preview_url`. 4. **Use it** — the clone appears among your narration voices for faceless renders. ## Discover content ideas Find and rank content ideas for a keyword. 1. **Discover** — `POST /ideas/discover` with a `keyword` and `network`. **Charges 15 credits** on any fruitful search (including cached re-searches); synchronous and can take up to ~2 minutes. 2. **List** — `GET /ideas?keyword=…&network=…` returns the ranked ideas. **Free**, paginated, and sortable (`sort=-volume` by default). > If a discover call times out, do **not** blindly retry — there is no idempotency key and a retry can charge another 15 credits. Reconcile with the free `GET /ideas` first. ## Create a consistent character Build a reusable AI character from a reference photo. 1. **Pick a genre** *(optional)* — `GET /characters/genres`. 2. **Create** — `POST /characters` (`multipart/form-data`) with an `image`. Keep the returned `id`. 3. **Preview** — `POST /characters/{id}/preview` with a `genre_id` (synchronous, up to ~90s, 5/min). Pick a candidate from `data.previews[]`. 4. **Train** — `POST /characters/{id}/train` with that `preview_id` plus `name`, `age`, `gender`. **Charges 10 credits.** Poll `GET /characters/{id}` until `data.status` is `ready`. ## Authentication All endpoints require a **Bearer token**. Send it on every request: ```http Authorization: Bearer ``` Generate a token from your Syllaby account settings. Treat it like a password — never expose it in client-side code. The v2 API is token-only and stateless: authenticate with `Authorization: Bearer ` on every request. Do **not** send cookies or a CSRF token — they are ignored. ## Base URL Production requests go to `https://api.syllaby.io/v2`. Every path in this reference is relative to that base. ## Subscription required Every generative endpoint requires an **active paid subscription** — reads included. Two distinct `403` codes tell you exactly why a caller was rejected, before any ownership, credit, or validation check runs: | Code | Who gets it | Message | |------|-------------|---------| | `PAID-SUBSCRIPTION-REQUIRED` | Subscribed but **on a trial** | `API access requires an active paid subscription. Trials are not eligible — start a paid subscription to unlock the API.` | | `SUBSCRIPTION-REQUIRED` | No subscription (never subscribed, expired, or canceled) | `An active subscription is required.` | The public API has no free tier and trials are not eligible; access is restored automatically the moment the subscription becomes paid. Only `GET /me`, `GET /credits/costs`, and `GET /credits/history` are reachable without a paid subscription. ## Credits Generative actions (rendering, script generation) consume account credits. Use the credit-cost and estimate endpoints to check the price before you spend, and the credit history endpoint to audit usage. Credits consumed through the public API are tagged at the ledger level, so API-originated spend is distinguishable from in-app usage. Audit it with `GET /credits/history` — each entry records the action and the credits charged — and look up the per-action price list with `GET /credits/costs`. ## Responses & errors Responses are JSON wrapped in a `{ message, status, data }` envelope. Errors use standard HTTP status codes — `400` (bad request, e.g. an `include` value outside the endpoint's allowlist), `401` (unauthenticated), `402` (payment required — insufficient credits), `404` (not found), `422` (validation), and `429` (rate limited). Error payloads include a stable machine-readable `error.code`: | Code | Meaning | |------|---------| | `GEN-UNAUTHORIZED` | Missing, invalid, or revoked API key (`401`). | | `GEN-FORBIDDEN` | Authenticated but not allowed to act on this resource (`403`). | | `GEN-NOT-FOUND` | Resource does not exist or is not yours (`404`). | | `GEN-WRONG-ARGS` | Malformed request arguments (`400`). | | `GEN-API-ERROR` | Unexpected server-side error (`5xx`). | | `SUBSCRIPTION-REQUIRED` | No active subscription (`403`). | | `PAID-SUBSCRIPTION-REQUIRED` | Trial subscription — the API is paid-only (`403`). | | `INSUFFICIENT-CREDITS` | Not enough credits for the requested action (`402`). | | `EMAIL-NOT-VERIFIED` | Account email address not verified yet (`403`). | | `FEATURE-NOT-ALLOWED` | Feature not included in the current plan (`403`). | | `REACH-PLAN-LIMIT` / `REACH-PLAN-STORAGE-LIMIT` / `REACH-PLAN-PUBLISH-LIMIT` | A plan quota was exhausted (`403`). | ## Rate limiting Requests are rate limited **per API token** — 30 requests per minute by default. Every response carries `X-RateLimit-Limit` and `X-RateLimit-Remaining` headers so you can pace requests proactively. When you exceed the limit the API responds with `429 Too Many Requests` plus `Retry-After` (seconds to wait) and `X-RateLimit-Reset` (Unix timestamp when the window resets) — back off until then and retry. The `scrape-script` and `scrape-images` endpoints additionally draw from a shared scraping capacity pool; when it is exhausted they return `429` independently of your per-token allowance — retry later. ## CLI Prefer the terminal? The official [`@syllaby-ai/cli`](https://www.npmjs.com/package/@syllaby-ai/cli) wraps this entire API in curated commands — faceless videos, text-to-video, storyboards, ideas, characters, credits — plus a raw `syllaby api ` escape hatch for every endpoint. ```bash npm install -g @syllaby-ai/cli syllaby auth login # paste an API key from ai.syllaby.io/settings/api-tokens syllaby faceless create --topic "5 facts about Roman aqueducts" \ --render --wait --download video.mp4 ``` It authenticates with the same Bearer API keys and is subject to the same per-token rate limits described above; `--json` on any command prints the raw API envelope for scripting. **[Full CLI documentation →](/cli.html)** — installation, authentication and profiles, the complete command reference, waiting/download behavior, exit codes, and scripting recipes. ## MCP Prefer working from an AI agent? Syllaby runs a hosted remote MCP server at `https://api.syllaby.io/mcp` that exposes the same functionality as native tools for Claude and other MCP clients — no local process, no npm install. It authenticates over OAuth or a bearer API key; the client walkthroughs below cover both. Prefer a local process instead? The official [`@syllaby-ai/mcp-server`](https://www.npmjs.com/package/@syllaby-ai/mcp-server) package wraps this REST API as MCP tools for Claude, Cursor, and any MCP-compatible client — installable via npm, the MCP Registry, Smithery, or as a desktop extension. Set `SYLLABY_API_KEY` and point `SYLLABY_API_BASE_URL` at this API's base URL. Everything below is about the hosted remote server, not this wrapper. ### Tools & billing The server exposes **42 tools**: **31 are free** (reads, estimates, drafts, scene edits, scene chaining, scheduler edits, publishing, uploads, tool discovery) and **11 charge credits**. The charging list is closed — these eleven and no others: `create_video`, `generate_script`, `scrape_script`, `regenerate_video_asset`, `render_video`, `export_video`, `generate_text_to_video`, `generate_storyboard`, `generate_storyboard_media`, `create_scheduler` and `run_scheduler`. Bulk scheduling is available as tools too (`create_scheduler`, `get_scheduler`, `update_occurrence`, `estimate_scheduler`, `run_scheduler`) — the lifecycle is documented alongside the Bulk Scheduler REST endpoints when they publish. Two of them charge, and neither is obvious from its name: `create_scheduler` renders nothing but its `topic` path writes and bills one script per planned post (pass `scripts` instead and it is free), and `run_scheduler` bills a full video render for every planned post in a single call — price it with `estimate_scheduler` first. `chain_storyboard_scene` is free on every branch: it links two scenes and moves a frame, and never generates or charges for a clip. Generating the chained scene's clip is a separate, separately priced `generate_storyboard_media` call, and the tool's `chain_status` field says whether the frame is ready (`resolved`), on its way (`extracting`), or not yet available (`deferred`). The remote server requires an active **paid** Syllaby subscription — trial accounts can connect but every tool call is rejected with `PAID-SUBSCRIPTION-REQUIRED` until the subscription becomes paid. Renders bill a **minimum of one minute** — a 20-second and a 50-second render on the same engine cost the same. **Credits are purchased at [syllaby.io](https://syllaby.io), never inside a chat.** No tool on this server sells credits, tops up a balance, or upgrades a plan — if your balance is short, the agent will say so and point you to your account instead. ### Publish a single video Four free tools cover the web app's Publish page for one video: `publish_video` (now, or at `scheduled_at`), `list_publications`, `reschedule_publication` and `cancel_publication`. `publish_video` posts **publicly** on your channel and is not retry-safe, so the agent is told to state the channel, caption and time and wait for your OK before calling it. Channel ids come from `list_social_channels`; connect a channel in the web app or with `connect_social_account`. ### Bring your own video A video you made outside Syllaby — edited in ChatGPT or Claude, exported from a desktop editor, shot on your phone — can be scheduled and published too. It is imported as a private asset (free; it counts against your plan's storage), and `publish_video` takes the returned `asset_id`. Which tool brings it in depends on where the file is: | Where the file is | Tool | What happens | |---|---|---| | At a **public URL** | `upload_custom_video` with `url` | Syllaby downloads it. The URL must be reachable without login and serve an mp4, jpg, png or webp. | | **In the chat** (the assistant made or edited it) or **on your device** | `create_upload`, then `upload_custom_video` with `upload_id` | The agent gets a private upload link and either sends the file itself or hands the link to you. | MCP has no way to carry a file inside a tool call, and a file in an assistant's sandbox has no public URL, so `create_upload` moves the bytes out of band: 1. The agent calls `create_upload` and receives an `upload_id` and an `upload_url`. The link is **private, single-use and valid for 60 minutes**, and accepts one mp4, jpg, png or webp up to **250 MB**. 2. The file goes to the link one of two ways: - **You upload it.** The agent shows you the link; open it, choose the file, wait for "Uploaded", and tell the agent you are done. This is the path in ChatGPT and in claude.ai, whose sandboxes cannot reach the internet on their own. If the assistant made the file, download it from the chat first. - **The agent uploads it.** A client with a shell and internet access (Claude Code, Cursor, VS Code) posts the file directly: ```bash curl -F "file=@/path/to/video.mp4" "" ``` 3. The agent calls `upload_custom_video` with the `upload_id` and gets the `asset_id` back. Calling it before the file has arrived returns a "not uploaded yet" message rather than failing the flow. 4. `publish_video` with that `asset_id` and a `channel_id` posts or schedules it. Treat the upload link like a password until it is used: anyone holding it can add **one** file to your account during its 60 minutes. It can never read, list or delete anything, and it stops working the moment a file is accepted. The same file-type, size and storage-quota rules as the web app's upload apply; a refused file leaves the link usable so you can retry with another. ### A tool is missing — refresh your client MCP clients fetch a server's tool list when you add the connector, and most keep that copy. When Syllaby ships a new tool, a client connected earlier may not see it, and the assistant will say the action "is not exposed" or "is not available" even though the server has it. Refresh the client's copy: | Client | How to refresh | |---|---| | ChatGPT (developer mode) | Open the Syllaby connection in ChatGPT's app/connector settings ([chatgpt.com/plugins](https://chatgpt.com/plugins)), select **Refresh**, then start a **new conversation**. Existing conversations keep the old list. | | claude.ai / Claude Desktop | Tools are re-read on the next connection: **Settings → Connectors**, disconnect Syllaby and connect it again, then start a new chat. Claude Desktop may also need a full quit and reopen. | | Claude Code | Run `/mcp`, pick `syllaby`, choose **Reconnect**. | | Cursor | Quit Cursor completely and reopen it. | | VS Code | Run **MCP: Reset Cached Tools** from the Command Palette. | Until you refresh, two tools that never change give the assistant a way in: `discover_tools` returns the server's **live** catalog (all tools, one by `name` with its input schema, or filtered by a `query` word), and `run_tool` calls a tool from that catalog by name. Both are free. `run_tool` deliberately **refuses the eleven charging tools and `publish_video`**: their price and consent rules are attached to the real tool definitions, so those must be called directly — which means refreshing the client as above. You can prompt it yourself: *"Use discover_tools to check whether Syllaby has a tool for this."* The [Changelog](#changelog) lists every tool added to the server, with the date, so you can tell whether a refresh is due. ### Tool approval prompts Most MCP clients (Claude included) ask you to approve each tool call the first time it runs. A full video run is 15–25 sequential calls, so with per-call approval the flow pauses at every step. To let multi-step runs flow, choose **"Always allow"** for the Syllaby connector's tools when the first prompt appears. Read-only tools (status checks, lists, estimates) are marked read-only in the server's tool metadata, so clients that honor MCP annotations can skip prompting for them — including the repeated "is the render done yet?" polls. Know what "Always allow" covers: eleven tools charge credits (see Tools & billing), and blanket approval lets the agent call them without a click-through. The server instructs agents to state the price and get your explicit OK in chat before every charging call, but that is guidance to the model, not a hard gate — grant "Always allow" only in clients and accounts you trust with spending. ### claude.ai — custom connector 1. Go to **Settings → Connectors → Add custom connector**, or use this one-click link: [Add Syllaby to claude.ai](https://claude.ai/customize/connectors?modal=add-custom-connector&connectorName=Syllaby&connectorUrl=https%3A%2F%2Fapi.syllaby.io%2Fmcp) 2. Adding manually instead? Set **Name** to `Syllaby` and **URL** to `https://api.syllaby.io/mcp`. 3. Authorize via the OAuth prompt — you'll be redirected to sign in to your Syllaby account. ### Claude Desktop — custom connector 1. Open **Settings → Connectors → Add custom connector**. 2. Set **Name** to `Syllaby` and **URL** to `https://api.syllaby.io/mcp`. 3. Complete the OAuth sign-in when prompted. ### Claude Code — OAuth ```bash claude mcp add --transport http syllaby https://api.syllaby.io/mcp ``` Claude Code opens a browser to complete the OAuth flow; approve it and the server is available in that session. ### Claude Code — headless (bearer token) For CI or headless environments, skip OAuth with an API key instead: ```bash claude mcp add --transport http syllaby https://api.syllaby.io/mcp \ --header "Authorization: Bearer slb_..." ``` Generate the key from your Syllaby account settings. ### ChatGPT — developer mode 1. Enable **developer mode** in ChatGPT settings (Settings → Connectors → Advanced). 2. Add a custom connector with server URL `https://api.syllaby.io/mcp`. 3. Complete the OAuth sign-in when prompted. ChatGPT asks for a per-conversation confirmation before any write action. ChatGPT keeps the tool list it read when you added the connector. After Syllaby ships new tools, open the connection, select **Refresh** and start a new conversation — see "A tool is missing" above. ### OAuth consent notes Resubmitting a stale consent form (back button, duplicate tab) shows one of two expected pages: **"You're already connected"** means the approval went through — close the tab; **"This authorization page has expired"** means you should restart the connection from the client. Neither is a failure. ## Changelog - **2026-09-18** — MCP: `create_upload` brings in a video that has no public URL (made in the chat, or on your device) through a private single-use upload link, and `upload_custom_video` accepts the resulting `upload_id`. `discover_tools` and `run_tool` let a client holding an outdated tool list reach newly shipped free tools. 42 tools in total. Refresh your client to see them. - **2026-09-17** — MCP: `upload_custom_video` imports a finished video from a public URL, and `publish_video` accepts its `asset_id`. - **2026-09-06** — MCP: single-video publishing — `publish_video`, `list_publications`, `reschedule_publication`, `cancel_publication`. - **2026-08-19** — Dedicated [CLI documentation page](/cli.html) added to this docs site. - **2026-08-15** — Official CLI released: [`@syllaby-ai/cli`](https://www.npmjs.com/package/@syllaby-ai/cli) on npm. - **2026-07-04** — Trial subscriptions are rejected with the dedicated `PAID-SUBSCRIPTION-REQUIRED` code; scene `duration` validates against the clip engine's supported values; `POST /text-to-scene/{storyboard}/scenes` persists `raw_video_prompt` and `duration` on create; `GET /faceless/options` accepts both CSV and array syntax for `only`. - **2026-07-02** — Public API restricted to paid subscriptions (trials excluded). Render duration is floored at 1 second. - **2026-07-01** — Dedicated B-roll, AI-Visuals, URL-to-Video, and Text-to-Scene modules. ## Error responses Every endpoint shares these error shapes: ### `400` Bad request — e.g. an `include` value outside the endpoint's allowlist. ```json { "message": "Requested include(s) `foo` are not allowed. Allowed include(s) are `video, captions, media, music, voice, background, genre, watermark, character, assets`." } ``` ### `401` Unauthenticated ```json { "message": "Unauthenticated.", "error": { "code": "GEN-UNAUTHORIZED", "status": 401 } } ``` ### `402` Insufficient credits. The request is authenticated and valid, but the account balance is too low — top up and retry. ### `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. ```json { "message": "An active subscription is required.", "error": { "code": "SUBSCRIPTION-REQUIRED", "status": 403 } } ``` ### `404` Not found ```json { "message": "Resource Not Found", "error": { "code": "GEN-NOT-FOUND", "status": 404 } } ``` ### `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. ```json { "message": "The GET method is not supported for this route.", "error": { "code": "GEN-METHOD-NOT-ALLOWED", "status": 405 } } ``` ### `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. ```json { "message": "Your email address is not verified.", "error": { "code": "EMAIL-NOT-VERIFIED", "status": 409 } } ``` ### `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. ```json { "message": "The POST data is too large.", "error": { "code": "GEN-WRONG-ARGS", "status": 413 } } ``` ### `422` Validation error ```json { "message": "The given data was invalid.", "errors": { "voice_id": [ "The selected voice id is invalid." ] }, "error": { "code": "GEN-VALIDATION-FAILED", "status": 422 } } ``` ### `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. ```json { "message": "Too Many Attempts.", "error": { "code": "RATE-LIMIT-EXCEEDED", "status": 429 } } ``` ### `500` Server error. [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) # Account The authenticated user — profile, per-feature credit costs, and the credit ledger. ## GET /me **Get current account** (operationId: `getMyAccount`) Returns the authenticated user's public account profile: identity, credit balance, and a high-level subscription summary (no billing-provider internals). ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "id": 1, "name": "Jane Doe", "email": "jane@example.com", "email_verified": true, "credits": { "remaining": 120, "total": 150, "extra": 0 }, "subscription": { "exists": true, "active": true, "trial": false, "plan": "Pro", "ends_at": "2026-02-01T00:00:00+00:00" }, "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z" } } ``` ### Errors - `401` — Unauthenticated - `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 /credits/costs **List credit costs** (operationId: `listCreditCosts`) Returns the per-feature credit cost map so you can show users what each action will cost before they spend credits. ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "idea_discovery": { "credits": 15 }, "avatar": { "credits": 30 }, "faceless": { "credits": 13 }, "image_regenerations": { "credits": 1, "free": 3 }, "thumbnail_generation": { "credits": 3 } } } ``` ### Errors - `401` — Unauthenticated - `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 /credits/history **List credit history** (operationId: `listCreditHistory`) Returns the authenticated user's credit ledger (debits and credits), newest first by default. Pass `order=ASC` to reverse, and `per_page` to set the page size (default 12, max 50). ### Parameters - `order` (query, string) — Sort direction by date. `DESC` (newest first, default) or `ASC`. Example: `DESC` - `per_page` (query, integer) — Number of entries per page. Defaults to 12; capped at 50. Example: `12` - `page` (query, integer) — Page number of the paginated ledger (see `links`/`meta` in the response). Example: `2` - `source` (query, string) — Filter entries by origin. Use `api` to see only spend made through the public API. Example: `api` ### Response `200` ```json { "message": "Success.", "status": 200, "data": [ { "label": "Faceless video", "content_type": "Faceless video generated", "credit_spend": "-30", "transaction_type": "Debit", "created_at": "2026-01-01T12:00:00.000000Z" } ], "meta": { "current_page": 1, "per_page": 15, "total": 1 } } ``` ### Errors - `401` — Unauthenticated - `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 /account/auto-topup **Get auto top-up preference** (operationId: `getAutoTopUp`) Returns the authenticated account's auto top-up preference: whether it's enabled, how it's configured, and its failure/enablement history. Auto top-up automatically purchases more credits when your **API-originated** balance runs low — credits spent through the v2 API count toward the trigger; credits spent in the Syllaby app do not. It fires once your balance drops below **10%** of your monthly plan credits, purchasing either a fixed credit pack (`type: extra-credits`) or a custom amount (`type: dynamic-credits`), depending on how it's configured. Purchased credits land in your account's **extra credits**, on top of your plan allowance, and a notification is sent for both successful and failed attempts. **Safeguards:** at most **3** successful top-ups per UTC calendar day; a failed charge starts a **24-hour cooldown** before the next attempt is made; and **3 consecutive failures** disable the preference automatically (`enabled` flips to `false` — `consecutive_failures` tells you how close it is). See `PUT /account/auto-topup` for how enabling/disabling works and what a lapsed subscription means for this preference. ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "enabled": true, "type": "dynamic-credits", "plan_id": null, "units": 5, "enabled_at": "2026-01-01T12:00:00.000000Z", "consecutive_failures": 0 } } ``` ### 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). ## PUT /account/auto-topup **Update auto top-up preference** (operationId: `updateAutoTopUp`) Enables, reconfigures, or disables auto top-up. Counters (`consecutive_failures`, the daily success count) survive toggling — disabling and re-enabling neither resets the failure history nor bypasses the daily cap. **Disabling** — send `{ "enabled": false }`. Every other field is ignored, and the existing `type`/`plan_id`/`units` configuration is preserved for a future re-enable. **Enabling** (`enabled: true`) additionally requires: - `type` — `extra-credits` (also requires `plan_id`, an active one-time credit-pack plan) or `dynamic-credits` (also requires `units`). - A saved default payment method on the account. Without one, the request fails with `422` (`A saved payment method is required to enable auto top-up.`). **A lapsed subscription blocks this whole endpoint** — `GET` and `PUT /account/auto-topup` both sit behind the paid-subscription gate (`403`, below), the same as every other v2 endpoint. The preference itself is retained while lapsed and can still be reviewed or changed from the Syllaby web app; auto top-up resumes automatically once the subscription is active again. ### Request body Required. `enabled` is always required. Disabling accepts just `enabled: false`; enabling additionally requires `type`, plus `plan_id` or `units` depending on it. - `enabled` (boolean, required) — Turn auto top-up on or off. `false` disables it immediately; every other field is ignored when disabling. - `type` (string) — Required when `enabled` is `true`. Purchase mode: `extra-credits` buys a fixed one-time credit pack (set `plan_id`), `dynamic-credits` buys a custom amount (set `units`). Allowed values: `extra-credits`, `dynamic-credits`. - `plan_id` (integer) — Required when `type` is `extra-credits`. Identifier of an active one-time credit-pack plan. - `units` (integer) — Required when `type` is `dynamic-credits`. Number of pricing units to purchase — 100 credits per unit. Up to 100 units, only preset values are accepted (1, 2, 3, 4, 5, 10, 20, 30, 50, 100); above 100 (up to 199,999) any integer is accepted. Example request: ```json { "enabled": true, "type": "dynamic-credits", "units": 5 } ``` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "enabled": true, "type": "dynamic-credits", "plan_id": null, "units": 5, "enabled_at": "2026-01-01T12:00:00.000000Z", "consecutive_failures": 0 } } ``` ### 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). [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) # Faceless Videos Create, configure, render, and retrieve faceless videos, their assets, and reusable presets. The typed product endpoints — **B-roll**, **AI-Visuals**, and **URL-to-Video** — are recommended shortcuts for those specific types; this generic `/faceless` surface is the advanced entry point that supports every type, including `ai-clips` (only reachable here), and lets you switch a draft's `type` before rendering. ## GET /faceless/options **List faceless options** (operationId: `listFacelessOptions`) **Reference data** — the slugs/ids for Step 3 (Configure). Returns all reference data for building a faceless video in one payload, split into two layers. `mandatory` holds the inputs a typical render needs — aspect_ratios, genres, image_engines, voices. `optional` holds the styling and advanced groups — fonts, transitions, backgrounds, caption effects and positions, overlays, watermark positions, animations, sound effects, volumes, clip engines, and characters. Use the returned slugs/ids when creating, updating, or rendering a video. ### Parameters - `only` (query, string) — Return only the named option groups. Accepts CSV (`only=voices,genres`) or array (`only[]=voices&only[]=genres`) syntax. Group names: aspect_ratios, genres, image_engines, voices, fonts, transitions, backgrounds, caption_effects, caption_positions, overlays, watermark_positions, animations, sfx, volumes, clip_engines, characters. Example: `voices,genres` - `summary` (query, boolean) — When true, return per-group item counts instead of the full payloads — useful to size a follow-up `only` request. Example: `true` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "mandatory": { "aspect_ratios": [ { "slug": "9:16", "label": "Portrait" } ], "genres": [ { "id": 3, "name": "Cinematic", "slug": "cinematic" } ], "image_engines": [ { "id": 5, "name": "Hyperflux", "slug": "hyperflux", "type": "text-to-image", "cost": 2 } ], "voices": [ { "id": 12, "name": "Aria", "language": "english", "gender": "female" } ] }, "optional": { "fonts": [ { "name": "Inter", "slug": "inter" } ], "transitions": [ { "name": "Fade", "slug": "fade" } ], "backgrounds": [ { "id": 7, "name": "Studio", "slug": "studio" } ], "caption_effects": [ { "name": "None", "slug": "none" } ], "caption_positions": [ { "name": "Center", "slug": "center" } ], "overlays": [ { "name": "None", "slug": "none" } ], "watermark_positions": [ { "name": "Bottom Right", "slug": "bottom-right" } ], "animations": [ { "name": "Pan In (Zoom In)", "slug": "pan-in" } ], "sfx": [ { "name": "None", "slug": "none" } ], "volumes": [ { "name": "Medium", "slug": "medium" } ], "clip_engines": [ { "id": 23, "name": "Veo 3.1 Fast", "slug": "veo-3-1-fast", "description": "Faster generation with last frame support.", "type": "text-to-video", "cost": [ { "audio": "on", "credits_per_second": 4 }, { "audio": "off", "credits_per_second": 3 } ] } ], "characters": [ { "id": 2, "uuid": "chr_123", "name": "Maya", "slug": "maya", "gender": "female", "status": "ready" } ] } } } ``` ### 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). ## POST /faceless **Create a faceless video** (operationId: `createFacelessVideo`) **Step 1 of 7 · Create.** Creates a new faceless video in draft state. Returns the created video — with its render state embedded under `data.video` — so you can configure it (script, voice, visuals) and then render it. This does not start rendering. **Required:** `title` and `type` — name the video and choose how its visuals are sourced (`type` is fixed at creation and cannot be changed later). Keep the returned **`id`**: it is the identifier for every follow-up call, including polling `GET /faceless/{id}` (do not use `video_id` for that). **Requires an active subscription** — no active subscription → `403` `SUBSCRIPTION-REQUIRED` `An active subscription is required.`; the public API has no free tier, so subscribe before creating a video. → **Next:** Step 2 — add a script with `PUT /faceless/{faceless}/scripts` (or supply your own via `PATCH /faceless/{faceless}`). ### Request body Optional. - `title` (string, required) — Title of the resource. - `type` (string, required) — Faceless video type — controls how the visuals are sourced: `b-roll` (stock footage), `url-based` (images scraped from a web page — requires `POST /faceless/{faceless}/scrape-images` before render), `ai-visuals` (AI-generated images), or `ai-clips` (AI-generated video clips). Defaults to `ai-visuals`. **The type is fixed at creation and cannot be changed later.** Allowed values: `b-roll`, `url-based`, `ai-visuals`, `ai-clips`. - `idea_id` (integer) — Identifier of a content idea created in the Syllaby app, linking the video to it. Optional — omit it for API-only flows; the v2 API exposes no ideas endpoints, so there is no way to obtain a valid id through the API. - `starts_at` (string) — Optional ISO-8601 timestamp that schedules the video on your Syllaby content calendar — sending it creates a calendar event for the video. It does not delay or schedule the render. - `ends_at` (string) — Optional ISO-8601 timestamp marking the end of the scheduled calendar slot. Must be the same as or after `starts_at`; only meaningful together with it. Example request: ```json { "title": "My first faceless video", "type": "ai-visuals" } ``` ### Response `201` ```json { "message": "Success.", "status": 201, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "faceless", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "Three habits that quietly improve your focus.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": null, "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z", "video": { "id": 10, "user_id": 1, "idea_id": null, "scheduler_id": null, "title": "Focus habits", "type": "faceless", "url": null, "status": "draft", "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" } } } ``` ### 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). - `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 /faceless/{faceless}/scripts **Generate a faceless script** (operationId: `generateFacelessScript`) **Step 2 of 7 · Add a script.** *(Alternative: skip this generated-script step and supply a full custom script verbatim via `PATCH /faceless/{faceless}` — set the `script` field.)* 💳 Charges credits. The response `meta.credits` shows the cost and your remaining balance. **Before you can generate a script:** create a faceless video first (`POST /faceless`). This charges credits, so make sure your balance is sufficient — `meta.credits.cost` is the exact amount charged. Generates a narration script for the faceless video from a topic, tone, style, language, and target duration, and stores it on the video. **Note the method:** this endpoint is a `PUT` (idempotent replace of the video's script), not a `POST` — each call regenerates the script and overwrites the previous one. **Preconditions & common errors:** - **No active subscription** → `403` `SUBSCRIPTION-REQUIRED` `An active subscription is required.` — checked before anything else; the public API has no free tier. - **Video busy** (rendering or syncing) → `403` — you cannot regenerate the script while the video is processing. - **Not your video** → `403`. - **Insufficient credits** → `402` with code `INSUFFICIENT-CREDITS` and `required` / `available` in the error body. - **Missing required fields** (`topic`, `tone`, `style`, `language`, `duration`) → `422`; `topic` is capped at 500 characters. → **Next:** Step 3 — configure voice, genre, captions, and visuals with `PATCH /faceless/{faceless}`. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` ### Request body Required. - `style` (string, required) — Required — narrative style for the generated script (free-form text, max 255 chars). Examples: educational, storytelling, listicle, conversational, motivational. - `tone` (string, required) — Tone of voice for generated content (e.g. "professional"). - `language` (string, required) — Language of the content (e.g. "english"). - `topic` (string, required) — Topic or subject of the content. - `duration` (integer, required) — Target length of the generated script in seconds. Must be one of 30, 60, 180, 300, 600, 900, or 1200. Allowed values: `30`, `60`, `180`, `300`, `600`, `900`, `1200`. Example request: ```json { "topic": "Morning routines for better focus", "tone": "professional", "style": "educational", "language": "english", "duration": 60 } ``` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "faceless", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "Three habits that quietly improve your focus.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": null, "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z", "video": { "id": 10, "user_id": 1, "idea_id": null, "scheduler_id": null, "title": "Focus habits", "type": "faceless", "url": null, "status": "draft", "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" } }, "meta": { "credits": { "cost": 5, "remaining": 875 } } } ``` ### 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. - `500` — Server error. Error shapes and examples are shared across endpoints — see the **Error responses** section in [getting-started](getting-started.md). ## POST /faceless/{faceless}/scrape-images **Scrape images from a URL** (operationId: `scrapeFacelessImages`) **URL-based flow · Step A · Scrape images** *(free)* — extract images from a web page and attach them to a `url-based` video. Required before rendering. **Free — no credits charged.** Extracts images from the supplied web page URL and attaches them to the `url-based` faceless video as ordered assets. A subsequent `GET /faceless/{faceless}/assets` will list them. **This endpoint is only valid for `url-based` videos.** Calling it on any other type returns `422`. **Before you can render a `url-based` video:** you must call this endpoint at least once. A render attempted without scraped assets returns `422 assets`. **Preconditions & common errors:** - **Not a `url-based` video** → `422 faceless` — this step only makes sense for `url-based` videos; the type is fixed at creation. - **Social-media or unsupported URL** (YouTube, TikTok, Twitter/X, Instagram, Facebook, etc.) → `422 url`. - **Page un-parseable or unreachable** → `422`. - **Not your video** → `403`. → **Next for url-based:** Step B — generate a script from the same URL with `POST /faceless/{faceless}/scrape-script` (or supply your own via `PATCH /faceless/{faceless}`), then configure (`PATCH`) and render. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` ### Request body Required. The URL to extract images from. - `url` (string, required) — Publicly reachable web page URL to extract product/page images from. Social-media URLs (YouTube, TikTok, Twitter/X, Instagram, Facebook, etc.) are rejected with `422`. Example request: ```json { "url": "https://www.amazon.com/dp/B0CXYZ1234" } ``` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "url-based", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "Three habits that quietly improve your focus.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": null, "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z", "assets": [ { "id": 1, "user_id": 1, "type": "faceless_background", "status": "success", "order": 0, "media": [ { "id": 5, "name": "scene-1", "file_name": "scene-1.png", "mime_type": "image/png", "extension": "png", "download_url": "https://cdn.syllaby.dev/assets/scene-1.png" } ], "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z" } ], "video": { "id": 10, "user_id": 1, "idea_id": null, "scheduler_id": null, "title": "Focus habits", "type": "faceless", "url": null, "status": "draft", "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" } } } ``` ### 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 /faceless/{faceless}/scrape-script **Scrape a script from a URL** (operationId: `scrapeFacelessScript`) **URL-based flow · Step B · Scrape script** 💳 — generate a narration script from a web page. Charges `CONTENT_PROMPT_REQUESTED` credits (same as `PUT /faceless/{faceless}/scripts`). 💳 Charges credits (`CONTENT_PROMPT_REQUESTED` — the same amount as `PUT /faceless/{faceless}/scripts`). The response `meta.credits` shows the cost and your remaining balance. Generates a narration script directly from the content of the supplied web page URL and stores it on the `url-based` faceless video. Use this as an alternative to `PUT /faceless/{faceless}/scripts` when the script should be grounded in page content rather than a free-form topic. **Note:** this endpoint works on any faceless video type, not just `url-based` — it generates and stores a script regardless of type. You may also skip both scrape-script and `PUT /scripts` and supply your own script verbatim via `PATCH /faceless/{faceless}`. **Preconditions & common errors:** - **No active subscription** → `403` `SUBSCRIPTION-REQUIRED` `An active subscription is required.` — checked before anything else; the public API has no free tier. - **Social-media or unsupported URL** → `422 url`. - **Page un-parseable or unreachable** → `422`. - **Not your video** → `403`. - **Insufficient credits** → `402` with code `INSUFFICIENT-CREDITS` and `required` / `available` in the error body. - **Missing required fields** (`url`, `duration`, `style`, `tone`, `language`) → `422`. → **Next for url-based:** Step 3 — configure voice, captions, and visuals with `PATCH /faceless/{faceless}`, then render. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` ### Request body Required. The URL to generate a script from, plus generation options. - `url` (string, required) — Publicly reachable web page URL to extract script content from. Social-media URLs are rejected with `422`. - `duration` (integer, required) — Target length of the generated script in seconds. Must be one of 30, 60, 180, 300, 600, 900, or 1200. Allowed values: `30`, `60`, `180`, `300`, `600`, `900`, `1200`. - `style` (string, required) — Narrative style for the generated script (free-form text, max 255 chars). Examples: educational, storytelling, listicle, conversational, motivational. - `tone` (string, required) — Tone of voice for the generated script. Examples: professional, friendly, casual, authoritative. - `language` (string, required) — Language for the generated script (e.g. "english"). Example request: ```json { "url": "https://www.amazon.com/dp/B0CXYZ1234", "duration": 60, "style": "educational", "tone": "friendly", "language": "english" } ``` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "url-based", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "This product helps you focus throughout the day.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": null, "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z", "video": { "id": 10, "user_id": 1, "idea_id": null, "scheduler_id": null, "title": "Focus habits", "type": "faceless", "url": null, "status": "draft", "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" } }, "meta": { "credits": { "cost": 5, "remaining": 875 } } } ``` ### 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). ## PATCH /faceless/{faceless} **Update a faceless video** (operationId: `updateFacelessVideo`) **Step 3 of 7 · Configure.** **Before you can update:** the video must not be busy — you cannot edit it while it is rendering or syncing. Wait until it leaves those states. Updates the configuration of a faceless video (script, voice, genre, captions, transitions, and more). Only the fields you send are changed. **Render-only settings:** the caption `effect` and the `overlay` are **not** updatable here — this endpoint silently ignores them (the request succeeds with `200`, but the stored value is unchanged). Set them in the render request body (`POST /faceless/{faceless}/render`) instead. → **Next:** Step 4 — *(optional)* estimate the cost with `GET /faceless/{faceless}/estimate`, or skip to Step 5 — render with `POST /faceless/{faceless}/render`. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` ### Request body Optional. - `voice_id` (integer) — Optional — identifier of the narration voice (see faceless options). Omit voice entirely to render without narration. - `background_id` (integer) — Optional — identifier of the background asset (see faceless options). - `music_id` (integer|null) — Optional — identifier of the background music track (media id), or null to remove music. - `genre_id` (integer) — Optional — identifier of the genre/style (see faceless options). Required at render time for the `ai-visuals` and `ai-clips` types. - `image_engine_id` (integer) — Optional — identifier of the text-to-image engine (see faceless options). Relevant only for the `ai-visuals` and `ai-clips` types. - `clip_engine_id` (integer) — Optional — identifier of the text-to-video (clip) engine (see faceless options). Relevant only for clip-based types. - `transition` (string|null) — Optional — transition slug applied between scenes (see faceless options). Allowed values: `slide-left`, `slide-right`, `slide-up`, `slide-down`, `scale-in`, `scale-out`, `zoom-in`, `zoom-out`, `rotate-left`, `rotate-right`, `fade`, `none`, `mixed`, `pop`, `dreamy`, `swing`, `spin-right`, `spin-left`, `swoosh-left`, `swoosh-right`, `glide-left`, `glide-right`, `drop`, `tumble-left`, `tumble-right`, `float`, `rise-left`, `rise-right`, `bounce`, `flash`, `crossfade`, `blur-dissolve`, `zoom-dissolve`. - `animation` (string|null) — Optional — per-image motion effect slug (e.g. "pan-in"). See faceless options. Allowed values: `pan-in`, `pan-out`, `pan-left`, `pan-right`, `pan-up`, `pan-down`, `rotate-left-in`, `rotate-right-in`, `float`, `none`, `mixed`. - `sfx` (string|null) — Optional — sound-effect slug applied to the video (see faceless options). Allowed values: `none`, `whoosh`. - `volume` (string|null) — Optional — background-music volume level: "low", "medium", or "high". Allowed values: `low`, `medium`, `high`. - `script` (string) — Optional — supply a full custom script to use verbatim instead of generating one. Provide this via PATCH /faceless/{faceless}; the script-generation step is then unnecessary. - `duration` (integer) — Optional — target video length in seconds. Any positive integer; not restricted to the script-generation presets (those fixed values apply only to PUT /faceless/{faceless}/scripts). - `aspect_ratio` (string) — Optional — output aspect ratio: "16:9", "9:16", or "1:1". Allowed values: `16:9`, `9:16`, `1:1`. - `captions` (object) — Optional — caption styling. Only `font_family`, `font_color`, `font_url`, and `position` are honored on update; any other caption keys are ignored. - `font_family` (string) — Caption font family slug (see faceless options). - `font_color` (string) — Caption font color as a hex value (e.g. "#FFFFFF"). - `font_url` (string|null) — URL of a custom font file to use for captions. - `position` (string) — On-screen position slug (e.g. "bottom", "center", "top"). Allowed values: `top`, `bottom`, `center`. Example request: ```json { "voice_id": 12, "genre_id": 3, "script": "Three habits that quietly improve your focus.", "transition": "fade", "captions": { "font_family": "inter", "font_color": "#FFFFFF", "position": "bottom" } } ``` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "faceless", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "Three habits that quietly improve your focus.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": 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). ## GET /faceless/{faceless}/estimate **Estimate render credits** (operationId: `estimateRenderCredits`) **Step 4 of 7 · Estimate** *(optional)*. Estimates the number of credits required to render the given faceless video and compares it against the caller's available balance. **Before calling:** the faceless must already have a **script** and a **voice** configured. Set them with `PUT /faceless/{faceless}/scripts` (or `PATCH /faceless/{faceless}`) and `PATCH /faceless/{faceless}` (e.g. `{ "voice_id": 12 }`). By default the estimate reads from the stored faceless. **What-if overrides (query params):** pass `voice_id`, `image_engine_id`, and/or `clip_engine_id` as query parameters to estimate against a different configuration without saving it — e.g. `?voice_id=12&clip_engine_id=9`. Only these three inputs affect the estimate; `duration`, `genre`, and `aspect_ratio` do not, so they are not accepted here. **Common errors:** - **No script** (and no voiceover generated yet) → `422` `A script is required before rendering.` - **No voice** → `422` `Voice was not provided.` (or `Voice not found.` when the stored `voice_id` does not exist). - **Not your video** → `403`. **Accuracy:** for per-second (clip-engine) renders the estimate is an approximation — the exact charge is computed during rendering and recorded in the credit ledger (`GET /credits/history`), which is the authoritative record of what was charged. → **Next:** Step 5 — render with `POST /faceless/{faceless}/render`. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` - `voice_id` (query, integer) — What-if override: estimate against this narration voice instead of the one stored on the faceless. Discover voices at `GET /faceless/options?only=voices`. Example: `12` - `image_engine_id` (query, integer) — What-if override: estimate against this text-to-image engine instead of the stored one. Discover engines at `GET /faceless/options?only=image_engines`. Example: `1` - `clip_engine_id` (query, integer) — What-if override: estimate against this text-to-video (clip) engine instead of the stored one. Discover engines at `GET /faceless/options?only=clip_engines`. Example: `9` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "required": 30, "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. - `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 /faceless/{faceless}/render **Render a faceless video** (operationId: `renderFacelessVideo`) **Step 5 of 7 · Render.** 💳 Charges credits. The response `meta.credits` shows the cost and your remaining balance. **Before you can render:** first create a faceless video (`POST /faceless`), then generate or supply its script (`PUT /faceless/{faceless}/scripts`) and configure its options — voice, genre, captions, and visuals (`PATCH /faceless/{faceless}`). Optionally call the estimate endpoint first to check the credit cost against your balance. Starts rendering the faceless video asynchronously and charges the required credits — `meta.credits.cost` is the amount that will be charged once the pipeline reaches the charge step. For per-second clip-engine renders (`ai-clips`) this is an approximation; the exact charge is computed during rendering and recorded in `GET /credits/history` (the authoritative record). Returns immediately with HTTP 202 while generation runs in the background. **To track progress:** poll `GET /faceless/{id}` — use the faceless **`id`**, not `video_id` — and read `data.video.status` (the `video` object is always embedded). It moves from `rendering` to `completed` (or `failed`). When complete, the playable file is at `data.video.url`; on failure, `data.video.failure` carries the reason. The faceless object itself has no status field; the render lifecycle lives on the embedded `video`. See the **Get your rendered video** guide in the introduction. **Preconditions & common errors** (checked before any charge): - **No script** → `403` `A script is required before rendering.` — set one with `PUT /faceless/{faceless}/scripts` or `PATCH /faceless/{faceless}`. - **No voice** → `403` `Voice was not provided.` (or `Voice not found.` when `voice_id` does not exist) — set it with `PATCH /faceless/{faceless}`. - **Insufficient credits** → `402` with code `INSUFFICIENT-CREDITS` and `required` / `available` in the error body — check first with `GET /faceless/{faceless}/estimate`. - **Video busy** (already rendering or syncing) → `403` `The video is processing currently. Please try again once finished`. - **Storage full** → `403` `Please remove some files first` (code `REACH-PLAN-STORAGE-LIMIT`). - **No active subscription** → `403` `SUBSCRIPTION-REQUIRED` `An active subscription is required.` — the public API has no free tier; subscribe before generating. - **Missing genre** on an `ai-visuals` / `ai-clips` render — no genre set at creation or via `PATCH`, and none in the render body → `422` `A genre is required for AI visuals and AI clips.` A genre configured earlier is reused automatically, so you do **not** need to resend `genre_id` on every render. - **Invalid `image_engine_id`** (unknown id, or an engine that isn't a `text-to-image` engine) → `422` on the `image_engine_id` key, e.g. `The selected image engine id is invalid.` - **Missing image engine** on an `ai-visuals` / `ai-clips` render — no `image_engine_id` in the body and none saved on the video → `422` `An image engine is required for AI visuals and AI clips.` Like the genre, an engine configured earlier is reused automatically. - **`url-based` video with no scraped assets** → `422` `assets` — call `POST /faceless/{faceless}/scrape-images` first; without scraped images the render would produce an empty video. - **Missing required fields** (`script`, `duration`, `aspect_ratio`) → `422`. → **Next:** Step 6 — track progress by polling `GET /faceless/{id}` (use the faceless `id`, not `video_id`). ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` ### Request body Optional. Render configuration for the faceless video. Every field is optional: `script`, `duration`, and `aspect_ratio` are auto-filled from the saved draft when omitted (configure it first via `PUT /faceless/{faceless}/scripts` and `PATCH /faceless/{faceless}`), so a fully-configured video can be rendered with an empty body. A `422` is only returned when a required value is missing from both the body and the draft. - `script` (string) — Narration script text for the video. Optional — falls back to the draft's stored script when omitted. - `duration` (integer) — Target video length in seconds (a positive integer). Optional — falls back to the draft's estimated duration when omitted. Not restricted to the script-generation presets (those fixed values apply only to PUT /faceless/{faceless}/scripts). - `aspect_ratio` (string) — Output aspect ratio. Optional — falls back to the draft's stored aspect ratio when omitted. Allowed values are listed under faceless options (aspect_ratios). - `title` (string) — Optional title for the video. - `voice_id` (integer) — Identifier of the narration voice (see faceless options). - `background_id` (integer) — Identifier of the background asset (see faceless options). - `type` (string) — Faceless video type — controls how the visuals are sourced (stock b-roll, a source URL, AI-generated images, or AI-generated clips). Allowed values: `b-roll`, `url-based`, `ai-visuals`, `ai-clips`. - `genre_id` (integer) — Identifier of the genre/style that drives the image-prompt look. REQUIRED for image-based renders (`type` of `ai-visuals` or `ai-clips`); optional for `b-roll` / `url-based` renders that don't use a genre. Allowed values are listed under faceless options (genres). - `character_id` (integer) — Identifier of the consistent character (see faceless options). - `image_engine_id` (integer) — Identifier of the text-to-image engine (see faceless options). - `clip_engine_id` (integer) — Identifier of the text-to-video (clip) engine (see faceless options). - `transition` (string) — Transition slug applied between scenes. Allowed values are listed under faceless options (transitions). - `animation` (string) — Per-image motion effect slug. Allowed values are listed under faceless options (animations). - `overlay` (string) — Overlay style slug applied over the video. Allowed values are listed under faceless options (overlays). Render-body-only: set it here (or on export) — `PATCH /faceless/{faceless}` silently ignores it. - `sfx` (string) — Sound-effect slug applied to the video. Allowed values are listed under faceless options (sfx). - `ai_labels` (boolean) — Whether AI-content disclosure labels are applied on publish. - `custom_description` (string) — Custom caption/description applied to published posts. - `destination_id` (integer) — Identifier of the destination folder/resource to file the video under. - `captions` (object) — Caption styling. Only `font_family`, `font_color`, `font_url`, `position`, and `effect` are honored; any other caption keys are ignored. - `font_family` (string) — Caption font family slug (see faceless options). - `font_color` (string) — Caption font color as a hex value (e.g. "#FFFFFF"). - `font_url` (string) — URL of a custom font file to use for captions. - `position` (string) — On-screen caption position slug. Allowed values are listed under faceless options (caption_positions). - `effect` (string) — Caption effect slug applied to on-screen text. Allowed values are listed under faceless options (caption_effects). Render-body-only: set it here (or on export) — `PATCH /faceless/{faceless}` silently ignores it. - `watermark` (object) — Watermark image and placement. Provide exactly one source — `id`, `url`, or `file` (mutually exclusive). - `id` (integer) — Identifier of a watermark asset you own. Mutually exclusive with `url` and `file`. - `url` (string) — Remote URL of the watermark image. Mutually exclusive with `id` and `file`. - `file` (string) — Uploaded watermark image file (jpg, jpeg, png, webp; max 5 MB). Mutually exclusive with `id` and `url`. - `position` (string) — Watermark placement slug. Allowed values are listed under faceless options (watermark_positions). - `opacity` (integer) — Watermark opacity as a percentage (0–100). - `music_id` (integer) — Identifier of a background music track (media id). Mutually exclusive with `music.url` and `music.file`. - `music` (object) — Background music source. Provide exactly one of `url` or `file`, and only when `music_id` is omitted (all three are mutually exclusive). - `url` (string) — Remote URL of a music track. Mutually exclusive with `music_id` and `music.file`. - `file` (string) — Uploaded music file (mp3, wav, aac, m4a, ogg; max 20 MB). Mutually exclusive with `music_id` and `music.url`. - `volume` (string) — Background-music volume level. Required when any music source is set. Allowed values: `low`, `medium`, `high`. - `publications` (array of object) — Social posts to schedule for the rendered video. - `channel_id` (integer) — Identifier of the connected social channel to publish to. - `scheduled_at` (string) — Future timestamp to publish the post, or null to publish immediately. - `assets` (array of object) — Explicit ordered media assets to compose the video from. Orders must start at 0 and be consecutive with no gaps. - `id` (integer, required) — Identifier of the media asset. - `order` (integer, required) — Zero-based position of the asset in the sequence. Example request: ```json { "script": "Three small habits that quietly improve your focus every day.", "duration": 60, "aspect_ratio": "9:16", "type": "ai-visuals", "title": "Focus habits", "voice_id": 12, "genre_id": 3, "image_engine_id": 1, "clip_engine_id": 2, "transition": "fade", "animation": "pan-in", "overlay": "none", "sfx": "whoosh", "volume": "medium", "music_id": 8, "captions": { "font_family": "inter", "font_color": "#FFFFFF", "position": "bottom", "effect": "highlight" }, "watermark": { "id": 42, "position": "bottom-right", "opacity": 80 }, "publications": [ { "channel_id": 5, "scheduled_at": "2025-02-01T18:30:00Z" } ], "assets": [ { "id": 101, "order": 0 }, { "id": 102, "order": 1 } ] } ``` ### Response `202` ```json { "message": "Success.", "status": 202, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "faceless", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "Three habits that quietly improve your focus.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": null, "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z" }, "meta": { "credits": { "cost": 120, "remaining": 880 } } } ``` ### 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. - `500` — Server error. Error shapes and examples are shared across endpoints — see the **Error responses** section in [getting-started](getting-started.md). ## GET /faceless/{id} **Get a faceless video** (operationId: `getFacelessVideo`) **Step 6 of 7 · Track progress.** Returns a single faceless video you own. The render state is always embedded under `data.video` — no `include` needed: `url` (null until the render completes), `status`, and `failure` (`code` + `message`, non-null only when the render failed). Use the `include` query parameter to embed further related resources; values outside the documented allowlist are rejected with `400 Bad Request`. Responds with `404 Not Found` when the id does not exist **or** belongs to another account. Make sure you pass the faceless **`id`** (from the create/render response), not `video_id` — they are different identifiers, and polling with `video_id` is the most common cause of unexpected 404s. → **Next:** when `data.video.status` is `completed`, you have your video — *(optional)* Step 7, export a restyled cut with `POST /faceless/{faceless}/export`. If it is `failed`, retry with `POST /faceless/{faceless}/retry`. ### Parameters - `id` (path, integer, required) — Identifier of the faceless video. Example: `1` - `include` (query, string) — Comma-separated list of related resources to embed. Allowed values: `video`, `captions`, `media`, `music`, `voice`, `background`, `genre`, `watermark`, `character`, `assets`. Any other value is rejected with `400 Bad Request`. Note that `video` is always embedded in the response — you never need `include` to read render progress. Example: `captions,voice` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "faceless", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "Three habits that quietly improve your focus.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": null, "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z", "video": { "id": 10, "user_id": 1, "idea_id": null, "scheduler_id": null, "title": "Focus habits", "type": "faceless", "url": "https://cdn.syllaby.dev/videos/10/final.mp4", "status": "completed", "retries": 0, "hash": "abc123", "synced_at": "2026-01-01T12:05:00.000000Z", "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" } } } ``` ### Errors - `400` — Bad request — e.g. an `include` value outside the endpoint's allowlist. - `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 /faceless/{faceless}/retry **Retry a faceless render** (operationId: `retryFacelessRender`) **Step 6 of 7 · Track progress — retry a failed render.** 💳 Charges credits. The response `meta.credits` shows the cost and your remaining balance. **Before you can retry:** the embedded video must have **failed** — fetch `GET /faceless/{id}` and check that `data.video.status` is `failed` (the reason is in `data.video.failure`). The video must also not be busy (rendering or syncing). Retry does nothing for drafts or already-completed videos. Re-attempts rendering for a faceless video that previously failed. Returns HTTP 202 with the video state embedded under `data.video` while the retry runs asynchronously; track it the same way as render. **Credits:** for a video that had already been exported, `meta.credits.cost` is the exact export-fix amount charged — `0` when nothing qualified for a charge. For a video that has not been exported, the charge runs asynchronously inside the render pipeline, so `cost` is the preflight estimate that will be charged. **Preconditions & common errors:** - **Video not in a failed state** → `403` `Only failed videos can be re-tried. Please create a new video instead.` — only a `failed` render can be retried. - **Video busy** (rendering or syncing) → `403` `The video is still being processed. Please wait until it is finished.` - **Insufficient credits** → `402` with code `INSUFFICIENT-CREDITS` and `required` / `available` in the error body. - **Not your video** → `403` `You are not allowed to re-generate this video`. → **Next:** keep polling `GET /faceless/{id}` until `data.video.status` is `completed`. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` ### Response `202` ```json { "message": "Success.", "status": 202, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "faceless", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "Three habits that quietly improve your focus.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": null, "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z", "video": { "id": 10, "user_id": 1, "idea_id": null, "scheduler_id": null, "title": "Focus habits", "type": "faceless", "url": null, "status": "rendering", "retries": 1, "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" } }, "meta": { "credits": { "cost": 120, "remaining": 880 } } } ``` ### 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. - `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 /faceless/{faceless}/export **Export a faceless video** (operationId: `exportFacelessVideo`) **Step 7 of 7 · Export a restyled cut** *(optional)*. 💳 Charges credits. The response `meta.credits` shows the cost and your remaining balance. **Before you can export:** the video must already be rendered, and must not currently be busy (rendering or syncing). Rebuilds the rendered faceless video source with the supplied styling (captions, watermark, music, transitions) and queues an export. Returns HTTP 202 with the video state embedded under `data.video` while the export runs asynchronously. **Credits:** `meta.credits.cost` is the exact amount charged — `0` when nothing changed enough to charge (unchanged options, music, and watermark). **Preconditions & common errors:** - **Video not rendered** (still a draft — never rendered) → `403` `The video must be rendered before it can be exported.` Export restyles an already-built video; render it first (`POST /faceless/{faceless}/render`). - **Video busy** (rendering or syncing) → `403` `The video is still being processed`. - **Not your video** → `403` `You are not allowed to export this video`. - **Insufficient credits** (only when the restyle qualifies for a charge) → `402` with code `INSUFFICIENT-CREDITS`. → This is the final step. Track the export the same way — poll `GET /faceless/{id}`. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` ### Request body Optional. Restyling options for the export. All fields are optional; omit a field to keep the rendered video's current styling. Note: unlike render, the watermark accepts only an existing asset `id` (no upload), and music is selected by `music_id` only. - `transition` (string) — Transition slug applied between scenes. Allowed values are listed under faceless options (transitions). - `overlay` (string) — Overlay style slug applied over the video. Allowed values are listed under faceless options (overlays). - `sfx` (string) — Sound-effect slug applied to the video. Allowed values are listed under faceless options (sfx). - `music_id` (integer) — Identifier of a background music track (media id). - `volume` (string) — Background-music volume level. Required when `music_id` is set. Allowed values: `low`, `medium`, `high`. - `captions` (object) — Caption styling. Only `font_family`, `font_color`, `font_url`, `position`, and `effect` are honored; any other caption keys are ignored. - `font_family` (string) — Caption font family slug (see faceless options). - `font_color` (string) — Caption font color as a hex value (e.g. "#FFFFFF"). - `font_url` (string) — URL of a custom font file to use for captions. - `position` (string) — On-screen caption position slug. Allowed values are listed under faceless options (caption_positions). - `effect` (string) — Caption effect slug applied to on-screen text. Allowed values are listed under faceless options (caption_effects). - `watermark` (object) — Watermark asset and placement. References an existing asset you own. - `id` (integer) — Identifier of a watermark asset you own. - `position` (string) — Watermark placement slug. Allowed values are listed under faceless options (watermark_positions). - `opacity` (integer) — Watermark opacity as a percentage (0–100). Example request: ```json { "transition": "fade", "volume": "medium", "captions": { "font_family": "inter", "font_color": "#FFFFFF", "position": "bottom", "effect": "highlight" }, "watermark": { "id": 42, "position": "bottom-right", "opacity": 80 } } ``` ### Response `202` ```json { "message": "Success.", "status": 202, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "faceless", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "Three habits that quietly improve your focus.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": null, "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z", "video": { "id": 10, "user_id": 1, "idea_id": null, "scheduler_id": null, "title": "Focus habits", "type": "faceless", "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" } }, "meta": { "credits": { "cost": 50, "remaining": 830 } } } ``` ### 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. - `500` — Server error. Error shapes and examples are shared across endpoints — see the **Error responses** section in [getting-started](getting-started.md). ## GET /faceless/{faceless}/assets **List faceless assets** (operationId: `listFacelessAssets`) **Reference** — inspect rendered assets (populated after Step 6). Lists the media assets (images, clips) that make up the faceless video, ordered by scene. Pass `index` to fetch the asset at a single scene position. **Note:** assets are produced during rendering — expect an empty list until the video has been rendered at least once. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` - `index` (query, string) — Zero-based scene position; returns only the asset at that position. Example: `0` ### Response `200` ```json { "message": "Success.", "status": 200, "data": [ { "id": 1, "user_id": 1, "type": "faceless_background", "status": "success", "order": 0, "media": [ { "id": 5, "name": "scene-1", "file_name": "scene-1.png", "mime_type": "image/png", "extension": "png", "download_url": "https://cdn.syllaby.dev/assets/scene-1.png" } ], "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). ## GET /faceless/{faceless}/assets/{asset} **Get a faceless asset** (operationId: `getFacelessAsset`) **Reference** — inspect a single rendered asset (populated after Step 6). Returns a single media asset belonging to the faceless video. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` - `asset` (path, integer, required) — The asset ID Example: `1` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "id": 1, "user_id": 1, "type": "faceless_background", "status": "success", "order": 0, "media": [ { "id": 5, "name": "scene-1", "file_name": "scene-1.png", "mime_type": "image/png", "extension": "png", "download_url": "https://cdn.syllaby.dev/assets/scene-1.png" } ], "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). ## GET /presets/faceless **List faceless presets** (operationId: `listFacelessPresets`) **Reusable presets** — saved defaults you can apply at Step 1 (Create). Lists the authenticated user's saved faceless presets, newest first. A preset bundles reusable defaults (voice, genre, captions, watermark, etc.) you can apply when creating videos. Use the `include` query parameter to embed related resources; values outside the documented allowlist are rejected with `400 Bad Request`. ### Parameters - `include` (query, string) — Comma-separated list of related resources to embed. Allowed values: `music`, `voice`, `background`, `watermark`, `genre`. Any other value is rejected with `400 Bad Request`. Example: `music,voice` ### Response `200` ```json { "message": "Success.", "status": 200, "data": [ { "id": 1, "user_id": 1, "name": "My default preset", "voice_id": 12, "genre_id": 3, "orientation": "portrait", "font_family": "inter", "font_color": "#FFFFFF", "volume": "medium", "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z" } ] } ``` ### Errors - `400` — Bad request — e.g. an `include` value outside the endpoint's allowlist. - `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). ## POST /presets/faceless **Create a faceless preset** (operationId: `createFacelessPreset`) **Reusable presets** — saved defaults you can apply at Step 1 (Create). Creates a reusable faceless preset for the authenticated user from the supplied defaults. **`name` is required on create** — omitting it returns `422`. (On update, `PATCH /presets/faceless/{preset}`, `name` is optional.) All other fields are optional; `duration` must be a positive integer (≥ 1) and `font_color` must be either `default` or a hex value (e.g. `#ffffff`). ### Request body Optional. - `name` (string, required) — Display name of the preset. **Required when creating** (`POST /presets/faceless`); **optional when updating** (`PATCH /presets/faceless/{preset}`) — OpenAPI cannot make `required` depend on the HTTP verb, so it is listed in `required` here and the create-only rule is documented in this note and the create operation's description. - `volume` (string|null) — Background-music volume level: "low", "medium", or "high". - `language` (string|null) — Language of the content (e.g. "english"). - `font_color` (string|null) — Caption font color — either the literal `default` or a hex value (3- or 6-digit, e.g. `#ffffff` or `#fff`). - `font_family` (string|null) — Caption font family slug (see faceless options). - `duration` (integer|null) — Target video length in seconds — a positive number of seconds. - `orientation` (string|null) — Video orientation. One of "landscape", "portrait", or "square". Allowed values: `landscape`, `portrait`, `square`. - `position` (string|null) — On-screen position slug (e.g. "bottom", "center", "top"). - `caption_animation` (string|null) — Caption animation/effect slug applied to on-screen captions. - `sfx` (string|null) — Sound-effect slug applied to the video (see faceless options). Allowed values: `none`, `whoosh`. - `music_id` (integer|null) — Identifier of the background music track (media id). - `music_category_id` (integer|null) — Identifier of the music category tag. - `background_id` (integer|null) — Identifier of the background asset (see faceless options). - `genre_id` (integer|null) — Identifier of the genre/style (see faceless options). - `voice_id` (integer|null) — Identifier of the narration voice (see faceless options). - `transition` (string|null) — Transition slug applied between scenes (see faceless options). Allowed values: `slide-left`, `slide-right`, `slide-up`, `slide-down`, `scale-in`, `scale-out`, `zoom-in`, `zoom-out`, `rotate-left`, `rotate-right`, `fade`, `none`, `mixed`, `pop`, `dreamy`, `swing`, `spin-right`, `spin-left`, `swoosh-left`, `swoosh-right`, `glide-left`, `glide-right`, `drop`, `tumble-left`, `tumble-right`, `float`, `rise-left`, `rise-right`, `bounce`, `flash`, `crossfade`, `blur-dissolve`, `zoom-dissolve`. - `animation` (string|null) — Per-image motion effect slug (e.g. "zoom-in"). See faceless options. Allowed values: `pan-in`, `pan-out`, `pan-left`, `pan-right`, `pan-up`, `pan-down`, `rotate-left-in`, `rotate-right-in`, `float`, `none`, `mixed`. - `overlay` (string|null) — Overlay style slug applied over the video (see faceless options). Allowed values: `none`, `vhs`, `rain`, `glitch`, `dust`, `sparkling-gold`, `spark-effect`, `abstract-particles`. - `watermark_id` (integer|null) — Identifier of the watermark asset (must be owned by the caller). - `watermark_position` (string|null) — Watermark placement slug (e.g. "bottom-right"). Allowed values: `top-left`, `top-center`, `top-right`, `middle-left`, `middle-center`, `middle-right`, `bottom-left`, `bottom-center`, `bottom-right`, `none`. - `watermark_opacity` (integer|null) — Watermark opacity as a percentage (0–100). - `resource_id` (integer|null) — Identifier of the destination resource/folder. - `image_engine_id` (integer|null) — Identifier of the text-to-image engine (see faceless options). - `clip_engine_id` (integer|null) — Identifier of the text-to-video (clip) engine (see faceless options). Example request: ```json { "name": "My default preset", "voice_id": 12, "genre_id": 3, "orientation": "portrait", "font_family": "inter", "font_color": "#FFFFFF", "volume": "medium" } ``` ### Response `201` ```json { "message": "Success.", "status": 201, "data": { "id": 1, "user_id": 1, "name": "My default preset", "voice_id": 12, "genre_id": 3, "orientation": "portrait", "font_family": "inter", "font_color": "#FFFFFF", "volume": "medium", "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). ## PATCH /presets/faceless/{preset} **Update a faceless preset** (operationId: `updateFacelessPreset`) **Reusable presets.** Updates a faceless preset you own. Only the fields you send are changed. ### Parameters - `preset` (path, integer, required) — The preset ID Example: `1` ### Request body Optional. - `name` (string, required) — Display name of the preset. **Required when creating** (`POST /presets/faceless`); **optional when updating** (`PATCH /presets/faceless/{preset}`) — OpenAPI cannot make `required` depend on the HTTP verb, so it is listed in `required` here and the create-only rule is documented in this note and the create operation's description. - `volume` (string|null) — Background-music volume level: "low", "medium", or "high". - `language` (string|null) — Language of the content (e.g. "english"). - `font_color` (string|null) — Caption font color — either the literal `default` or a hex value (3- or 6-digit, e.g. `#ffffff` or `#fff`). - `font_family` (string|null) — Caption font family slug (see faceless options). - `duration` (integer|null) — Target video length in seconds — a positive number of seconds. - `orientation` (string|null) — Video orientation. One of "landscape", "portrait", or "square". Allowed values: `landscape`, `portrait`, `square`. - `position` (string|null) — On-screen position slug (e.g. "bottom", "center", "top"). - `caption_animation` (string|null) — Caption animation/effect slug applied to on-screen captions. - `sfx` (string|null) — Sound-effect slug applied to the video (see faceless options). Allowed values: `none`, `whoosh`. - `music_id` (integer|null) — Identifier of the background music track (media id). - `music_category_id` (integer|null) — Identifier of the music category tag. - `background_id` (integer|null) — Identifier of the background asset (see faceless options). - `genre_id` (integer|null) — Identifier of the genre/style (see faceless options). - `voice_id` (integer|null) — Identifier of the narration voice (see faceless options). - `transition` (string|null) — Transition slug applied between scenes (see faceless options). Allowed values: `slide-left`, `slide-right`, `slide-up`, `slide-down`, `scale-in`, `scale-out`, `zoom-in`, `zoom-out`, `rotate-left`, `rotate-right`, `fade`, `none`, `mixed`, `pop`, `dreamy`, `swing`, `spin-right`, `spin-left`, `swoosh-left`, `swoosh-right`, `glide-left`, `glide-right`, `drop`, `tumble-left`, `tumble-right`, `float`, `rise-left`, `rise-right`, `bounce`, `flash`, `crossfade`, `blur-dissolve`, `zoom-dissolve`. - `animation` (string|null) — Per-image motion effect slug (e.g. "zoom-in"). See faceless options. Allowed values: `pan-in`, `pan-out`, `pan-left`, `pan-right`, `pan-up`, `pan-down`, `rotate-left-in`, `rotate-right-in`, `float`, `none`, `mixed`. - `overlay` (string|null) — Overlay style slug applied over the video (see faceless options). Allowed values: `none`, `vhs`, `rain`, `glitch`, `dust`, `sparkling-gold`, `spark-effect`, `abstract-particles`. - `watermark_id` (integer|null) — Identifier of the watermark asset (must be owned by the caller). - `watermark_position` (string|null) — Watermark placement slug (e.g. "bottom-right"). Allowed values: `top-left`, `top-center`, `top-right`, `middle-left`, `middle-center`, `middle-right`, `bottom-left`, `bottom-center`, `bottom-right`, `none`. - `watermark_opacity` (integer|null) — Watermark opacity as a percentage (0–100). - `resource_id` (integer|null) — Identifier of the destination resource/folder. - `image_engine_id` (integer|null) — Identifier of the text-to-image engine (see faceless options). - `clip_engine_id` (integer|null) — Identifier of the text-to-video (clip) engine (see faceless options). Example request: ```json { "name": "Updated preset", "voice_id": 14, "volume": "high" } ``` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "id": 1, "user_id": 1, "name": "My default preset", "voice_id": 12, "genre_id": 3, "orientation": "portrait", "font_family": "inter", "font_color": "#FFFFFF", "volume": "medium", "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 /presets/faceless/{preset} **Delete a faceless preset** (operationId: `deleteFacelessPreset`) **Reusable presets.** Permanently deletes a faceless preset you own. Responds with HTTP 204 on success. ### Parameters - `preset` (path, integer, required) — The preset ID Example: `1` ### Response `204` Empty body (`204 No Content`). ### 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). [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 ". - `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). [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) # Transcripts Speech-to-text: transcribe any audio/video source into text with word-level timestamps, and read or correct a faceless video's captions before rendering. ## GET /transcriptions **List transcriptions** (operationId: `listTranscriptions`) Lists your transcriptions, newest first. Paginated. Word lists are omitted here (they can be large) — fetch `GET /transcriptions/{id}` for a full transcript. ### Parameters - `page` (query, integer) — Page number to fetch. Example: `1` - `per_page` (query, integer) — Items per page (default 12, max 50). Example: `12` ### Response `200` ```json { "message": "Success.", "status": 200, "data": [ { "id": 42, "status": "processing", "language": null, "duration": 184.2, "credits": 4, "text": null, "words": null, "error": null, "created_at": "2026-08-16T12:00:00Z", "completed_at": null }, { "id": 41, "status": "completed", "language": null, "duration": 63, "credits": 2, "text": "Hello world, welcome to the show.", "words": null, "error": null, "created_at": "2026-08-16T12:00:00Z", "completed_at": "2026-08-16T12:00:00Z" } ] } ``` ### 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). ## POST /transcriptions **Create a transcription** (operationId: `createTranscription`) **Step 1 of 2 · Transcribe.** 💳 **Charges 1 credit per started minute** of the source — the charge lands *after* the source is probed, so an unreadable url, a file with no audio track, or a source over 3 hours fails with **nothing charged**. The final `credits` figure appears on the record once probed. Transcribes an audio or video source. Send a public https `url` as JSON, **or** upload the `file` itself as `multipart/form-data` (mp3, wav, m4a, aac, ogg, flac, mp4, mov, webm, mkv — max 500 MB). Private-network urls are rejected. Returns `201` immediately with the record in a **`pending`** state; probing and transcription run asynchronously. Throttled to **10 creations per minute**. **To track progress:** poll `GET /transcriptions/{id}` every 5–10 seconds. If transcription fails after the charge, it is refunded automatically. → **Next:** Step 2 — track progress by polling `GET /transcriptions/{id}` with the `id` from this response. ### Request body Required. The source to transcribe: a public https `url` (JSON) or an uploaded `file` (`multipart/form-data`), plus an optional language hint. - `url` (string) — Public https url of the audio/video file to transcribe. Required unless a `file` is uploaded (then send `multipart/form-data` with the `file` part instead). Private-network addresses are rejected. - `file` (string) — The audio/video file itself (`multipart/form-data`; mp3, wav, m4a, aac, ogg, flac, mp4, mov, webm, mkv; max 500 MB). Required unless `url` is provided. - `language` (string) — Optional ISO language code hint (e.g. "en"). Omit to auto-detect. Example request: ```json { "url": "https://cdn.example.com/interviews/episode-12.mp3", "language": "en" } ``` ### Response `201` ```json { "message": "Success.", "status": 201, "data": { "id": 42, "status": "pending", "language": null, "duration": null, "credits": null, "text": null, "words": null, "error": null, "created_at": "2026-08-16T12:00:00Z", "completed_at": null } } ``` ### Errors - `401` — Unauthenticated - `402` — Insufficient credits — the account cannot cover even the minimum charge. The final cost is 1 credit per started minute of the probed source. - `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 /transcriptions/{id} **Get a transcription** (operationId: `getTranscription`) **Step 2 of 2 · Track progress.** Returns a single transcription you own — this is the polling endpoint. Use the **`id` from the create response**; polling every 5–10 seconds is plenty. `status` is the field to branch on — match these **exact strings**: `pending` (source being probed) → `processing` (transcribing) → terminal `completed` (then `text`, `duration`, `credits`, and word-level `words` are populated) or `failed` (`error` carries a stable slug; post-charge failures are refunded automatically). Responds with a uniform `404` when the id does not exist, belongs to another account, or is not numeric. → **Done** when `data.status` is `completed` — the transcript is in `data.text` with word timestamps in `data.words`. On `failed`, post-charge failures are auto-refunded. ### Parameters - `id` (path, integer, required) — Identifier of the transcription — the `id` field from the create response. Example: `42` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "id": 42, "status": "completed", "language": null, "duration": 63, "credits": 2, "text": "Hello world, welcome to the show.", "words": [ { "text": "Hello", "start": 0.12, "end": 0.4, "type": "word" }, { "text": "world,", "start": 0.46, "end": 0.9, "type": "word" }, { "text": "welcome", "start": 1, "end": 1.4, "type": "word" } ], "error": null, "created_at": "2026-08-16T12:00:00Z", "completed_at": "2026-08-16T12:00:00Z" } } ``` ### 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, 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). ## GET /faceless/{faceless}/captions **Get faceless captions** (operationId: `getFacelessCaptions`) **Caption correction · Read.** Returns a faceless video's transcript as caption segments with word-level timestamps. Available once the video is transcribed — a script-based video gets its transcript when the voiceover is generated during render; an uploaded-audio video after its transcription step. Each segment carries the raw `index` used to address edits, its `[start, end]` window, and its `words`. Silence gaps appear with `gap: true` and an empty `text` — they keep their indexes but cannot be edited. Responds `404` while the video has no transcript yet. → **Next:** apply corrections with `PUT /faceless/{faceless}/captions`, then render or export the video. ### Parameters - `faceless` (path, integer, required) — Identifier of the faceless video whose transcript to read or edit. Example: `42` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "id": 9, "segments": [ { "index": 0, "text": "Hello world", "gap": false, "start": 0, "end": 1.2, "duration": 1.2, "words": [ { "text": "Hello", "start": 0, "end": 0.5 }, { "text": "world", "start": 0.6, "end": 1.2 } ] }, { "index": 1, "text": "", "gap": true, "start": 1.2, "end": 3.4, "duration": 2.2, "words": [] } ], "updated_at": "2026-08-16T12:00:00Z" } } ``` ### 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, 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). ## PUT /faceless/{faceless}/captions **Correct faceless captions** (operationId: `updateFacelessCaptions`) **Caption correction · Edit.** **Free.** Corrects the transcript before rendering or exporting — the video's captions render from the edited segments. Send per-segment edits addressed by `index` (from the GET response), each in exactly one of two modes: - **`words`** — one-for-one word replacement; every timestamp is preserved. The array length must match the segment's word count (`422` otherwise). - **`text`** — full segment rewrite; the words are rebuilt and retimed within the segment's original time window (neighbouring segments never shift). Use this when the word count changes. Gap segments and out-of-range indexes are rejected with `422`, as are edits while the video has no transcript or is currently rendering. → **Done** — the corrected captions are used by the next `render` / `export` of this video. ### Parameters - `faceless` (path, integer, required) — Identifier of the faceless video whose transcript to read or edit. Example: `42` ### Request body Required. The segment edits to apply — word replacements or full-text rewrites. - `segments` (array of object, required) — The edits to apply, addressed by segment `index` (from the GET response). Exactly one of `words` / `text` per entry. - `index` (integer, required) — Raw index of the segment to edit (gaps included). Gap segments and out-of-range indexes are rejected with `422`. - `words` (array of string) — Word-for-word replacement — every timestamp is preserved. The count must match the segment's word count exactly (`422` otherwise); use `text` to change the word count. - `text` (string) — Full segment rewrite — the words are rebuilt and retimed within the segment's original time window, proportionally to character length. Neighbouring segments are never shifted. Example request: ```json { "segments": [ { "index": 0, "words": [ "Hello", "world" ] }, { "index": 2, "text": "An entirely rewritten sentence" } ] } ``` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "id": 9, "segments": [ { "index": 0, "text": "Hello world", "gap": false, "start": 0, "end": 1.2, "duration": 1.2, "words": [ { "text": "Hello", "start": 0, "end": 0.5 }, { "text": "world", "start": 0.6, "end": 1.2 } ] }, { "index": 1, "text": "", "gap": true, "start": 1.2, "end": 3.4, "duration": 2.2, "words": [] } ], "updated_at": "2026-08-16T12:00:00Z" } } ``` ### 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, 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. - `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). [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 Speech Standalone narration: browse the voice catalog (with free audio previews), generate speech from a script with any system voice or your own clone, and poll it to a finished mp3 with word-level timestamps. ## GET /voices **List voices** (operationId: `listVoices`) **Step 1 of 3 · Pick a voice.** Lists the narration voices available to you: the system catalog plus your own voice clones, ordered clones → standard → community. Paginated; filterable by `name` (partial), `language`, `gender`, `accent`, and `type`. Each voice carries a free `preview` url with a ready-made audio sample — play it to audition the voice before spending credits. `words_per_minute` is the voice's calibrated pace, handy for estimating how long a script will run. → **Next:** Step 2 — generate the narration with `POST /speech`. ### Parameters - `filter[name]` (query, string) — Partial, case-insensitive match on the voice name. Example: `aria` - `filter[language]` (query, string) — Exact match on the voice language. Example: `english` - `filter[gender]` (query, string) — Exact match on the voice gender. Example: `female` - `filter[accent]` (query, string) — Exact match on the voice accent. Example: `american` - `filter[type]` (query, string) — Exact match on the voice category: `standard`, `community`, or `real-clone`. Example: `standard` - `page` (query, integer) — Page number to fetch. Example: `1` - `per_page` (query, integer) — Items per page (default 12, max 50). Example: `12` ### Response `200` ```json { "message": "Success.", "status": 200, "data": [ { "id": 12, "name": "Aria", "preview": "https://cdn.syllaby.dev/voices/aria-preview.mp3", "language": "english", "accent": "american", "gender": "female", "words_per_minute": 150, "type": "standard" } ] } ``` ### 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 /speech **List speech generations** (operationId: `listSpeeches`) Lists your speech generations, newest first. Paginated. Poll individual in-flight generations with `GET /speech/{id}` rather than re-fetching the list. ### Parameters - `page` (query, integer) — Page number to fetch. Example: `1` - `per_page` (query, integer) — Items per page (default 12, max 50). Example: `12` ### Response `200` ```json { "message": "Success.", "status": 200, "data": [ { "id": 42, "status": "processing", "text": "Welcome to the show! Today we are covering the five habits that quietly transform your mornings.", "voice_id": 12, "url": null, "duration": null, "words": null, "credits": 7, "error": null, "created_at": "2026-08-16T12:00:00Z", "updated_at": "2026-08-16T12:00:00Z" }, { "id": 41, "status": "success", "text": "Welcome to the show! Today we are covering the five habits that quietly transform your mornings.", "voice_id": 12, "url": "https://cdn.syllaby.dev/speech/41/voiceover.mp3", "duration": 6.4, "words": [ { "text": "Welcome", "start": 0, "end": 0.42 } ], "credits": 7, "error": null, "created_at": "2026-08-16T12:00:00Z", "updated_at": "2026-08-16T12:00:00Z" } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 12, "total": 2 } } ``` ### 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). ## POST /speech **Generate speech** (operationId: `createSpeech`) **Step 2 of 3 · Generate.** 💳 **Charges 7 credits per started 1,000 characters** of the script (a 2,500-character script costs 21 credits). The response `meta.credits` reports the exact charge and your remaining balance. Generates spoken audio from `text` using the chosen voice — a system voice or one of your own voice clones. Returns `201` immediately with the generation in a **`processing`** state; synthesis runs asynchronously. **To track progress:** poll `GET /speech/{id}` with the **`id` from this response**, every 3–5 seconds. When `status` flips to `success` the response carries the mp3 `url`, the spoken `duration`, and **word-level timestamps** in `words` — ready-made input for captions. If generation fails, the charge is refunded automatically. Insufficient balance is rejected with `402` before anything is queued; an inactive voice or another user's clone is a `422`. → **Next:** Step 3 — track progress by polling `GET /speech/{id}` with the `id` from this response. ### Request body Required. The voice and script for the narration, plus optional tuning. - `voice_id` (integer, required) — Identifier of the voice to speak with (from `GET /voices`) — a system voice or one of your own voice clones. Inactive voices and other users' clones are rejected with `422`. - `text` (string, required) — The script to speak — up to 10,000 characters. The charge scales with length: 7 credits per started 1,000 characters. - `language` (string) — Optional ISO language code (e.g. "en") to enforce. Only applied on models that support language enforcement; cloned voices auto-detect the language from the text instead. - `settings` (object) — Optional voice-tuning overrides, merged over the voice's defaults. All fields optional. - `stability` (number) — Voice stability, 0–1. Lower is more expressive, higher is more consistent. - `style` (number) — Style exaggeration, 0–1 (ignored by models that do not support it). - `similarity_boost` (number) — Similarity boost, 0–1 (ignored by models that do not support it). - `speed` (number) — Speaking speed multiplier, 0.5–2 (ignored by models that do not support it). - `use_speaker_boost` (boolean) — Whether to boost speaker similarity (ignored by models that do not support it). Example request: ```json { "voice_id": 12, "text": "Welcome to the show! Today we are covering the five habits that quietly transform your mornings." } ``` ### Response `201` ```json { "message": "Success.", "status": 201, "data": { "id": 42, "status": "processing", "text": "Welcome to the show! Today we are covering the five habits that quietly transform your mornings.", "voice_id": 12, "url": null, "duration": null, "words": null, "credits": 7, "error": null, "created_at": "2026-08-16T12:00:00Z", "updated_at": "2026-08-16T12:00:00Z" }, "meta": { "credits": { "cost": 7, "remaining": 93 } } } ``` ### Errors - `401` — Unauthenticated - `402` — Insufficient credits — `error.required` and `error.available` carry the figures. The cost is 7 credits per started 1,000 characters of the script. - `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 /speech/{id} **Get a speech generation** (operationId: `getSpeech`) **Step 3 of 3 · Track progress.** Returns a single speech generation you own — this is the polling endpoint. Use the **`id` from the create response**; polling every 3–5 seconds is plenty. `status` is the field to branch on — match these **exact strings**: `processing` (synthesis in flight) → terminal `success` (then, and only then, `url`, `duration`, and `words` are non-null) or `failed` (`error` carries the reason and the charge has been refunded automatically). Responds with a uniform `404` when the id does not exist, belongs to another account, is not a speech generation, or is not numeric. → **Done** when `data.status` is `success` — the mp3 is at `data.url`, with word timestamps in `data.words`. On `failed`, the charge is auto-refunded; adjust and create again. ### Parameters - `id` (path, integer, required) — Identifier of the speech generation — the `id` field from the create response. Example: `42` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "id": 42, "status": "success", "text": "Welcome to the show! Today we are covering the five habits that quietly transform your mornings.", "voice_id": 12, "url": "https://cdn.syllaby.dev/speech/42/voiceover.mp3", "duration": 6.4, "words": [ { "text": "Welcome", "start": 0, "end": 0.42 }, { "text": "to", "start": 0.46, "end": 0.55 }, { "text": "the", "start": 0.58, "end": 0.66 }, { "text": "show!", "start": 0.7, "end": 1.1 } ], "credits": 7, "error": null, "created_at": "2026-08-16T12:00:00Z", "updated_at": "2026-08-16T12:00:00Z" } } ``` ### 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 speech generation, 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). [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) # Launch Clips Create a 20-second launch video from an idea: supply a product name, domain, and idea — the API writes the creative brief, renders a 1080×1920 MP4, and you poll it to completion. Flat 5 credits per clip, refunded on failure. ## GET /launch-clips/estimate **Estimate launch clip credits** (operationId: `estimateLaunchClipCredits`) **Step 1 of 3 · Check the price (optional).** Reports the flat charge for a launch clip against your balance. **Every clip costs exactly 5 credits** — the price does not vary with the idea, the brief, or the rendered length, so `data.total` always equals what `POST /launch-clips` will charge. Nothing is charged by this call. → **Next:** Step 2 — create the clip with `POST /launch-clips`. ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "total": 5 }, "meta": { "credits": { "cost": 5, "remaining": 95 } } } ``` ### 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 /launch-clips **List launch clips** (operationId: `listLaunchClips`) Lists your launch clips, newest first — the list is owner-scoped, so you only ever see your own. Paginated. Poll individual in-flight clips with `GET /launch-clips/{id}` rather than re-fetching the list. ### Parameters - `page` (query, integer) — Page number to fetch. Example: `1` - `per_page` (query, integer) — Items per page (default 12, max 50). Example: `12` ### Response `200` ```json { "message": "Success.", "status": 200, "data": [ { "id": 42, "status": "rendering", "product_name": "Launchly", "domain": "getlaunchly.io", "idea": "A tool that turns a one-line product idea into a ready-to-post 20-second launch video.", "accent_color": "#7C3AED", "video": null, "credits": 5, "error": null, "created_at": "2026-08-18T12:00:00Z", "updated_at": "2026-08-18T12:00:00Z" }, { "id": 41, "status": "completed", "product_name": "Launchly", "domain": "getlaunchly.io", "idea": "A tool that turns a one-line product idea into a ready-to-post 20-second launch video.", "accent_color": "#7C3AED", "video": { "id": 6, "title": "Launchly — launch clip", "url": "https://cdn.syllaby.dev/launch-clips/41/launch-clip.mp4" }, "credits": 5, "error": null, "created_at": "2026-08-18T12:00:00Z", "updated_at": "2026-08-18T12:00:00Z" } ], "meta": { "current_page": 1, "last_page": 1, "per_page": 12, "total": 2 } } ``` ### 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). ## POST /launch-clips **Create a launch clip** (operationId: `createLaunchClip`) **Step 2 of 3 · Create.** 💳 **Charges a flat 5 credits.** The response `meta.credits` reports the charge and your remaining balance. Creates a 20-second, 1080×1920 launch video from three inputs: the `product_name`, the `domain` to show as the call-to-action, and the `idea` in your own words. The API writes the creative brief (hook, benefits, CTA) from the idea and renders the MP4 — no script, voice, or media required. An optional `accent_color` pins the brand color. Returns `201` immediately with the clip in a **`planning`** state; briefing and rendering run asynchronously. **To track progress:** poll `GET /launch-clips/{id}` with the **`id` from this response**, every 5–10 seconds. If briefing or rendering fails, the 5-credit charge is refunded automatically. Insufficient balance is rejected with `402` before anything is queued; a plan that does not include launch clips is a `403` with code `FEATURE-NOT-ALLOWED`. → **Next:** Step 3 — track progress by polling `GET /launch-clips/{id}` with the `id` from this response. ### Request body Required. The product name, domain, and idea for the clip, plus an optional accent color. - `product_name` (string, required) — Name of the product the clip announces — up to 40 characters, rendered verbatim in the clip. - `domain` (string, required) — Domain rendered as the clip's call-to-action (e.g. "getlaunchly.io") — up to 60 characters. It is rendered as text, never fetched or validated for reachability. - `idea` (string, required) — What the product does, in your own words — 10 to 500 characters. The API writes the clip's creative brief (hook, benefits, call-to-action) from this. - `accent_color` (string) — Optional brand accent color as a `#RRGGBB` hex value (e.g. "#7C3AED"). Omit to let the renderer pick a palette from the brief. Example request: ```json { "product_name": "Launchly", "domain": "getlaunchly.io", "idea": "A tool that turns a one-line product idea into a ready-to-post 20-second launch video.", "accent_color": "#7C3AED" } ``` ### Response `201` ```json { "message": "Success.", "status": 201, "data": { "id": 42, "status": "planning", "product_name": "Launchly", "domain": "getlaunchly.io", "idea": "A tool that turns a one-line product idea into a ready-to-post 20-second launch video.", "accent_color": "#7C3AED", "video": null, "credits": 5, "error": null, "created_at": "2026-08-18T12:00:00Z", "updated_at": "2026-08-18T12:00:00Z" }, "meta": { "credits": { "cost": 5, "remaining": 95 } } } ``` ### Errors - `401` — Unauthenticated - `402` — Insufficient credits — `error.required` and `error.available` carry the figures. The cost is a flat 5 credits per clip. - `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. **Feature not on the plan** (code `FEATURE-NOT-ALLOWED`) — the subscription is active but launch clips are not included in the current plan. - `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 /launch-clips/{id} **Get a launch clip** (operationId: `getLaunchClip`) **Step 3 of 3 · Track progress.** Returns a single launch clip you own — this is the polling endpoint. Use the **`id` from the create response**; polling every 5–10 seconds is plenty. `status` is the field to branch on — match these **exact strings**: `planning` (the creative brief is being written) → `briefed` (brief ready, render queued) → `rendering` (the MP4 is being composed) → terminal `completed` (then, and only then, `video` is non-null and the MP4 is at `video.url`) or `failed` (`error` carries the reason and the charge has been refunded automatically). Responds with a uniform `404` when the id does not exist, belongs to another account, is not a launch clip, or is not numeric. → **Done** when `data.status` is `completed` — the MP4 is at `data.video.url`. On `failed`, the 5-credit charge is auto-refunded; adjust the idea and create again. ### Parameters - `id` (path, integer, required) — Identifier of the launch clip — the `id` field from the create response. Example: `42` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "id": 42, "status": "completed", "product_name": "Launchly", "domain": "getlaunchly.io", "idea": "A tool that turns a one-line product idea into a ready-to-post 20-second launch video.", "accent_color": "#7C3AED", "video": { "id": 7, "title": "Launchly — launch clip", "url": "https://cdn.syllaby.dev/launch-clips/42/launch-clip.mp4" }, "credits": 5, "error": null, "created_at": "2026-08-18T12:00:00Z", "updated_at": "2026-08-18T12:00:00Z" } } ``` ### 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 launch 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). [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) # B-roll Generate stock-footage videos from a narration script. ## POST /b-roll **Create a B-roll video** (operationId: `createBrollVideo`) Creates a new B-roll video in draft state (its `type` is fixed at `b-roll` and cannot be changed later). Returns the created video with its render state embedded under `data.video`. Keep the returned `id` — it identifies the video in every follow-up call, including polling `GET /b-roll/{id}`. **Requires an active subscription** — the public API has no free tier. ### Request body Required. - `title` (string, required) — Display name of the video (max 255 characters). - `idea_id` (integer) — Optional identifier of a Syllaby content idea to link the video to. The v2 API exposes no ideas endpoints, so omit it for API-only flows. - `starts_at` (string) — Optional ISO-8601 timestamp that schedules the video on your Syllaby content calendar. Does not delay the render. - `ends_at` (string) — Optional ISO-8601 end timestamp for the scheduled calendar slot. Must be the same as or after `starts_at`. Example request: ```json { "title": "My B-roll video" } ``` ### Response `201` ```json { "message": "Success.", "status": 201, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "b-roll", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "Three habits that quietly improve your focus.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": null, "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z", "video": { "id": 10, "user_id": 1, "idea_id": null, "scheduler_id": null, "title": "Focus habits", "type": "faceless", "url": null, "status": "draft", "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" } } } ``` ### 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). ## PUT /b-roll/{faceless}/scripts **Generate a B-roll script** (operationId: `generateBrollScript`) 💳 Charges credits. The response `meta.credits` shows the cost and your remaining balance. Generates a narration script for the B-roll video from a topic, tone, style, language, and target duration, and stores it on the video. This is a `PUT` — each call regenerates and overwrites the previous script. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` ### Request body Required. - `style` (string, required) — Required — narrative style for the generated script (free-form text, max 255 chars). Examples: educational, storytelling, listicle, conversational, motivational. - `tone` (string, required) — Tone of voice for generated content (e.g. "professional"). - `language` (string, required) — Language of the content (e.g. "english"). - `topic` (string, required) — Topic or subject of the content. - `duration` (integer, required) — Target length of the generated script in seconds. Must be one of 30, 60, 180, 300, 600, 900, or 1200. Allowed values: `30`, `60`, `180`, `300`, `600`, `900`, `1200`. Example request: ```json { "topic": "Morning routines for better focus", "tone": "professional", "style": "educational", "language": "english", "duration": 60 } ``` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "b-roll", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "Three habits that quietly improve your focus.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": null, "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z", "video": { "id": 10, "user_id": 1, "idea_id": null, "scheduler_id": null, "title": "Focus habits", "type": "faceless", "url": null, "status": "draft", "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" } }, "meta": { "credits": { "cost": 5, "remaining": 875 } } } ``` ### 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 /b-roll/{id} **Get a B-roll video** (operationId: `getBrollVideo`) Returns a single B-roll video you own. The render state is embedded under `data.video`: `url` (null until the render completes), `status`, and `failure`. Responds with `404` when the id does not exist or belongs to another account. Pass the faceless `id` (not `video_id`). ### Parameters - `id` (path, string, required) — Identifier of the faceless video. Example: `1` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "b-roll", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "Three habits that quietly improve your focus.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": null, "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z", "video": { "id": 10, "user_id": 1, "idea_id": null, "scheduler_id": null, "title": "Focus habits", "type": "faceless", "url": "https://cdn.syllaby.dev/videos/10/final.mp4", "status": "completed", "retries": 0, "hash": "abc123", "synced_at": "2026-01-01T12:00:00.000000Z", "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" } } } ``` ### 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 /b-roll/{id} **Update a B-roll video** (operationId: `updateBrollVideo`) Updates the B-roll video's configuration (script, voice, genre, captions, transitions, and more). Only the fields you send are changed. The video must not be busy (rendering or syncing). ### Parameters - `id` (path, string, required) — Identifier of the faceless video. Example: `1` ### Request body Optional. - `voice_id` (integer) — Optional — identifier of the narration voice (see faceless options). Omit voice entirely to render without narration. - `background_id` (integer) — Optional — identifier of the background asset (see faceless options). - `music_id` (integer|null) — Optional — identifier of the background music track (media id), or null to remove music. - `genre_id` (integer) — Optional — identifier of the genre/style (see faceless options). Required at render time for the `ai-visuals` and `ai-clips` types. - `image_engine_id` (integer) — Optional — identifier of the text-to-image engine (see faceless options). Relevant only for the `ai-visuals` and `ai-clips` types. - `clip_engine_id` (integer) — Optional — identifier of the text-to-video (clip) engine (see faceless options). Relevant only for clip-based types. - `transition` (string|null) — Optional — transition slug applied between scenes (see faceless options). Allowed values: `slide-left`, `slide-right`, `slide-up`, `slide-down`, `scale-in`, `scale-out`, `zoom-in`, `zoom-out`, `rotate-left`, `rotate-right`, `fade`, `none`, `mixed`, `pop`, `dreamy`, `swing`, `spin-right`, `spin-left`, `swoosh-left`, `swoosh-right`, `glide-left`, `glide-right`, `drop`, `tumble-left`, `tumble-right`, `float`, `rise-left`, `rise-right`, `bounce`, `flash`, `crossfade`, `blur-dissolve`, `zoom-dissolve`. - `animation` (string|null) — Optional — per-image motion effect slug (e.g. "pan-in"). See faceless options. Allowed values: `pan-in`, `pan-out`, `pan-left`, `pan-right`, `pan-up`, `pan-down`, `rotate-left-in`, `rotate-right-in`, `float`, `none`, `mixed`. - `sfx` (string|null) — Optional — sound-effect slug applied to the video (see faceless options). Allowed values: `none`, `whoosh`. - `volume` (string|null) — Optional — background-music volume level: "low", "medium", or "high". Allowed values: `low`, `medium`, `high`. - `script` (string) — Optional — supply a full custom script to use verbatim instead of generating one. Provide this via PATCH /faceless/{faceless}; the script-generation step is then unnecessary. - `duration` (integer) — Optional — target video length in seconds. Any positive integer; not restricted to the script-generation presets (those fixed values apply only to PUT /faceless/{faceless}/scripts). - `aspect_ratio` (string) — Optional — output aspect ratio: "16:9", "9:16", or "1:1". Allowed values: `16:9`, `9:16`, `1:1`. - `captions` (object) — Optional — caption styling. Only `font_family`, `font_color`, `font_url`, and `position` are honored on update; any other caption keys are ignored. - `font_family` (string) — Caption font family slug (see faceless options). - `font_color` (string) — Caption font color as a hex value (e.g. "#FFFFFF"). - `font_url` (string|null) — URL of a custom font file to use for captions. - `position` (string) — On-screen position slug (e.g. "bottom", "center", "top"). Allowed values: `top`, `bottom`, `center`. Example request: ```json { "voice_id": 12, "genre_id": 3, "script": "Three habits that quietly improve your focus.", "transition": "fade", "captions": { "font_family": "inter", "font_color": "#FFFFFF", "position": "bottom" } } ``` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "b-roll", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "Three habits that quietly improve your focus.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": 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). ## GET /b-roll/{faceless}/estimate **Estimate B-roll render credits** (operationId: `estimateBrollVideo`) Estimates the credits required to render the B-roll video and compares them against your available balance. The video must already have a script and a voice configured. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "required": 30, "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 /b-roll/{faceless}/render **Render a B-roll video** (operationId: `renderBrollVideo`) 💳 Charges credits. The response `meta.credits` shows the cost and your remaining balance. Starts rendering the B-roll video asynchronously and returns HTTP 202. Track progress by polling `GET /b-roll/{id}` and reading `data.video.status` (`rendering` → `completed`/`failed`); the playable file appears at `data.video.url` on completion. Common preconditions checked before any charge: a script and voice must be set, credits must be sufficient (`402`), and the video must not be busy. **Fields omitted from the render body are filled from the video's saved configuration** — a value set at creation, via `PATCH /b-roll/{id}`, or on a previous render is reused automatically, and an explicit body value always wins. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` ### Request body Optional. Render configuration for the faceless video. Every field is optional: `script`, `duration`, and `aspect_ratio` are auto-filled from the saved draft when omitted (configure it first via `PUT /faceless/{faceless}/scripts` and `PATCH /faceless/{faceless}`), so a fully-configured video can be rendered with an empty body. A `422` is only returned when a required value is missing from both the body and the draft. - `script` (string) — Narration script text for the video. Optional — falls back to the draft's stored script when omitted. - `duration` (integer) — Target video length in seconds (a positive integer). Optional — falls back to the draft's estimated duration when omitted. Not restricted to the script-generation presets (those fixed values apply only to PUT /faceless/{faceless}/scripts). - `aspect_ratio` (string) — Output aspect ratio. Optional — falls back to the draft's stored aspect ratio when omitted. Allowed values are listed under faceless options (aspect_ratios). - `title` (string) — Optional title for the video. - `voice_id` (integer) — Identifier of the narration voice (see faceless options). - `background_id` (integer) — Identifier of the background asset (see faceless options). - `type` (string) — Faceless video type — controls how the visuals are sourced (stock b-roll, a source URL, AI-generated images, or AI-generated clips). Allowed values: `b-roll`, `url-based`, `ai-visuals`, `ai-clips`. - `genre_id` (integer) — Identifier of the genre/style that drives the image-prompt look. REQUIRED for image-based renders (`type` of `ai-visuals` or `ai-clips`); optional for `b-roll` / `url-based` renders that don't use a genre. Allowed values are listed under faceless options (genres). - `character_id` (integer) — Identifier of the consistent character (see faceless options). - `image_engine_id` (integer) — Identifier of the text-to-image engine (see faceless options). - `clip_engine_id` (integer) — Identifier of the text-to-video (clip) engine (see faceless options). - `transition` (string) — Transition slug applied between scenes. Allowed values are listed under faceless options (transitions). - `animation` (string) — Per-image motion effect slug. Allowed values are listed under faceless options (animations). - `overlay` (string) — Overlay style slug applied over the video. Allowed values are listed under faceless options (overlays). Render-body-only: set it here (or on export) — `PATCH /faceless/{faceless}` silently ignores it. - `sfx` (string) — Sound-effect slug applied to the video. Allowed values are listed under faceless options (sfx). - `ai_labels` (boolean) — Whether AI-content disclosure labels are applied on publish. - `custom_description` (string) — Custom caption/description applied to published posts. - `destination_id` (integer) — Identifier of the destination folder/resource to file the video under. - `captions` (object) — Caption styling. Only `font_family`, `font_color`, `font_url`, `position`, and `effect` are honored; any other caption keys are ignored. - `font_family` (string) — Caption font family slug (see faceless options). - `font_color` (string) — Caption font color as a hex value (e.g. "#FFFFFF"). - `font_url` (string) — URL of a custom font file to use for captions. - `position` (string) — On-screen caption position slug. Allowed values are listed under faceless options (caption_positions). - `effect` (string) — Caption effect slug applied to on-screen text. Allowed values are listed under faceless options (caption_effects). Render-body-only: set it here (or on export) — `PATCH /faceless/{faceless}` silently ignores it. - `watermark` (object) — Watermark image and placement. Provide exactly one source — `id`, `url`, or `file` (mutually exclusive). - `id` (integer) — Identifier of a watermark asset you own. Mutually exclusive with `url` and `file`. - `url` (string) — Remote URL of the watermark image. Mutually exclusive with `id` and `file`. - `file` (string) — Uploaded watermark image file (jpg, jpeg, png, webp; max 5 MB). Mutually exclusive with `id` and `url`. - `position` (string) — Watermark placement slug. Allowed values are listed under faceless options (watermark_positions). - `opacity` (integer) — Watermark opacity as a percentage (0–100). - `music_id` (integer) — Identifier of a background music track (media id). Mutually exclusive with `music.url` and `music.file`. - `music` (object) — Background music source. Provide exactly one of `url` or `file`, and only when `music_id` is omitted (all three are mutually exclusive). - `url` (string) — Remote URL of a music track. Mutually exclusive with `music_id` and `music.file`. - `file` (string) — Uploaded music file (mp3, wav, aac, m4a, ogg; max 20 MB). Mutually exclusive with `music_id` and `music.url`. - `volume` (string) — Background-music volume level. Required when any music source is set. Allowed values: `low`, `medium`, `high`. - `publications` (array of object) — Social posts to schedule for the rendered video. - `channel_id` (integer) — Identifier of the connected social channel to publish to. - `scheduled_at` (string) — Future timestamp to publish the post, or null to publish immediately. - `assets` (array of object) — Explicit ordered media assets to compose the video from. Orders must start at 0 and be consecutive with no gaps. - `id` (integer, required) — Identifier of the media asset. - `order` (integer, required) — Zero-based position of the asset in the sequence. Example request: ```json { "script": "Three small habits that quietly improve your focus every day.", "duration": 60, "aspect_ratio": "9:16", "type": "ai-visuals", "title": "Focus habits", "voice_id": 12, "genre_id": 3, "image_engine_id": 1, "clip_engine_id": 2, "transition": "fade", "animation": "pan-in", "overlay": "none", "sfx": "whoosh", "volume": "medium", "music_id": 8, "captions": { "font_family": "inter", "font_color": "#FFFFFF", "position": "bottom", "effect": "highlight" }, "watermark": { "id": 42, "position": "bottom-right", "opacity": 80 }, "publications": [ { "channel_id": 5, "scheduled_at": "2025-02-01T18:30:00Z" } ], "assets": [ { "id": 101, "order": 0 }, { "id": 102, "order": 1 } ] } ``` ### Response `202` ```json { "message": "Success.", "status": 202, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "b-roll", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "Three habits that quietly improve your focus.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": null, "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z" }, "meta": { "credits": { "cost": 120, "remaining": 880 } } } ``` ### 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 /b-roll/{faceless}/retry **Retry a B-roll render** (operationId: `retryBrollVideo`) 💳 Charges credits. The response `meta.credits` shows the cost and your remaining balance. Re-attempts rendering for a B-roll video whose embedded video previously failed (`data.video.status` is `failed`). Returns HTTP 202; track it the same way as render. Only failed renders can be retried. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` ### Response `202` ```json { "message": "Success.", "status": 202, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "b-roll", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "Three habits that quietly improve your focus.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": null, "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z", "video": { "id": 10, "user_id": 1, "idea_id": null, "scheduler_id": null, "title": "Focus habits", "type": "faceless", "url": null, "status": "rendering", "retries": 1, "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" } }, "meta": { "credits": { "cost": 120, "remaining": 880 } } } ``` ### 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. - `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 /b-roll/{faceless}/export **Export a B-roll video** (operationId: `exportBrollVideo`) 💳 Charges credits. The response `meta.credits` shows the cost and your remaining balance. Rebuilds the rendered B-roll video with the supplied styling (captions, watermark, music, transitions) and queues an export. Returns HTTP 202. The video must already be rendered and not currently busy. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` ### Request body Optional. - `transition` (string) — Transition slug applied between scenes (see faceless options). Allowed values: `slide-left`, `slide-right`, `slide-up`, `slide-down`, `scale-in`, `scale-out`, `zoom-in`, `zoom-out`, `rotate-left`, `rotate-right`, `fade`, `none`, `mixed`, `pop`, `dreamy`, `swing`, `spin-right`, `spin-left`, `swoosh-left`, `swoosh-right`, `glide-left`, `glide-right`, `drop`, `tumble-left`, `tumble-right`, `float`, `rise-left`, `rise-right`, `bounce`, `flash`, `crossfade`, `blur-dissolve`, `zoom-dissolve`. - `overlay` (string) — Overlay style slug applied over the video (see faceless options). Allowed values: `none`, `vhs`, `rain`, `glitch`, `dust`, `sparkling-gold`, `spark-effect`, `abstract-particles`. - `sfx` (string|null) — Sound-effect slug applied to the video (see faceless options). Allowed values: `none`, `whoosh`. - `volume` (string) — Background-music volume level: "low", "medium", or "high". Allowed values: `low`, `medium`, `high`. - `music_id` (integer|null) — Identifier of the background music track (media id). - `transcriptions` (array of array of string) — Per-scene transcription overrides keyed by scene. - `captions` (object) — Caption styling options (font, color, position, effect). - `font_family` (string|null) — Caption font family slug (see faceless options). - `font_url` (string|null) — URL of a custom font file to use for captions. - `font_color` (string|null) — Caption font color as a hex value (e.g. "#FFFFFF"). - `position` (string) — On-screen position slug (e.g. "bottom", "center", "top"). Allowed values: `top`, `bottom`, `center`. - `effect` (string) — Caption effect slug applied to on-screen text (see faceless options). Allowed values: `karaoke`, `highlight`, `fade`, `bounce`, `slide`, `enlarge`. - `watermark` (object) — Watermark image and placement options. - `id` (integer|null) — Unique numeric identifier of the resource. - `position` (string|null) — On-screen position slug (e.g. "bottom", "center", "top"). Allowed values: `top-left`, `top-center`, `top-right`, `middle-left`, `middle-center`, `middle-right`, `bottom-left`, `bottom-center`, `bottom-right`, `none`. - `opacity` (integer|null) — Watermark opacity as a percentage (0–100). Example request: ```json { "transition": "fade", "volume": "medium", "captions": { "font_family": "inter", "font_color": "#FFFFFF", "position": "bottom", "effect": "highlight" }, "watermark": { "id": 42, "position": "bottom-right", "opacity": 80 } } ``` ### Response `202` ```json { "message": "Success.", "status": 202, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "b-roll", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "Three habits that quietly improve your focus.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": null, "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z", "video": { "id": 10, "user_id": 1, "idea_id": null, "scheduler_id": null, "title": "Focus habits", "type": "faceless", "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" } }, "meta": { "credits": { "cost": 50, "remaining": 830 } } } ``` ### 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 /b-roll/{faceless}/assets **List B-roll assets** (operationId: `listBrollAssets`) Lists the media assets (images, clips) that make up the B-roll video, ordered by scene. Assets are produced during rendering — expect an empty list until the video has been rendered at least once. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` - `index` (query, string) — Zero-based scene position; returns only the asset at that position. Example: `0` ### Response `200` ```json { "message": "Success.", "status": 200, "data": [ { "id": 1, "user_id": 1, "type": "faceless_background", "status": "success", "order": 0, "media": [ { "id": 5, "name": "scene-1", "file_name": "scene-1.png", "mime_type": "image/png", "extension": "png", "download_url": "https://cdn.syllaby.dev/assets/scene-1.png" } ], "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). [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) # AI-Visuals Generate videos with AI-generated imagery, with per-scene upload and regeneration. ## POST /ai-visuals **Create a AI-Visuals video** (operationId: `createAiVisualsVideo`) Creates a new AI-Visuals video in draft state (its `type` is fixed at `ai-visuals` and cannot be changed later). Returns the created video with its render state embedded under `data.video`. Keep the returned `id` — it identifies the video in every follow-up call, including polling `GET /ai-visuals/{id}`. **Requires an active subscription** — the public API has no free tier. ### Request body Required. - `title` (string, required) — Display name of the video (max 255 characters). - `idea_id` (integer) — Optional identifier of a Syllaby content idea to link the video to. The v2 API exposes no ideas endpoints, so omit it for API-only flows. - `starts_at` (string) — Optional ISO-8601 timestamp that schedules the video on your Syllaby content calendar. Does not delay the render. - `ends_at` (string) — Optional ISO-8601 end timestamp for the scheduled calendar slot. Must be the same as or after `starts_at`. Example request: ```json { "title": "My AI-Visuals video" } ``` ### Response `201` ```json { "message": "Success.", "status": 201, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "ai-visuals", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "Three habits that quietly improve your focus.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": null, "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z", "video": { "id": 10, "user_id": 1, "idea_id": null, "scheduler_id": null, "title": "Focus habits", "type": "faceless", "url": null, "status": "draft", "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" } } } ``` ### 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). ## PUT /ai-visuals/{faceless}/scripts **Generate a AI-Visuals script** (operationId: `generateAiVisualsScript`) 💳 Charges credits. The response `meta.credits` shows the cost and your remaining balance. Generates a narration script for the AI-Visuals video from a topic, tone, style, language, and target duration, and stores it on the video. This is a `PUT` — each call regenerates and overwrites the previous script. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` ### Request body Required. - `style` (string, required) — Required — narrative style for the generated script (free-form text, max 255 chars). Examples: educational, storytelling, listicle, conversational, motivational. - `tone` (string, required) — Tone of voice for generated content (e.g. "professional"). - `language` (string, required) — Language of the content (e.g. "english"). - `topic` (string, required) — Topic or subject of the content. - `duration` (integer, required) — Target length of the generated script in seconds. Must be one of 30, 60, 180, 300, 600, 900, or 1200. Allowed values: `30`, `60`, `180`, `300`, `600`, `900`, `1200`. Example request: ```json { "topic": "Morning routines for better focus", "tone": "professional", "style": "educational", "language": "english", "duration": 60 } ``` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "ai-visuals", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "Three habits that quietly improve your focus.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": null, "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z", "video": { "id": 10, "user_id": 1, "idea_id": null, "scheduler_id": null, "title": "Focus habits", "type": "faceless", "url": null, "status": "draft", "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" } }, "meta": { "credits": { "cost": 5, "remaining": 875 } } } ``` ### 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 /ai-visuals/{id} **Get a AI-Visuals video** (operationId: `getAiVisualsVideo`) Returns a single AI-Visuals video you own. The render state is embedded under `data.video`: `url` (null until the render completes), `status`, and `failure`. Responds with `404` when the id does not exist or belongs to another account. Pass the faceless `id` (not `video_id`). ### Parameters - `id` (path, string, required) — Identifier of the faceless video. Example: `1` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "ai-visuals", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "Three habits that quietly improve your focus.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": null, "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z", "video": { "id": 10, "user_id": 1, "idea_id": null, "scheduler_id": null, "title": "Focus habits", "type": "faceless", "url": "https://cdn.syllaby.dev/videos/10/final.mp4", "status": "completed", "retries": 0, "hash": "abc123", "synced_at": "2026-01-01T12:00:00.000000Z", "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" } } } ``` ### 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 /ai-visuals/{id} **Update a AI-Visuals video** (operationId: `updateAiVisualsVideo`) Updates the AI-Visuals video's configuration (script, voice, genre, captions, transitions, and more). Only the fields you send are changed. The video must not be busy (rendering or syncing). ### Parameters - `id` (path, string, required) — Identifier of the faceless video. Example: `1` ### Request body Optional. - `voice_id` (integer) — Optional — identifier of the narration voice (see faceless options). Omit voice entirely to render without narration. - `background_id` (integer) — Optional — identifier of the background asset (see faceless options). - `music_id` (integer|null) — Optional — identifier of the background music track (media id), or null to remove music. - `genre_id` (integer) — Optional — identifier of the genre/style (see faceless options). Required at render time for the `ai-visuals` and `ai-clips` types. - `image_engine_id` (integer) — Optional — identifier of the text-to-image engine (see faceless options). Relevant only for the `ai-visuals` and `ai-clips` types. - `clip_engine_id` (integer) — Optional — identifier of the text-to-video (clip) engine (see faceless options). Relevant only for clip-based types. - `transition` (string|null) — Optional — transition slug applied between scenes (see faceless options). Allowed values: `slide-left`, `slide-right`, `slide-up`, `slide-down`, `scale-in`, `scale-out`, `zoom-in`, `zoom-out`, `rotate-left`, `rotate-right`, `fade`, `none`, `mixed`, `pop`, `dreamy`, `swing`, `spin-right`, `spin-left`, `swoosh-left`, `swoosh-right`, `glide-left`, `glide-right`, `drop`, `tumble-left`, `tumble-right`, `float`, `rise-left`, `rise-right`, `bounce`, `flash`, `crossfade`, `blur-dissolve`, `zoom-dissolve`. - `animation` (string|null) — Optional — per-image motion effect slug (e.g. "pan-in"). See faceless options. Allowed values: `pan-in`, `pan-out`, `pan-left`, `pan-right`, `pan-up`, `pan-down`, `rotate-left-in`, `rotate-right-in`, `float`, `none`, `mixed`. - `sfx` (string|null) — Optional — sound-effect slug applied to the video (see faceless options). Allowed values: `none`, `whoosh`. - `volume` (string|null) — Optional — background-music volume level: "low", "medium", or "high". Allowed values: `low`, `medium`, `high`. - `script` (string) — Optional — supply a full custom script to use verbatim instead of generating one. Provide this via PATCH /faceless/{faceless}; the script-generation step is then unnecessary. - `duration` (integer) — Optional — target video length in seconds. Any positive integer; not restricted to the script-generation presets (those fixed values apply only to PUT /faceless/{faceless}/scripts). - `aspect_ratio` (string) — Optional — output aspect ratio: "16:9", "9:16", or "1:1". Allowed values: `16:9`, `9:16`, `1:1`. - `captions` (object) — Optional — caption styling. Only `font_family`, `font_color`, `font_url`, and `position` are honored on update; any other caption keys are ignored. - `font_family` (string) — Caption font family slug (see faceless options). - `font_color` (string) — Caption font color as a hex value (e.g. "#FFFFFF"). - `font_url` (string|null) — URL of a custom font file to use for captions. - `position` (string) — On-screen position slug (e.g. "bottom", "center", "top"). Allowed values: `top`, `bottom`, `center`. Example request: ```json { "voice_id": 12, "genre_id": 3, "script": "Three habits that quietly improve your focus.", "transition": "fade", "captions": { "font_family": "inter", "font_color": "#FFFFFF", "position": "bottom" } } ``` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "ai-visuals", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "Three habits that quietly improve your focus.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": 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). ## GET /ai-visuals/{faceless}/estimate **Estimate AI-Visuals render credits** (operationId: `estimateAiVisualsVideo`) Estimates the credits required to render the AI-Visuals video and compares them against your available balance. The video must already have a script and a voice configured. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "required": 30, "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 /ai-visuals/{faceless}/render **Render a AI-Visuals video** (operationId: `renderAiVisualsVideo`) 💳 Charges credits. The response `meta.credits` shows the cost and your remaining balance. Starts rendering the AI-Visuals video asynchronously and returns HTTP 202. Track progress by polling `GET /ai-visuals/{id}` and reading `data.video.status` (`rendering` → `completed`/`failed`); the playable file appears at `data.video.url` on completion. Common preconditions checked before any charge: a script and voice must be set, credits must be sufficient (`402`), and the video must not be busy. **Fields omitted from the render body are filled from the video's saved configuration** — a value set at creation, via `PATCH /ai-visuals/{id}`, or on a previous render is reused automatically, and an explicit body value always wins. For `ai-visuals`, `genre_id` and `image_engine_id` are **required** in that combined sense: each must resolve from the render body or the saved draft, and a render that resolves neither returns `422` on that key. Browse valid ids at `GET /faceless/options`. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` ### Request body Optional. Render configuration for the faceless video. Every field is optional: `script`, `duration`, and `aspect_ratio` are auto-filled from the saved draft when omitted (configure it first via `PUT /faceless/{faceless}/scripts` and `PATCH /faceless/{faceless}`), so a fully-configured video can be rendered with an empty body. A `422` is only returned when a required value is missing from both the body and the draft. - `script` (string) — Narration script text for the video. Optional — falls back to the draft's stored script when omitted. - `duration` (integer) — Target video length in seconds (a positive integer). Optional — falls back to the draft's estimated duration when omitted. Not restricted to the script-generation presets (those fixed values apply only to PUT /faceless/{faceless}/scripts). - `aspect_ratio` (string) — Output aspect ratio. Optional — falls back to the draft's stored aspect ratio when omitted. Allowed values are listed under faceless options (aspect_ratios). - `title` (string) — Optional title for the video. - `voice_id` (integer) — Identifier of the narration voice (see faceless options). - `background_id` (integer) — Identifier of the background asset (see faceless options). - `type` (string) — Faceless video type — controls how the visuals are sourced (stock b-roll, a source URL, AI-generated images, or AI-generated clips). Allowed values: `b-roll`, `url-based`, `ai-visuals`, `ai-clips`. - `genre_id` (integer) — Identifier of the genre/style that drives the image-prompt look. REQUIRED for image-based renders (`type` of `ai-visuals` or `ai-clips`); optional for `b-roll` / `url-based` renders that don't use a genre. Allowed values are listed under faceless options (genres). - `character_id` (integer) — Identifier of the consistent character (see faceless options). - `image_engine_id` (integer) — Identifier of the text-to-image engine (see faceless options). - `clip_engine_id` (integer) — Identifier of the text-to-video (clip) engine (see faceless options). - `transition` (string) — Transition slug applied between scenes. Allowed values are listed under faceless options (transitions). - `animation` (string) — Per-image motion effect slug. Allowed values are listed under faceless options (animations). - `overlay` (string) — Overlay style slug applied over the video. Allowed values are listed under faceless options (overlays). Render-body-only: set it here (or on export) — `PATCH /faceless/{faceless}` silently ignores it. - `sfx` (string) — Sound-effect slug applied to the video. Allowed values are listed under faceless options (sfx). - `ai_labels` (boolean) — Whether AI-content disclosure labels are applied on publish. - `custom_description` (string) — Custom caption/description applied to published posts. - `destination_id` (integer) — Identifier of the destination folder/resource to file the video under. - `captions` (object) — Caption styling. Only `font_family`, `font_color`, `font_url`, `position`, and `effect` are honored; any other caption keys are ignored. - `font_family` (string) — Caption font family slug (see faceless options). - `font_color` (string) — Caption font color as a hex value (e.g. "#FFFFFF"). - `font_url` (string) — URL of a custom font file to use for captions. - `position` (string) — On-screen caption position slug. Allowed values are listed under faceless options (caption_positions). - `effect` (string) — Caption effect slug applied to on-screen text. Allowed values are listed under faceless options (caption_effects). Render-body-only: set it here (or on export) — `PATCH /faceless/{faceless}` silently ignores it. - `watermark` (object) — Watermark image and placement. Provide exactly one source — `id`, `url`, or `file` (mutually exclusive). - `id` (integer) — Identifier of a watermark asset you own. Mutually exclusive with `url` and `file`. - `url` (string) — Remote URL of the watermark image. Mutually exclusive with `id` and `file`. - `file` (string) — Uploaded watermark image file (jpg, jpeg, png, webp; max 5 MB). Mutually exclusive with `id` and `url`. - `position` (string) — Watermark placement slug. Allowed values are listed under faceless options (watermark_positions). - `opacity` (integer) — Watermark opacity as a percentage (0–100). - `music_id` (integer) — Identifier of a background music track (media id). Mutually exclusive with `music.url` and `music.file`. - `music` (object) — Background music source. Provide exactly one of `url` or `file`, and only when `music_id` is omitted (all three are mutually exclusive). - `url` (string) — Remote URL of a music track. Mutually exclusive with `music_id` and `music.file`. - `file` (string) — Uploaded music file (mp3, wav, aac, m4a, ogg; max 20 MB). Mutually exclusive with `music_id` and `music.url`. - `volume` (string) — Background-music volume level. Required when any music source is set. Allowed values: `low`, `medium`, `high`. - `publications` (array of object) — Social posts to schedule for the rendered video. - `channel_id` (integer) — Identifier of the connected social channel to publish to. - `scheduled_at` (string) — Future timestamp to publish the post, or null to publish immediately. - `assets` (array of object) — Explicit ordered media assets to compose the video from. Orders must start at 0 and be consecutive with no gaps. - `id` (integer, required) — Identifier of the media asset. - `order` (integer, required) — Zero-based position of the asset in the sequence. Example request: ```json { "script": "Three small habits that quietly improve your focus every day.", "duration": 60, "aspect_ratio": "9:16", "type": "ai-visuals", "title": "Focus habits", "voice_id": 12, "genre_id": 3, "image_engine_id": 1, "clip_engine_id": 2, "transition": "fade", "animation": "pan-in", "overlay": "none", "sfx": "whoosh", "volume": "medium", "music_id": 8, "captions": { "font_family": "inter", "font_color": "#FFFFFF", "position": "bottom", "effect": "highlight" }, "watermark": { "id": 42, "position": "bottom-right", "opacity": 80 }, "publications": [ { "channel_id": 5, "scheduled_at": "2025-02-01T18:30:00Z" } ], "assets": [ { "id": 101, "order": 0 }, { "id": 102, "order": 1 } ] } ``` ### Response `202` ```json { "message": "Success.", "status": 202, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "ai-visuals", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "Three habits that quietly improve your focus.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": null, "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z" }, "meta": { "credits": { "cost": 120, "remaining": 880 } } } ``` ### 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 /ai-visuals/{faceless}/retry **Retry a AI-Visuals render** (operationId: `retryAiVisualsVideo`) 💳 Charges credits. The response `meta.credits` shows the cost and your remaining balance. Re-attempts rendering for a AI-Visuals video whose embedded video previously failed (`data.video.status` is `failed`). Returns HTTP 202; track it the same way as render. Only failed renders can be retried. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` ### Response `202` ```json { "message": "Success.", "status": 202, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "ai-visuals", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "Three habits that quietly improve your focus.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": null, "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z", "video": { "id": 10, "user_id": 1, "idea_id": null, "scheduler_id": null, "title": "Focus habits", "type": "faceless", "url": null, "status": "rendering", "retries": 1, "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" } }, "meta": { "credits": { "cost": 120, "remaining": 880 } } } ``` ### 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. - `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 /ai-visuals/{faceless}/export **Export a AI-Visuals video** (operationId: `exportAiVisualsVideo`) 💳 Charges credits. The response `meta.credits` shows the cost and your remaining balance. Rebuilds the rendered AI-Visuals video with the supplied styling (captions, watermark, music, transitions) and queues an export. Returns HTTP 202. The video must already be rendered and not currently busy. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` ### Request body Optional. - `transition` (string) — Transition slug applied between scenes (see faceless options). Allowed values: `slide-left`, `slide-right`, `slide-up`, `slide-down`, `scale-in`, `scale-out`, `zoom-in`, `zoom-out`, `rotate-left`, `rotate-right`, `fade`, `none`, `mixed`, `pop`, `dreamy`, `swing`, `spin-right`, `spin-left`, `swoosh-left`, `swoosh-right`, `glide-left`, `glide-right`, `drop`, `tumble-left`, `tumble-right`, `float`, `rise-left`, `rise-right`, `bounce`, `flash`, `crossfade`, `blur-dissolve`, `zoom-dissolve`. - `overlay` (string) — Overlay style slug applied over the video (see faceless options). Allowed values: `none`, `vhs`, `rain`, `glitch`, `dust`, `sparkling-gold`, `spark-effect`, `abstract-particles`. - `sfx` (string|null) — Sound-effect slug applied to the video (see faceless options). Allowed values: `none`, `whoosh`. - `volume` (string) — Background-music volume level: "low", "medium", or "high". Allowed values: `low`, `medium`, `high`. - `music_id` (integer|null) — Identifier of the background music track (media id). - `transcriptions` (array of array of string) — Per-scene transcription overrides keyed by scene. - `captions` (object) — Caption styling options (font, color, position, effect). - `font_family` (string|null) — Caption font family slug (see faceless options). - `font_url` (string|null) — URL of a custom font file to use for captions. - `font_color` (string|null) — Caption font color as a hex value (e.g. "#FFFFFF"). - `position` (string) — On-screen position slug (e.g. "bottom", "center", "top"). Allowed values: `top`, `bottom`, `center`. - `effect` (string) — Caption effect slug applied to on-screen text (see faceless options). Allowed values: `karaoke`, `highlight`, `fade`, `bounce`, `slide`, `enlarge`. - `watermark` (object) — Watermark image and placement options. - `id` (integer|null) — Unique numeric identifier of the resource. - `position` (string|null) — On-screen position slug (e.g. "bottom", "center", "top"). Allowed values: `top-left`, `top-center`, `top-right`, `middle-left`, `middle-center`, `middle-right`, `bottom-left`, `bottom-center`, `bottom-right`, `none`. - `opacity` (integer|null) — Watermark opacity as a percentage (0–100). Example request: ```json { "transition": "fade", "volume": "medium", "captions": { "font_family": "inter", "font_color": "#FFFFFF", "position": "bottom", "effect": "highlight" }, "watermark": { "id": 42, "position": "bottom-right", "opacity": 80 } } ``` ### Response `202` ```json { "message": "Success.", "status": 202, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "ai-visuals", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "Three habits that quietly improve your focus.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": null, "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z", "video": { "id": 10, "user_id": 1, "idea_id": null, "scheduler_id": null, "title": "Focus habits", "type": "faceless", "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" } }, "meta": { "credits": { "cost": 50, "remaining": 830 } } } ``` ### 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 /ai-visuals/{faceless}/assets **List AI-Visuals assets** (operationId: `listAiVisualsAssets`) Lists the media assets (images, clips) that make up the AI-Visuals video, ordered by scene. Assets are produced during rendering — expect an empty list until the video has been rendered at least once. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` - `index` (query, string) — Zero-based scene position; returns only the asset at that position. Example: `0` ### Response `200` ```json { "message": "Success.", "status": 200, "data": [ { "id": 1, "user_id": 1, "type": "faceless_background", "status": "success", "order": 0, "media": [ { "id": 5, "name": "scene-1", "file_name": "scene-1.png", "mime_type": "image/png", "extension": "png", "download_url": "https://cdn.syllaby.dev/assets/scene-1.png" } ], "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 /ai-visuals/{faceless}/assets/upload **Upload an AI-Visuals asset** (operationId: `uploadAiVisualsAsset`) Attaches a media asset to the AI-Visuals video. Provide EITHER a multipart `file` upload OR a remote `url` to transload — exactly one. Accepts image and video files, subject to your storage quota. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` ### Request body Optional. - `file` (string) — The media file to upload (multipart/form-data). Provide this OR `url`, not both. - `url` (string) — Publicly reachable URL of a media file to transload. Provide this OR `file`, not both. Example request: ```json { "url": "https://cdn.example.com/media/clip.mp4" } ``` ### Response `201` ```json { "message": "Success.", "status": 201, "data": { "id": 1, "user_id": 1, "type": "faceless_background", "status": "success", "order": 0, "media": [ { "id": 5, "name": "scene-1", "file_name": "scene-1.png", "mime_type": "image/png", "extension": "png", "download_url": "https://cdn.syllaby.dev/assets/scene-1.png" } ], "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. - `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 /ai-visuals/{faceless}/assets/{asset}/regenerate **Regenerate an AI-Visuals asset** (operationId: `regenerateAiVisualsAsset`) 💳 Charges credits beyond the engine's free-generation allowance. The response `meta.credits` shows the cost and your remaining balance. Regenerates a single AI-Visuals scene image. The engine and image-generation settings come from the stored video; you may optionally override the prompt. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` - `asset` (path, integer, required) — The asset ID Example: `1` ### Request body Optional. - `prompt` (string|null) — Optional replacement prompt for the regenerated image (max 800 characters). Omit to reuse the scene's existing prompt. Example request: ```json { "prompt": "A neon city skyline at dusk, cinematic lighting." } ``` ### Response `202` ```json { "message": "Success.", "status": 202, "data": { "id": 1, "user_id": 1, "type": "faceless_background", "status": "success", "order": 0, "media": [ { "id": 5, "name": "scene-1", "file_name": "scene-1.png", "mime_type": "image/png", "extension": "png", "download_url": "https://cdn.syllaby.dev/assets/scene-1.png" } ], "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z" }, "meta": { "credits": { "cost": 1, "remaining": 874 } } } ``` ### 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). [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) # URL-to-Video Turn a web page into a video: scrape images and script, then render. ## POST /url-to-video **Create a URL-to-Video video** (operationId: `createUrlToVideo`) Creates a new URL-to-Video video in draft state (its `type` is fixed at `url-based` and cannot be changed later). Returns the created video with its render state embedded under `data.video`. Keep the returned `id` — it identifies the video in every follow-up call, including polling `GET /url-to-video/{id}`. **Requires an active subscription** — the public API has no free tier. ### Request body Required. - `title` (string, required) — Display name of the video (max 255 characters). - `idea_id` (integer) — Optional identifier of a Syllaby content idea to link the video to. The v2 API exposes no ideas endpoints, so omit it for API-only flows. - `starts_at` (string) — Optional ISO-8601 timestamp that schedules the video on your Syllaby content calendar. Does not delay the render. - `ends_at` (string) — Optional ISO-8601 end timestamp for the scheduled calendar slot. Must be the same as or after `starts_at`. Example request: ```json { "title": "My URL-to-Video video" } ``` ### Response `201` ```json { "message": "Success.", "status": 201, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "url-based", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "Three habits that quietly improve your focus.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": null, "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z", "video": { "id": 10, "user_id": 1, "idea_id": null, "scheduler_id": null, "title": "Focus habits", "type": "faceless", "url": null, "status": "draft", "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" } } } ``` ### 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). ## POST /url-to-video/{faceless}/scrape-images **Scrape images from a URL** (operationId: `scrapeUrlToVideoImages`) **Free — no credits charged.** Extracts images from the supplied web page URL and attaches them to the URL-to-Video as ordered assets. You must call this at least once before rendering a URL-to-Video. Social-media URLs are rejected with `422`. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` ### Request body Required. - `url` (string, required) — Publicly reachable web page URL to extract product/page images from. Social-media URLs (YouTube, TikTok, Twitter/X, Instagram, Facebook, etc.) are rejected with `422`. Example request: ```json { "url": "https://www.amazon.com/dp/B0CXYZ1234" } ``` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "url-based", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "Three habits that quietly improve your focus.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": null, "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z", "assets": [ { "id": 1, "user_id": 1, "type": "faceless_background", "status": "success", "order": 0, "media": [ { "id": 5, "name": "scene-1", "file_name": "scene-1.png", "mime_type": "image/png", "extension": "png", "download_url": "https://cdn.syllaby.dev/assets/scene-1.png" } ], "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z" } ], "video": { "id": 10, "user_id": 1, "idea_id": null, "scheduler_id": null, "title": "Focus habits", "type": "faceless", "url": null, "status": "draft", "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" } } } ``` ### 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 /url-to-video/{faceless}/scrape-script **Scrape a script from a URL** (operationId: `scrapeUrlToVideoScript`) 💳 Charges credits. The response `meta.credits` shows the cost and your remaining balance. Generates a narration script directly from the content of the supplied web page URL and stores it on the video. Social-media URLs are rejected with `422`. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` ### Request body Required. - `url` (string, required) — Publicly reachable web page URL to extract script content from. Social-media URLs are rejected with `422`. - `duration` (integer, required) — Target length of the generated script in seconds. Must be one of 30, 60, 180, 300, 600, 900, or 1200. Allowed values: `30`, `60`, `180`, `300`, `600`, `900`, `1200`. - `style` (string, required) — Narrative style for the generated script (free-form text, max 255 chars). Examples: educational, storytelling, listicle, conversational, motivational. - `language` (string, required) — Language for the generated script (e.g. "english"). - `tone` (string, required) — Tone of voice for the generated script. Examples: professional, friendly, casual, authoritative. Example request: ```json { "url": "https://www.amazon.com/dp/B0CXYZ1234", "duration": 60, "style": "educational", "tone": "friendly", "language": "english" } ``` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "url-based", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "This product helps you focus throughout the day.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": null, "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z", "video": { "id": 10, "user_id": 1, "idea_id": null, "scheduler_id": null, "title": "Focus habits", "type": "faceless", "url": null, "status": "draft", "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" } }, "meta": { "credits": { "cost": 5, "remaining": 875 } } } ``` ### 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). ## PUT /url-to-video/{faceless}/scripts **Generate a URL-to-Video script** (operationId: `generateUrlToVideoScript`) 💳 Charges credits. The response `meta.credits` shows the cost and your remaining balance. Generates a narration script for the URL-to-Video video from a topic, tone, style, language, and target duration, and stores it on the video. This is a `PUT` — each call regenerates and overwrites the previous script. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` ### Request body Required. - `style` (string, required) — Required — narrative style for the generated script (free-form text, max 255 chars). Examples: educational, storytelling, listicle, conversational, motivational. - `tone` (string, required) — Tone of voice for generated content (e.g. "professional"). - `language` (string, required) — Language of the content (e.g. "english"). - `topic` (string, required) — Topic or subject of the content. - `duration` (integer, required) — Target length of the generated script in seconds. Must be one of 30, 60, 180, 300, 600, 900, or 1200. Allowed values: `30`, `60`, `180`, `300`, `600`, `900`, `1200`. Example request: ```json { "topic": "Morning routines for better focus", "tone": "professional", "style": "educational", "language": "english", "duration": 60 } ``` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "url-based", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "Three habits that quietly improve your focus.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": null, "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z", "video": { "id": 10, "user_id": 1, "idea_id": null, "scheduler_id": null, "title": "Focus habits", "type": "faceless", "url": null, "status": "draft", "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" } }, "meta": { "credits": { "cost": 5, "remaining": 875 } } } ``` ### 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 /url-to-video/{id} **Get a URL-to-Video video** (operationId: `getUrlToVideoVideo`) Returns a single URL-to-Video video you own. The render state is embedded under `data.video`: `url` (null until the render completes), `status`, and `failure`. Responds with `404` when the id does not exist or belongs to another account. Pass the faceless `id` (not `video_id`). ### Parameters - `id` (path, string, required) — Identifier of the faceless video. Example: `1` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "url-based", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "Three habits that quietly improve your focus.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": null, "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z", "video": { "id": 10, "user_id": 1, "idea_id": null, "scheduler_id": null, "title": "Focus habits", "type": "faceless", "url": "https://cdn.syllaby.dev/videos/10/final.mp4", "status": "completed", "retries": 0, "hash": "abc123", "synced_at": "2026-01-01T12:00:00.000000Z", "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" } } } ``` ### 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 /url-to-video/{id} **Update a URL-to-Video video** (operationId: `updateUrlToVideoVideo`) Updates the URL-to-Video video's configuration (script, voice, genre, captions, transitions, and more). Only the fields you send are changed. The video must not be busy (rendering or syncing). ### Parameters - `id` (path, string, required) — Identifier of the faceless video. Example: `1` ### Request body Optional. - `voice_id` (integer) — Optional — identifier of the narration voice (see faceless options). Omit voice entirely to render without narration. - `background_id` (integer) — Optional — identifier of the background asset (see faceless options). - `music_id` (integer|null) — Optional — identifier of the background music track (media id), or null to remove music. - `genre_id` (integer) — Optional — identifier of the genre/style (see faceless options). Required at render time for the `ai-visuals` and `ai-clips` types. - `image_engine_id` (integer) — Optional — identifier of the text-to-image engine (see faceless options). Relevant only for the `ai-visuals` and `ai-clips` types. - `clip_engine_id` (integer) — Optional — identifier of the text-to-video (clip) engine (see faceless options). Relevant only for clip-based types. - `transition` (string|null) — Optional — transition slug applied between scenes (see faceless options). Allowed values: `slide-left`, `slide-right`, `slide-up`, `slide-down`, `scale-in`, `scale-out`, `zoom-in`, `zoom-out`, `rotate-left`, `rotate-right`, `fade`, `none`, `mixed`, `pop`, `dreamy`, `swing`, `spin-right`, `spin-left`, `swoosh-left`, `swoosh-right`, `glide-left`, `glide-right`, `drop`, `tumble-left`, `tumble-right`, `float`, `rise-left`, `rise-right`, `bounce`, `flash`, `crossfade`, `blur-dissolve`, `zoom-dissolve`. - `animation` (string|null) — Optional — per-image motion effect slug (e.g. "pan-in"). See faceless options. Allowed values: `pan-in`, `pan-out`, `pan-left`, `pan-right`, `pan-up`, `pan-down`, `rotate-left-in`, `rotate-right-in`, `float`, `none`, `mixed`. - `sfx` (string|null) — Optional — sound-effect slug applied to the video (see faceless options). Allowed values: `none`, `whoosh`. - `volume` (string|null) — Optional — background-music volume level: "low", "medium", or "high". Allowed values: `low`, `medium`, `high`. - `script` (string) — Optional — supply a full custom script to use verbatim instead of generating one. Provide this via PATCH /faceless/{faceless}; the script-generation step is then unnecessary. - `duration` (integer) — Optional — target video length in seconds. Any positive integer; not restricted to the script-generation presets (those fixed values apply only to PUT /faceless/{faceless}/scripts). - `aspect_ratio` (string) — Optional — output aspect ratio: "16:9", "9:16", or "1:1". Allowed values: `16:9`, `9:16`, `1:1`. - `captions` (object) — Optional — caption styling. Only `font_family`, `font_color`, `font_url`, and `position` are honored on update; any other caption keys are ignored. - `font_family` (string) — Caption font family slug (see faceless options). - `font_color` (string) — Caption font color as a hex value (e.g. "#FFFFFF"). - `font_url` (string|null) — URL of a custom font file to use for captions. - `position` (string) — On-screen position slug (e.g. "bottom", "center", "top"). Allowed values: `top`, `bottom`, `center`. Example request: ```json { "voice_id": 12, "genre_id": 3, "script": "Three habits that quietly improve your focus.", "transition": "fade", "captions": { "font_family": "inter", "font_color": "#FFFFFF", "position": "bottom" } } ``` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "url-based", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "Three habits that quietly improve your focus.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": 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). ## GET /url-to-video/{faceless}/estimate **Estimate URL-to-Video render credits** (operationId: `estimateUrlToVideoVideo`) Estimates the credits required to render the URL-to-Video video and compares them against your available balance. The video must already have a script and a voice configured. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "required": 30, "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 /url-to-video/{faceless}/render **Render a URL-to-Video video** (operationId: `renderUrlToVideoVideo`) 💳 Charges credits. The response `meta.credits` shows the cost and your remaining balance. Starts rendering the URL-to-Video video asynchronously and returns HTTP 202. Track progress by polling `GET /url-to-video/{id}` and reading `data.video.status` (`rendering` → `completed`/`failed`); the playable file appears at `data.video.url` on completion. Common preconditions checked before any charge: a script and voice must be set, credits must be sufficient (`402`), and the video must not be busy. **Fields omitted from the render body are filled from the video's saved configuration** — a value set at creation, via `PATCH /url-to-video/{id}`, or on a previous render is reused automatically, and an explicit body value always wins. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` ### Request body Optional. Render configuration for the faceless video. Every field is optional: `script`, `duration`, and `aspect_ratio` are auto-filled from the saved draft when omitted (configure it first via `PUT /faceless/{faceless}/scripts` and `PATCH /faceless/{faceless}`), so a fully-configured video can be rendered with an empty body. A `422` is only returned when a required value is missing from both the body and the draft. - `script` (string) — Narration script text for the video. Optional — falls back to the draft's stored script when omitted. - `duration` (integer) — Target video length in seconds (a positive integer). Optional — falls back to the draft's estimated duration when omitted. Not restricted to the script-generation presets (those fixed values apply only to PUT /faceless/{faceless}/scripts). - `aspect_ratio` (string) — Output aspect ratio. Optional — falls back to the draft's stored aspect ratio when omitted. Allowed values are listed under faceless options (aspect_ratios). - `title` (string) — Optional title for the video. - `voice_id` (integer) — Identifier of the narration voice (see faceless options). - `background_id` (integer) — Identifier of the background asset (see faceless options). - `type` (string) — Faceless video type — controls how the visuals are sourced (stock b-roll, a source URL, AI-generated images, or AI-generated clips). Allowed values: `b-roll`, `url-based`, `ai-visuals`, `ai-clips`. - `genre_id` (integer) — Identifier of the genre/style that drives the image-prompt look. REQUIRED for image-based renders (`type` of `ai-visuals` or `ai-clips`); optional for `b-roll` / `url-based` renders that don't use a genre. Allowed values are listed under faceless options (genres). - `character_id` (integer) — Identifier of the consistent character (see faceless options). - `image_engine_id` (integer) — Identifier of the text-to-image engine (see faceless options). - `clip_engine_id` (integer) — Identifier of the text-to-video (clip) engine (see faceless options). - `transition` (string) — Transition slug applied between scenes. Allowed values are listed under faceless options (transitions). - `animation` (string) — Per-image motion effect slug. Allowed values are listed under faceless options (animations). - `overlay` (string) — Overlay style slug applied over the video. Allowed values are listed under faceless options (overlays). Render-body-only: set it here (or on export) — `PATCH /faceless/{faceless}` silently ignores it. - `sfx` (string) — Sound-effect slug applied to the video. Allowed values are listed under faceless options (sfx). - `ai_labels` (boolean) — Whether AI-content disclosure labels are applied on publish. - `custom_description` (string) — Custom caption/description applied to published posts. - `destination_id` (integer) — Identifier of the destination folder/resource to file the video under. - `captions` (object) — Caption styling. Only `font_family`, `font_color`, `font_url`, `position`, and `effect` are honored; any other caption keys are ignored. - `font_family` (string) — Caption font family slug (see faceless options). - `font_color` (string) — Caption font color as a hex value (e.g. "#FFFFFF"). - `font_url` (string) — URL of a custom font file to use for captions. - `position` (string) — On-screen caption position slug. Allowed values are listed under faceless options (caption_positions). - `effect` (string) — Caption effect slug applied to on-screen text. Allowed values are listed under faceless options (caption_effects). Render-body-only: set it here (or on export) — `PATCH /faceless/{faceless}` silently ignores it. - `watermark` (object) — Watermark image and placement. Provide exactly one source — `id`, `url`, or `file` (mutually exclusive). - `id` (integer) — Identifier of a watermark asset you own. Mutually exclusive with `url` and `file`. - `url` (string) — Remote URL of the watermark image. Mutually exclusive with `id` and `file`. - `file` (string) — Uploaded watermark image file (jpg, jpeg, png, webp; max 5 MB). Mutually exclusive with `id` and `url`. - `position` (string) — Watermark placement slug. Allowed values are listed under faceless options (watermark_positions). - `opacity` (integer) — Watermark opacity as a percentage (0–100). - `music_id` (integer) — Identifier of a background music track (media id). Mutually exclusive with `music.url` and `music.file`. - `music` (object) — Background music source. Provide exactly one of `url` or `file`, and only when `music_id` is omitted (all three are mutually exclusive). - `url` (string) — Remote URL of a music track. Mutually exclusive with `music_id` and `music.file`. - `file` (string) — Uploaded music file (mp3, wav, aac, m4a, ogg; max 20 MB). Mutually exclusive with `music_id` and `music.url`. - `volume` (string) — Background-music volume level. Required when any music source is set. Allowed values: `low`, `medium`, `high`. - `publications` (array of object) — Social posts to schedule for the rendered video. - `channel_id` (integer) — Identifier of the connected social channel to publish to. - `scheduled_at` (string) — Future timestamp to publish the post, or null to publish immediately. - `assets` (array of object) — Explicit ordered media assets to compose the video from. Orders must start at 0 and be consecutive with no gaps. - `id` (integer, required) — Identifier of the media asset. - `order` (integer, required) — Zero-based position of the asset in the sequence. Example request: ```json { "script": "Three small habits that quietly improve your focus every day.", "duration": 60, "aspect_ratio": "9:16", "type": "ai-visuals", "title": "Focus habits", "voice_id": 12, "genre_id": 3, "image_engine_id": 1, "clip_engine_id": 2, "transition": "fade", "animation": "pan-in", "overlay": "none", "sfx": "whoosh", "volume": "medium", "music_id": 8, "captions": { "font_family": "inter", "font_color": "#FFFFFF", "position": "bottom", "effect": "highlight" }, "watermark": { "id": 42, "position": "bottom-right", "opacity": 80 }, "publications": [ { "channel_id": 5, "scheduled_at": "2025-02-01T18:30:00Z" } ], "assets": [ { "id": 101, "order": 0 }, { "id": 102, "order": 1 } ] } ``` ### Response `202` ```json { "message": "Success.", "status": 202, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "url-based", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "Three habits that quietly improve your focus.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": null, "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z" }, "meta": { "credits": { "cost": 120, "remaining": 880 } } } ``` ### 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 /url-to-video/{faceless}/retry **Retry a URL-to-Video render** (operationId: `retryUrlToVideoVideo`) 💳 Charges credits. The response `meta.credits` shows the cost and your remaining balance. Re-attempts rendering for a URL-to-Video video whose embedded video previously failed (`data.video.status` is `failed`). Returns HTTP 202; track it the same way as render. Only failed renders can be retried. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` ### Response `202` ```json { "message": "Success.", "status": 202, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "url-based", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "Three habits that quietly improve your focus.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": null, "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z", "video": { "id": 10, "user_id": 1, "idea_id": null, "scheduler_id": null, "title": "Focus habits", "type": "faceless", "url": null, "status": "rendering", "retries": 1, "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" } }, "meta": { "credits": { "cost": 120, "remaining": 880 } } } ``` ### 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. - `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 /url-to-video/{faceless}/export **Export a URL-to-Video video** (operationId: `exportUrlToVideoVideo`) 💳 Charges credits. The response `meta.credits` shows the cost and your remaining balance. Rebuilds the rendered URL-to-Video video with the supplied styling (captions, watermark, music, transitions) and queues an export. Returns HTTP 202. The video must already be rendered and not currently busy. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` ### Request body Optional. - `transition` (string) — Transition slug applied between scenes (see faceless options). Allowed values: `slide-left`, `slide-right`, `slide-up`, `slide-down`, `scale-in`, `scale-out`, `zoom-in`, `zoom-out`, `rotate-left`, `rotate-right`, `fade`, `none`, `mixed`, `pop`, `dreamy`, `swing`, `spin-right`, `spin-left`, `swoosh-left`, `swoosh-right`, `glide-left`, `glide-right`, `drop`, `tumble-left`, `tumble-right`, `float`, `rise-left`, `rise-right`, `bounce`, `flash`, `crossfade`, `blur-dissolve`, `zoom-dissolve`. - `overlay` (string) — Overlay style slug applied over the video (see faceless options). Allowed values: `none`, `vhs`, `rain`, `glitch`, `dust`, `sparkling-gold`, `spark-effect`, `abstract-particles`. - `sfx` (string|null) — Sound-effect slug applied to the video (see faceless options). Allowed values: `none`, `whoosh`. - `volume` (string) — Background-music volume level: "low", "medium", or "high". Allowed values: `low`, `medium`, `high`. - `music_id` (integer|null) — Identifier of the background music track (media id). - `transcriptions` (array of array of string) — Per-scene transcription overrides keyed by scene. - `captions` (object) — Caption styling options (font, color, position, effect). - `font_family` (string|null) — Caption font family slug (see faceless options). - `font_url` (string|null) — URL of a custom font file to use for captions. - `font_color` (string|null) — Caption font color as a hex value (e.g. "#FFFFFF"). - `position` (string) — On-screen position slug (e.g. "bottom", "center", "top"). Allowed values: `top`, `bottom`, `center`. - `effect` (string) — Caption effect slug applied to on-screen text (see faceless options). Allowed values: `karaoke`, `highlight`, `fade`, `bounce`, `slide`, `enlarge`. - `watermark` (object) — Watermark image and placement options. - `id` (integer|null) — Unique numeric identifier of the resource. - `position` (string|null) — On-screen position slug (e.g. "bottom", "center", "top"). Allowed values: `top-left`, `top-center`, `top-right`, `middle-left`, `middle-center`, `middle-right`, `bottom-left`, `bottom-center`, `bottom-right`, `none`. - `opacity` (integer|null) — Watermark opacity as a percentage (0–100). Example request: ```json { "transition": "fade", "volume": "medium", "captions": { "font_family": "inter", "font_color": "#FFFFFF", "position": "bottom", "effect": "highlight" }, "watermark": { "id": 42, "position": "bottom-right", "opacity": 80 } } ``` ### Response `202` ```json { "message": "Success.", "status": 202, "data": { "id": 1, "user_id": 1, "video_id": 10, "voice_id": 12, "music_id": null, "background_id": null, "estimated_duration": 60, "type": "url-based", "genre": { "id": 3, "name": "Cinematic", "slug": "cinematic", "active": true }, "script": "Three habits that quietly improve your focus.", "hash": "abc123", "options": { "aspect_ratio": "9:16" }, "is_transcribed": false, "watermark_id": null, "created_at": "2026-01-01T12:00:00.000000Z", "updated_at": "2026-01-01T12:00:00.000000Z", "video": { "id": 10, "user_id": 1, "idea_id": null, "scheduler_id": null, "title": "Focus habits", "type": "faceless", "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" } }, "meta": { "credits": { "cost": 50, "remaining": 830 } } } ``` ### 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 /url-to-video/{faceless}/assets **List URL-to-Video assets** (operationId: `listUrlToVideoAssets`) Lists the media assets (images, clips) that make up the URL-to-Video video, ordered by scene. Assets are produced during rendering — expect an empty list until the video has been rendered at least once. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` - `index` (query, string) — Zero-based scene position; returns only the asset at that position. Example: `0` ### Response `200` ```json { "message": "Success.", "status": 200, "data": [ { "id": 1, "user_id": 1, "type": "faceless_background", "status": "success", "order": 0, "media": [ { "id": 5, "name": "scene-1", "file_name": "scene-1.png", "mime_type": "image/png", "extension": "png", "download_url": "https://cdn.syllaby.dev/assets/scene-1.png" } ], "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 /url-to-video/{faceless}/assets/upload **Upload a URL-to-Video asset** (operationId: `uploadUrlToVideoAsset`) Attaches a media asset to the URL-to-Video. Provide EITHER a multipart `file` upload OR a remote `url` to transload — exactly one. Accepts image and video files, subject to your storage quota. ### Parameters - `faceless` (path, integer, required) — The faceless ID Example: `1` ### Request body Optional. - `file` (string) — The media file to upload (multipart/form-data). Provide this OR `url`, not both. - `url` (string) — Publicly reachable URL of a media file to transload. Provide this OR `file`, not both. Example request: ```json { "url": "https://cdn.example.com/media/clip.mp4" } ``` ### Response `201` ```json { "message": "Success.", "status": 201, "data": { "id": 1, "user_id": 1, "type": "faceless_background", "status": "success", "order": 0, "media": [ { "id": 5, "name": "scene-1", "file_name": "scene-1.png", "mime_type": "image/png", "extension": "png", "download_url": "https://cdn.syllaby.dev/assets/scene-1.png" } ], "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. - `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). [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": [ "" ] } ``` ### 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). [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) # Voice Clones Clone a narration voice from audio samples, manage voice-clone slots, and list, update, or delete your clones. ## GET /voice-clones **List voice clones** (operationId: `listVoiceClones`) Lists the voice clones you own, newest first. ### Response `200` ```json { "message": "Success.", "status": 200, "data": [ { "id": 1, "name": "My Narrator", "gender": "female", "language": "en", "description": "Warm, conversational narrator voice.", "status": "completed", "slot_type": "default", "preview_url": "https://cdn.syllaby.dev/voices/clone-1/preview.mp3", "created_at": "2026-01-01T12:00:00.000000Z" }, { "id": 2, "name": "Podcast Host", "gender": "female", "language": "en", "description": "Warm, conversational narrator voice.", "status": "pending", "slot_type": "addon", "preview_url": null, "created_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. - `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 /voice-clones **Create a voice clone** (operationId: `createVoiceClone`) Clones a voice from 1–3 audio samples. Send as `multipart/form-data`. Returns `201` with the clone in a **`pending`** state — cloning runs asynchronously. Poll `GET /voice-clones/{id}` and read `data.status` (`pending` → `reviewing` → `completed`, or `failed`); the sample audio preview appears at `data.preview_url` once `completed`. **Slots.** Each plan includes a fixed number of voice-clone slots. When your included slots are full you can buy an extra one by sending `purchase_addon: true` — this **💳 charges credits** (the price is `cost` on `GET /voice-clones/slots`) and the response `meta.credits` reports the charge and your remaining balance. Without a free slot and without `purchase_addon`, the request returns `403`; with `purchase_addon` but too few credits, it returns `402`. ### Request body Required. - `terms` (string, required) — Required — you must accept the voice-cloning terms. Send a truthy value (`true`, `1`, `"yes"`, `"on"`); consent is a legal boundary and the request is rejected without it. - `name` (string, required) — Required — display name for the voice clone (max 20 characters). - `description` (string|null) — Optional — free-text description of the voice (max 200 characters). - `gender` (string, required) — Required — voice gender: `male`, `female`, or `neutral`. Allowed values: `male`, `female`, `neutral`. - `language` (string|null) — Optional — ISO language code for the voice (max 3 characters, e.g. `en`). - `purchase_addon` (boolean|null) — Optional — set `true` to buy an extra voice-clone slot when your included slots are full. Sent over `multipart/form-data`, so the accepted truthy spellings are `true`, `1`, `on` and `yes` (case-insensitive); `false`, `0`, `off`, `no` and an empty value all decline. Any other value returns `422` rather than silently declining the purchase. Charges credits (see `GET /voice-clones/slots` for the price); a `false`/omitted value with no free slot returns `403`. If purchasing and your balance is too low, the request returns `402`. - `samples` (array of string, required) — Required — 1 to 3 audio sample files of the voice to clone. Each file may be mp3, wav, m4a, or webm, up to 10 MB. Send as `multipart/form-data` (`samples[]`). Example request: ```json { "terms": true, "name": "My Narrator", "gender": "female", "language": "en", "description": "Warm, conversational narrator voice.", "samples": [ "" ], "purchase_addon": true } ``` ### Response `201` ```json { "message": "Success.", "status": 201, "data": { "id": 1, "name": "My Narrator", "gender": "female", "language": "en", "description": "Warm, conversational narrator voice.", "status": "pending", "slot_type": "addon", "preview_url": null, "created_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). - `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). ## GET /voice-clones/slots **Get voice-clone slot availability** (operationId: `getVoiceCloneSlots`) Reports your voice-clone slot capacity and whether you can buy another one. Use this before `POST /voice-clones` to decide whether to send `purchase_addon`. - `default_max` — slots included with your plan. - `addon_cap` — the maximum number of extra slots you may purchase. - `current_addons` — extra slots you have already bought. - `lifetime_purchases` — total add-on slots ever purchased. - `filled` / `available` — slots in use and slots free right now. - `can_buy` — whether buying another slot is currently possible (add-on cap not reached, enough credits, not on a trial). - `cost` — credits charged to buy one extra slot. ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "default_max": 1, "addon_cap": 5, "current_addons": 2, "lifetime_purchases": 2, "filled": 2, "available": 1, "can_buy": true, "cost": 15 } } ``` ### 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 /voice-clones/{id} **Get a voice clone** (operationId: `getVoiceClone`) Returns a single voice clone you own — poll this to track cloning status (`pending` → `reviewing` → `completed`/`failed`). Responds with `404` when the id does not exist or belongs to another account. ### Parameters - `id` (path, integer, required) — Identifier of the faceless video. Example: `1` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "id": 1, "name": "My Narrator", "gender": "female", "language": "en", "description": "Warm, conversational narrator voice.", "status": "completed", "slot_type": "default", "preview_url": "https://cdn.syllaby.dev/voices/clone-1/preview.mp3", "created_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 /voice-clones/{id} **Update a voice clone** (operationId: `updateVoiceClone`) Updates a voice clone's `name`, `gender`, and `description`. Only the fields you send are changed. Owner-scoped — a clone belonging to another account responds with `404`. ### Parameters - `id` (path, integer, required) — Identifier of the faceless video. Example: `1` ### Request body Required. - `name` (string, required) — Required — display name for the voice clone (max 125 characters). - `description` (string|null) — Optional — free-text description of the voice (max 255 characters). - `gender` (string, required) — Required — voice gender: `male`, `female`, or `neutral`. Allowed values: `male`, `female`, `neutral`. Example request: ```json { "name": "My Narrator", "gender": "female", "description": "Warm, conversational narrator voice." } ``` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "id": 1, "name": "My Narrator", "gender": "female", "language": "en", "description": "Warm, conversational narrator voice.", "status": "completed", "slot_type": "default", "preview_url": "https://cdn.syllaby.dev/voices/clone-1/preview.mp3", "created_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). ## DELETE /voice-clones/{id} **Delete a voice clone** (operationId: `deleteVoiceClone`) Deletes a voice clone you own and removes the underlying provider voice. Returns `204 No Content`. Owner-scoped — a clone belonging to another account responds with `404`. ### Parameters - `id` (path, integer, required) — Identifier of the faceless video. Example: `1` ### Response `204` Empty body (`204 No Content`). ### 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). [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) # Ideas Discover and rank content ideas for a keyword on a given network. ## POST /ideas/discover **Discover ideas for a keyword** (operationId: `discoverIdeas`) Discovers content ideas for a keyword on a given network. This call is **synchronous** and can be slow — worst case around **2 minutes** while the upstream keyword tool responds. On success it returns the keyword summary (with `ideas_count`); read the discovered ideas themselves with `GET /ideas`. **💳 Charges 15 credits — and it charges on *every* fruitful call, including a cached re-search of a keyword you have already discovered.** Discovery results are cached globally for three weeks; a repeat discover of the same keyword within that window returns the cached ideas **but still charges the full 15 credits**. A call is charged only when the search actually yields ideas (`ideas_count > 0`); a search that finds nothing is free. The charge and your remaining balance are reported in `meta.credits`. **Do not blindly retry a timed-out discover.** The synchronous worst case (~2 minutes) exceeds many client and proxy timeouts, so your HTTP layer may give up *after* the server has already completed the discovery and charged you. There is **no idempotency key** on this endpoint: a retry that lands on the now-warm three-week cache is treated as a fresh fruitful discovery and **charges another 15 credits for the same logical search**. If a discover times out, reconcile first with the **free** `GET /ideas` (same `keyword` + `network`): if it now returns ideas, the discovery succeeded — do not retry. If your balance is below 15 credits the request is rejected up front with `402` before any search runs. ### Request body Required. - `keyword` (string, required) — Required — the keyword or phrase to discover content ideas for (2–80 characters). - `network` (string, required) — Required — the network the ideas are sourced for: `google`, `google-trends`, `youtube`, `instagram`, `twitter`, `bing`, `pinterest`, or `tiktok`. Allowed values: `google`, `google-trends`, `youtube`, `instagram`, `twitter`, `bing`, `pinterest`, `tiktok`. Example request: ```json { "keyword": "cold plunge benefits", "network": "youtube" } ``` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "name": "cold plunge benefits", "slug": "cold-plunge-benefits", "network": "youtube", "ideas_count": 42, "searched_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). - `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 /ideas **List ideas for a keyword** (operationId: `listIdeas`) Returns a paginated, sortable list of the ideas discovered for a keyword. **Free — no credits charged.** You can only list ideas for a keyword **you have previously discovered** via `POST /ideas/discover`; a `keyword`+`network` pair you never discovered responds with `403`. Sort with `sort` (default `-volume`). ### Parameters - `keyword` (query, string, required) — The keyword to list ideas for — must be one you have previously discovered (2–80 characters). Example: `cold plunge benefits` - `network` (query, string, required) — The network the keyword was discovered on: `google`, `google-trends`, `youtube`, `instagram`, `twitter`, `bing`, `pinterest`, or `tiktok`. Example: `youtube` - `sort` (query, string|null) — Sort field, optionally prefixed with `-` for descending. One of `volume`, `cpc`, `competition`, `trend` (or their `-` variants). Defaults to `-volume` (highest search volume first). Example: `-volume` ### Response `200` ```json { "message": "Success.", "status": 200, "data": [ { "name": "Cold plunge for beginners: a 30-day guide", "volume": 18100, "cpc": 0.42, "competition": 0.35, "competition_label": "LOW", "trend": "up", "trends": [ { "month": "2025-11", "value": 14800 }, { "month": "2025-12", "value": 16200 }, { "month": "2026-01", "value": 18100 } ], "updated_at": "2026-01-01T12:00:00.000000Z" }, { "name": "Are cold plunges worth it? The science, explained", "volume": 9800, "cpc": 0.42, "competition": 0.52, "competition_label": "MEDIUM", "trend": "flat", "trends": [ { "month": "2025-11", "value": 14800 }, { "month": "2025-12", "value": 16200 }, { "month": "2026-01", "value": 18100 } ], "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). - `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). [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) # Characters Build reusable AI characters: browse genres, create from a reference photo, generate previews, and train a consistent character. ## GET /characters/genres **List character genres** (operationId: `listCharacterGenres`) Lists the visual genres a character can be generated in. Use a genre `id` as `genre_id` when generating a preview (`POST /characters/{id}/preview`). Only character-capable genres are returned. ### Response `200` ```json { "message": "Success.", "status": 200, "data": [ { "id": 3, "name": "Realistic", "slug": "realistic", "preview": { "16:9": "https://cdn.syllaby.dev/faceless/genres/realistic/16:9.webp", "9:16": "https://cdn.syllaby.dev/faceless/genres/realistic/9:16.webp", "1:1": "https://cdn.syllaby.dev/faceless/genres/realistic/1:1.webp" } }, { "id": 4, "name": "Anime", "slug": "anime", "preview": { "16:9": "https://cdn.syllaby.dev/faceless/genres/realistic/16:9.webp", "9:16": "https://cdn.syllaby.dev/faceless/genres/realistic/9:16.webp", "1:1": "https://cdn.syllaby.dev/faceless/genres/realistic/1:1.webp" } } ] } ``` ### 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 /characters **List characters** (operationId: `listCharacters`) Lists characters available to you — Syllaby's built-in `system` characters plus the `custom` characters you have created (your own sorted first). Filter with `genre` (a genre slug), `type` (`system` or `custom`), and/or `status`. **By default the list excludes `draft` and `preview-failed` characters**, so a character you just created with `POST /characters` will not appear until it has been previewed. Pass `type=custom` to see everything you own including drafts, or `status=draft` to see only those. An unknown `type` or `status` value returns `422`. A `genre` slug that matches nothing returns `200` with an empty list — a genre is a lookup, not an enum. ### Parameters - `genre` (query, string) — Filter by genre slug (see `GET /characters/genres`). Example: `realistic` - `type` (query, string) — Filter by ownership: `system` (Syllaby's built-in characters) or `custom` (characters you created). Omit to return both. `custom` also widens the status filter: it returns **every** character you own, including `draft` ones that are not yet previewed. The unfiltered list excludes `draft` and `preview-failed`. Any other value returns `422` — an unknown value used to be ignored and returned the full unfiltered list, which looked like a successful filter. Example: `custom` - `status` (query, string) — Return only characters in this state. Overrides the default exclusion of `draft` and `preview-failed`, and applies with or without `type`. Any value outside the list returns `422`. Example: `ready` ### Response `200` ```json { "message": "Success.", "status": 200, "data": [ { "id": 1, "name": "Aria", "status": "ready", "genre": { "id": 3, "name": "Realistic", "slug": "realistic", "preview": { "16:9": "https://cdn.syllaby.dev/faceless/genres/realistic/16:9.webp", "9:16": "https://cdn.syllaby.dev/faceless/genres/realistic/9:16.webp", "1:1": "https://cdn.syllaby.dev/faceless/genres/realistic/1:1.webp" } }, "gender": "female", "age": "25-34", "traits": [ "confident", "friendly" ], "preview_url": "https://cdn.syllaby.dev/characters/1/preview.png", "previews": [ { "id": 501, "url": "https://cdn.syllaby.dev/characters/1/sandbox-501.png" }, { "id": 502, "url": "https://cdn.syllaby.dev/characters/1/sandbox-502.png" }, { "id": 503, "url": "https://cdn.syllaby.dev/characters/1/sandbox-503.png" }, { "id": 504, "url": "https://cdn.syllaby.dev/characters/1/sandbox-504.png" } ], "poses": [ "https://cdn.syllaby.dev/characters/1/pose-1.png", "https://cdn.syllaby.dev/characters/1/pose-2.png" ], "created_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. - `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 /characters **Create a character** (operationId: `createCharacter`) Creates a custom character from a reference photo. Send as `multipart/form-data` with an `image` file (plus optional `name`, `gender`, `age`, `traits`, `genre_id`). Returns `201` with the character in an early state. Next, generate preview candidates with `POST /characters/{id}/preview`, then train the one you like with `POST /characters/{id}/train`. ### Request body Required. - `image` (string, required) — Required — a reference photo of the character to build (image file, max 10 MB). Send as `multipart/form-data`. - `genre_id` (integer|null) — Optional — id of a character-capable genre (see `GET /characters/genres`). - `name` (string|null) — Optional — display name for the character (max 250 characters). Omit it and the name is derived from your account name. - `description` (string|null) — Optional — free-text description of the character (max 500 characters). - `gender` (string|null) — Optional — character gender. Accepted values: `male`, `female`, `neutral`. **Omitting it stores `unknown`**, which is what the character then returns — `unknown` is a response value only and is rejected if you send it. Allowed values: `male`, `female`. - `traits` (array|null) — Optional — list of descriptive traits for the character. - `age` (string|null) — Optional — age or age-range label for the character (e.g. "25-34"). Example request: ```json { "image": "", "name": "Aria", "gender": "female", "age": "25-34", "genre_id": 3 } ``` ### Response `201` ```json { "message": "Success.", "status": 201, "data": { "id": 1, "name": "Aria", "status": "draft", "genre": { "id": 3, "name": "Realistic", "slug": "realistic", "preview": { "16:9": "https://cdn.syllaby.dev/faceless/genres/realistic/16:9.webp", "9:16": "https://cdn.syllaby.dev/faceless/genres/realistic/9:16.webp", "1:1": "https://cdn.syllaby.dev/faceless/genres/realistic/1:1.webp" } }, "gender": "female", "age": "25-34", "traits": [ "confident", "friendly" ], "preview_url": "https://cdn.syllaby.dev/characters/1/preview.png", "previews": [], "poses": [], "created_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. - `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). ## GET /characters/{id} **Get a character** (operationId: `getCharacter`) Returns a single character (a `system` character or one you own). Poll this to track preview and training progress via `data.status`. When previews are ready, `data.previews[]` holds the candidate ids — pass the chosen `previews[].id` as `preview_id` to `POST /characters/{id}/train`. Responds with `404` when the id does not exist or belongs to another account. ### Parameters - `id` (path, integer, required) — Identifier of the faceless video. Example: `1` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "id": 1, "name": "Aria", "status": "preview-ready", "genre": { "id": 3, "name": "Realistic", "slug": "realistic", "preview": { "16:9": "https://cdn.syllaby.dev/faceless/genres/realistic/16:9.webp", "9:16": "https://cdn.syllaby.dev/faceless/genres/realistic/9:16.webp", "1:1": "https://cdn.syllaby.dev/faceless/genres/realistic/1:1.webp" } }, "gender": "female", "age": "25-34", "traits": [ "confident", "friendly" ], "preview_url": "https://cdn.syllaby.dev/characters/1/preview.png", "previews": [ { "id": 501, "url": "https://cdn.syllaby.dev/characters/1/sandbox-501.png" }, { "id": 502, "url": "https://cdn.syllaby.dev/characters/1/sandbox-502.png" }, { "id": 503, "url": "https://cdn.syllaby.dev/characters/1/sandbox-503.png" }, { "id": 504, "url": "https://cdn.syllaby.dev/characters/1/sandbox-504.png" } ], "poses": [], "created_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). ## DELETE /characters/{id} **Delete a character** (operationId: `deleteCharacter`) Deletes a custom character you own. Returns `204 No Content`, and the character is gone — a follow-up `GET` returns `404`, as does a repeat `DELETE`. **System characters cannot be deleted** — a built-in character responds with `403`; a character owned by another account responds with `404`. A character that is mid-preview or mid-training also responds with `403`; wait for it to settle. Deleting a character that a scheduler or an existing video referenced does not delete those — they simply lose the character reference. ### Parameters - `id` (path, integer, required) — Identifier of the faceless video. Example: `1` ### Response `204` Empty body (`204 No Content`). ### 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 /characters/{id}/preview **Generate character previews** (operationId: `previewCharacter`) Generates preview candidates for a character in the chosen genre, using the reference image supplied at creation (you never handle its media id). This call is **synchronous** and can take up to **~90 seconds**; it is throttled to **5 requests per minute**. Returns `201` with the character; the candidate images appear under `data.previews[]`, each with an `id` you can pass as `preview_id` to `POST /characters/{id}/train`. **Candidates accumulate.** Every call appends to `previews[]` — earlier candidates are never pruned, and the number produced per call varies. Read the ids from the response you just received rather than assuming a fixed count or that the array holds only the latest batch. **System characters cannot be previewed** — a built-in character resolves but is rejected with `403`. **Two distinct `422` shapes can come back, and they mean different things:** - **Validation error** — a bad request field (e.g. a `genre_id` that is not a character-capable genre). Standard validation shape: `{ message, errors: { genre_id: [...] } }`. Fix the input and retry. - **Generation failure** — the request was valid but preview generation failed downstream. Error-envelope shape: `{ message, error: { code, status } }` with no `errors` bag. Poll `GET /characters/{id}` to check status, then retry. ### Parameters - `id` (path, integer, required) — Identifier of the faceless video. Example: `1` ### Request body Required. - `genre_id` (integer, required) — Required — id of the character-capable genre to render the preview in. Must be one of the genres returned by `GET /characters/genres`; any other id fails validation with `422`. Example request: ```json { "genre_id": 3 } ``` ### Response `201` ```json { "message": "Success.", "status": 201, "data": { "id": 1, "name": "Aria", "status": "preview-ready", "genre": { "id": 3, "name": "Realistic", "slug": "realistic", "preview": { "16:9": "https://cdn.syllaby.dev/faceless/genres/realistic/16:9.webp", "9:16": "https://cdn.syllaby.dev/faceless/genres/realistic/9:16.webp", "1:1": "https://cdn.syllaby.dev/faceless/genres/realistic/1:1.webp" } }, "gender": "female", "age": "25-34", "traits": [ "confident", "friendly" ], "preview_url": "https://cdn.syllaby.dev/characters/1/preview.png", "previews": [ { "id": 501, "url": "https://cdn.syllaby.dev/characters/1/sandbox-501.png" }, { "id": 502, "url": "https://cdn.syllaby.dev/characters/1/sandbox-502.png" }, { "id": 503, "url": "https://cdn.syllaby.dev/characters/1/sandbox-503.png" }, { "id": 504, "url": "https://cdn.syllaby.dev/characters/1/sandbox-504.png" } ], "poses": [], "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. - `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 /characters/{id}/train **Train a character** (operationId: `trainCharacter`) 💳 **Charges 10 credits.** The response `meta.credits` reports the charge and your remaining balance. Trains a consistent character from a chosen preview candidate and kicks off pose generation. Pass `preview_id` (one of the `previews[].id` values from `POST /characters/{id}/preview`) plus `name`, `age`, and `gender`. Training runs asynchronously — poll `GET /characters/{id}` until `data.status` is `ready`, at which point `data.poses[]` holds the generated pose images. **System characters cannot be trained** — a built-in character responds with `403`. ### Parameters - `id` (path, integer, required) — Identifier of the faceless video. Example: `1` ### Request body Required. - `preview_id` (integer, required) — Required — id of the chosen preview candidate (an entry from the character's `previews[].id`, produced by `POST /characters/{id}/preview`). - `name` (string, required) — Required — display name for the trained character. - `age` (string, required) — Required — age or age-range label for the character (e.g. "25-34"). - `gender` (string, required) — Required — character gender: `male`, `female`, or `neutral`. Allowed values: `male`, `female`. Example request: ```json { "preview_id": 501, "name": "Aria", "age": "25-34", "gender": "female" } ``` ### Response `200` ```json { "message": "Success.", "status": 200, "data": { "id": 1, "name": "Aria", "status": "pose-generating", "genre": { "id": 3, "name": "Realistic", "slug": "realistic", "preview": { "16:9": "https://cdn.syllaby.dev/faceless/genres/realistic/16:9.webp", "9:16": "https://cdn.syllaby.dev/faceless/genres/realistic/9:16.webp", "1:1": "https://cdn.syllaby.dev/faceless/genres/realistic/1:1.webp" } }, "gender": "female", "age": "25-34", "traits": [ "confident", "friendly" ], "preview_url": "https://cdn.syllaby.dev/characters/1/preview.png", "previews": [ { "id": 501, "url": "https://cdn.syllaby.dev/characters/1/sandbox-501.png" }, { "id": 502, "url": "https://cdn.syllaby.dev/characters/1/sandbox-502.png" }, { "id": 503, "url": "https://cdn.syllaby.dev/characters/1/sandbox-503.png" }, { "id": 504, "url": "https://cdn.syllaby.dev/characters/1/sandbox-504.png" } ], "poses": [], "created_at": "2026-01-01T12:00:00.000000Z" }, "meta": { "credits": { "cost": 10, "remaining": 890 } } } ``` ### 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).