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

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

# Text to 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).
