<!-- Generated from openapi/v2.json (md/transcripts.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)

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