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

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

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

## Authentication
All endpoints require a **Bearer token**. Send it on every request:

```http
Authorization: Bearer <your-api-token>
```

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 <token>` 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
All faceless-video and preset endpoints require an **active
subscription** — reads included. Without one, every request to that surface
is rejected with `403 SUBSCRIPTION-REQUIRED` (`An active subscription is
required.`) before any ownership, credit, or validation check. The public
API has no free tier. Only `GET /me`, `GET /credits/costs`, and
`GET /credits/history` are reachable without an active 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).

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

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

### `402`

Insufficient credits. The request is authenticated and valid, but the account balance is too low — top up and retry.

### `403`

No active subscription. The v2 public API has no free tier — every faceless and preset endpoint (reads included) requires an active subscription. Unsubscribed, expired, or canceled callers are rejected with this `403` (code `SUBSCRIPTION-REQUIRED`) before any ownership, credit, or validation check. Only `GET /me`, `GET /credits/costs`, and `GET /credits/history` are reachable without one.

```json
{
  "message": "An active subscription is required.",
  "error": {
    "code": "SUBSCRIPTION-REQUIRED",
    "status": 403
  }
}
```

### `404`

Not found

```json
{
  "message": "No query results for the requested resource."
}
```

### `422`

Validation error

```json
{
  "message": "The given data was invalid.",
  "errors": {
    "voice_id": [
      "The selected voice id is invalid."
    ]
  }
}
```

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

### `500`

Server error.
