# Video Translate

## Translate a remote video

**post** `/v1/video-translate/generate`

Validates a remote video and starts an asynchronous translation request.

Optional `config.source_srt` / `config.target_srts` supply subtitles and
`config.speaker_voices` assigns voices to speakers (see `VideoTranslateConfig`).
When `webhook_url` is supplied, the service sends one JSON `POST` after the
request reaches `completed` or `failed`. The event type is
`video_translate.completed` or `video_translate.failed`; the request body uses
`{id, type, created_at, data}` and `data` mirrors the public request response
without `webhook_url`. Successful events include freshly signed video, audio and
SRT URLs. Receivers should deduplicate on `X-Chamelaion-Delivery`. Transport
failures, HTTP 429 and HTTP 5xx are retried up to three times with the same
delivery ID; redirects and other non-2xx responses are not accepted.
Validation happens in two phases:

1. **Synchronous (HTTP 400, no request is created):** request shape, config types,
   SRT size, UTF-8 encoding and syntax (optional BOM stripped, CRLF/CR normalized
   to LF), cue limit, language consistency, `target_srts` keys must be listed in
   `target_languages`, speaker ID syntax, and voice availability (every
   `speaker_voices` value must be a `voice_id` returned by
   `GET /v1/video-translate/voices`).
1. **After the video is processed, before translation starts:** SRT timing against
   the video duration (+1.2 s tolerance), source/target cue count and speaker
   compatibility, the detected source language against `source_srt.language`, and
   `speaker_voices` keys against the speakers in the source subtitles (or, without
   any SRT, the speakers already stored for the media). Failures mark the request
   `failed` with a descriptive `error_message`; no translation is started.

Limits: JSON body 8 MiB; each SRT 1 MiB; all SRTs 5 MiB; 5000 cues per SRT;
32 `speaker_voices` entries; config keys other than `source_srt`, `target_srts`
and `speaker_voices` 64 KiB in total; normalized config 6 MiB (measured without
HTML escaping). Duplicate JSON keys anywhere in the body and NUL characters in
config are rejected.

### Body Parameters

- `inputs: array of object { type, url }`

  - `type: "video"`

    - `"video"`

  - `url: string`

- `target_languages: array of string`

- `config: optional object { source_srt, speaker_voices, target_srts, tts_model }`

  Request config. `tts_model`, `source_srt`, `target_srts` and `speaker_voices` are
  validated and consumed by the translation; every other key is stored as sent (at most 64 KiB in
  total, no NUL characters) and returned in responses. Duplicate keys at any depth are
  rejected. Numbers are stored exactly, but responses decode them as IEEE-754
  doubles, so integers beyond 2^53 may lose precision when read back.
  Without SRTs and voices, translation behaves exactly as before (transcription and
  speaker detection are automatic).

  - `source_srt: optional object { content, file_name, language }`

    One SRT document. Each cue is an index (1, 2, ...), a timing line
    `HH:MM:SS,mmm --> HH:MM:SS,mmm`, and text formatted `Speaker N: text`.
    UTF-8 only (an optional BOM is stripped; CRLF/CR line endings are normalized to LF).
    At most 1 MiB and 5000 cues per document and 5 MiB across all documents.

    - `content: string`

    - `file_name: optional string`

      Display name without path separators or control characters. Defaults to `source-<language>.srt` (`source-source.srt` when the source language is not yet known) or `target-<language>.srt`.

    - `language: optional string`

      For `source_srt`, must equal `source_language` when that is given, and the detected language otherwise; taken from it when omitted. For `target_srts`, must equal the key when given.

  - `speaker_voices: optional map[string]`

    Voice per speaker. Keys are base-10, non-negative integer speaker IDs as used in
    the SRT cues (`"1"` for `Speaker 1: ...`); names such as `"speaker-1"`, signs,
    whitespace or hexadecimal are rejected, and keys that collide after normalization
    (`"01"` and `"1"`) are rejected. Keys are returned in canonical form. Values are
    external voice IDs (`voice_id` from `GET /v1/video-translate/voices`); unknown or
    deleted voices are rejected. Every key must be a speaker of the source subtitles;
    without any SRT, keys are checked against the speakers already detected for the
    video, and the request fails if none are known yet.

  - `target_srts: optional map[object { content, file_name, language } ]`

    Target-language subtitles keyed by language code (trimmed, case-insensitive; keys
    must be unique after normalization and listed in `target_languages`). Target
    languages without an entry are translated automatically. Each target must have the
    same cue count and speakers as the effective source subtitles (the supplied
    `source_srt`, or the segments stored for the processed video).

    - `content: string`

    - `file_name: optional string`

      Display name without path separators or control characters. Defaults to `source-<language>.srt` (`source-source.srt` when the source language is not yet known) or `target-<language>.srt`.

    - `language: optional string`

      For `source_srt`, must equal `source_language` when that is given, and the detected language otherwise; taken from it when omitted. For `target_srts`, must equal the key when given.

  - `tts_model: optional "eleven_v4" or "eleven_multilingual_v2"`

    Deprecated compatibility field. Prefer top-level `model`. `eleven_v4` equals
    `expressive`; `eleven_multilingual_v2` equals `classic`. If both fields are sent
    they must agree.

    - `"eleven_v4"`

    - `"eleven_multilingual_v2"`

- `disable_background_audio: optional boolean`

  Defaults to false when omitted; set to true to disable background audio.

- `disable_lip_sync: optional boolean`

  Defaults to false when omitted; set to true to disable lip sync.

- `formality: optional boolean`

- `model: optional "expressive" or "classic"`

  Product speech model. `expressive` maps to Eleven v4 and is the default; `classic` maps to Eleven Multilingual v2.

  - `"expressive"`

  - `"classic"`

- `reference_id: optional string`

- `source_language: optional string`

  Optional supported source language code; detected when omitted.

- `webhook_url: optional string`

  Optional HTTPS callback URL for terminal status. It must have a public host,
  contain no credentials or fragment, and redirects are not followed. The POST
  has `Content-Type: application/json`, `X-Chamelaion-Event` and
  `X-Chamelaion-Delivery` headers.

### Returns

- `request_id: string`

- `status: "success"`

  - `"success"`

### Example

```http
curl https://api.chamelaion.com/api/v1/video-translate/generate \
    -H 'Content-Type: application/json' \
    -H "Authorization: Bearer $CHAMELAION_BEARER_TOKEN" \
    -d '{
          "inputs": [
            {
              "type": "video",
              "url": "https://storage.googleapis.com/chamelaion-test-media/video.mp4"
            }
          ],
          "target_languages": [
            "de",
            "fr"
          ],
          "source_language": "en"
        }'
```

#### Response

```json
{
  "status": "success",
  "request_id": "4c98dd00-a982-47d9-a8d6-e1f0b35a8944"
}
```

## Translate an uploaded video

**post** `/v1/video-translate/generate-with-media`

Stages one bounded video upload and starts an asynchronous translation request.
Accepts the same settings as `POST /v1/video-translate/generate` as form fields.
Subtitles may be sent inline in the `config` field or as native file parts
`source_srt` and `target_srt.<language>` (for example `target_srt.de`); sending the
same subtitle both ways is rejected. Parts may arrive in any order; validation that
needs every field runs after the whole body is read but before a request is created,
and the staged video is discarded on every failure. Both transports produce the same
normalized, persisted config.

Limits: video 2 GiB; whole body 2 GiB + 9 MiB; part names 64 bytes; `config` field
6 MiB; other text fields 64 KiB; at most 11 SRT parts (one source plus one per
allowed target language); each SRT part 1 MiB (rejected, never truncated); all SRTs
5 MiB. `target_srt.<language>` must name a supported language; invalid names are
rejected before the part is read.

### Returns

- `request_id: string`

- `status: "success"`

  - `"success"`

### Example

```http
curl https://api.chamelaion.com/api/v1/video-translate/generate-with-media \
    -H 'Content-Type: multipart/form-data' \
    -H "Authorization: Bearer $CHAMELAION_BEARER_TOKEN" \
    -F target_languages=string \
    -F 'video=@/path/to/video' \
    -F config='{"speaker_voices":{"0":"21m00Tcm4TlvDq8ikWAM","1":"AZnzlk1XvdvUeBnXmlld"}}'
```

#### Response

```json
{
  "status": "success",
  "request_id": "4c98dd00-a982-47d9-a8d6-e1f0b35a8944"
}
```

## Domain Types

### Video Translate Generate Response

- `VideoTranslateGenerateResponse object { request_id, status }`

  - `request_id: string`

  - `status: "success"`

    - `"success"`

### Video Translate Generate With Media Response

- `VideoTranslateGenerateWithMediaResponse object { request_id, status }`

  - `request_id: string`

  - `status: "success"`

    - `"success"`

# Requests

## Get a video translation request

**get** `/v1/video-translate/requests/{id}`

Returns an account-owned request by UUID or exact reference ID (requests of other accounts return 404). Completed artifact URLs are freshly signed on every read; SRT content is never returned.

### Path Parameters

- `id: string`

### Returns

- `id: string`

- `config: object { source_srt, speaker_voices, target_srts, tts_model }`

  The request config as returned by the API. SRT content is replaced by metadata, `speaker_voices` keys are canonical speaker IDs, and other keys are returned unchanged.

  - `source_srt: optional object { supplied, file_name, language }`

    Public description of a supplied SRT. The subtitle content is never returned.

    - `supplied: true`

      - `true`

    - `file_name: optional string`

    - `language: optional string`

  - `speaker_voices: optional map[string]`

  - `target_srts: optional map[object { supplied, file_name, language } ]`

    - `supplied: true`

      - `true`

    - `file_name: optional string`

    - `language: optional string`

  - `tts_model: optional "eleven_v4" or "eleven_multilingual_v2"`

    The explicitly selected text-to-speech model. Omitted when the request used the default.

    - `"eleven_v4"`

    - `"eleven_multilingual_v2"`

- `created_at: string`

- `disable_background_audio: boolean`

  Whether background audio was disabled for this request.

- `disable_lip_sync: boolean`

  Whether lip sync was disabled for this request.

- `formality: boolean`

- `model: "expressive" or "classic"`

  Product speech model selected for the request.

  - `"expressive"`

  - `"classic"`

- `status: "queued" or "uploading" or "processing" or 2 more`

  - `"queued"`

  - `"uploading"`

  - `"processing"`

  - `"completed"`

  - `"failed"`

- `target_languages: array of string`

- `error_message: optional string`

  Present only when failed. A safe, bounded description, for example SRT or speaker validation errors found after the video was processed.

- `finished_at: optional string`

- `outputs: optional map[object { audio, srt, video } ]`

  Present only when completed, keyed by requested target language. URLs are signed on every read.

  - `audio: string`

  - `srt: string`

  - `video: string`

- `reference_id: optional string`

- `source_language: optional string`

  Explicit source language, or the detected one once processing starts.

- `started_at: optional string`

- `webhook_url: optional string`

  The configured terminal callback URL, when supplied.

### Example

```http
curl https://api.chamelaion.com/api/v1/video-translate/requests/$ID \
    -H "Authorization: Bearer $CHAMELAION_BEARER_TOKEN"
```

#### Response

```json
{
  "id": "4c98dd00-a982-47d9-a8d6-e1f0b35a8944",
  "reference_id": "customer-job-42",
  "source_language": "en",
  "target_languages": [
    "de",
    "fr"
  ],
  "disable_background_audio": false,
  "disable_lip_sync": false,
  "formality": false,
  "config": {
    "source_srt": {
      "file_name": "interview.srt",
      "language": "en",
      "supplied": true
    },
    "target_srts": {
      "de": {
        "file_name": "target-de.srt",
        "language": "de",
        "supplied": true
      }
    },
    "speaker_voices": {
      "0": "21m00Tcm4TlvDq8ikWAM",
      "1": "AZnzlk1XvdvUeBnXmlld"
    }
  },
  "status": "completed",
  "created_at": "2026-10-07T12:00:00Z",
  "started_at": "2026-10-07T12:00:01Z",
  "finished_at": "2026-10-07T12:04:00Z",
  "outputs": {
    "de": {
      "video": "https://storage.example/signed/de.mp4",
      "audio": "https://storage.example/signed/de.wav",
      "srt": "https://storage.example/signed/de.srt"
    },
    "fr": {
      "video": "https://storage.example/signed/fr.mp4",
      "audio": "https://storage.example/signed/fr.wav",
      "srt": "https://storage.example/signed/fr.srt"
    }
  }
}
```

## List video translation requests

**get** `/v1/video-translate/requests`

Returns the authenticated account's requests, newest first (`created_at` descending, then `id` descending), always paginated. `pagination.total` counts every request matching the filters. Responses use the same shape as `GET /v1/video-translate/requests/{id}`.

### Query Parameters

- `created_from: optional string`

  Inclusive lower bound on `created_at` (RFC 3339). Must not be after `created_to`.

- `created_to: optional string`

  Inclusive upper bound on `created_at` (RFC 3339).

- `limit: optional number`

  Page size. Non-integers and values below 1 are rejected with 400; values above 100 are clamped to 100.

- `offset: optional number`

  Number of items to skip. Non-integers and negative values are rejected with 400.

- `reference_id: optional string`

  Filter requests by exact `reference_id`.

- `source_language: optional string`

  Filter by the explicit or detected source language (supported language code).

- `status: optional "queued" or "uploading" or "processing" or 2 more`

  Filter by request status.

  - `"queued"`

  - `"uploading"`

  - `"processing"`

  - `"completed"`

  - `"failed"`

- `target_language: optional string`

  Return requests whose `target_languages` contain this supported language code.

### Returns

- `data: array of object { id, config, created_at, 13 more }`

  - `id: string`

  - `config: object { source_srt, speaker_voices, target_srts, tts_model }`

    The request config as returned by the API. SRT content is replaced by metadata, `speaker_voices` keys are canonical speaker IDs, and other keys are returned unchanged.

    - `source_srt: optional object { supplied, file_name, language }`

      Public description of a supplied SRT. The subtitle content is never returned.

      - `supplied: true`

        - `true`

      - `file_name: optional string`

      - `language: optional string`

    - `speaker_voices: optional map[string]`

    - `target_srts: optional map[object { supplied, file_name, language } ]`

      - `supplied: true`

        - `true`

      - `file_name: optional string`

      - `language: optional string`

    - `tts_model: optional "eleven_v4" or "eleven_multilingual_v2"`

      The explicitly selected text-to-speech model. Omitted when the request used the default.

      - `"eleven_v4"`

      - `"eleven_multilingual_v2"`

  - `created_at: string`

  - `disable_background_audio: boolean`

    Whether background audio was disabled for this request.

  - `disable_lip_sync: boolean`

    Whether lip sync was disabled for this request.

  - `formality: boolean`

  - `model: "expressive" or "classic"`

    Product speech model selected for the request.

    - `"expressive"`

    - `"classic"`

  - `status: "queued" or "uploading" or "processing" or 2 more`

    - `"queued"`

    - `"uploading"`

    - `"processing"`

    - `"completed"`

    - `"failed"`

  - `target_languages: array of string`

  - `error_message: optional string`

    Present only when failed. A safe, bounded description, for example SRT or speaker validation errors found after the video was processed.

  - `finished_at: optional string`

  - `outputs: optional map[object { audio, srt, video } ]`

    Present only when completed, keyed by requested target language. URLs are signed on every read.

    - `audio: string`

    - `srt: string`

    - `video: string`

  - `reference_id: optional string`

  - `source_language: optional string`

    Explicit source language, or the detected one once processing starts.

  - `started_at: optional string`

  - `webhook_url: optional string`

    The configured terminal callback URL, when supplied.

- `pagination: object { limit, offset, total }`

  - `limit: number`

    Applied page size.

  - `offset: number`

    Applied result offset.

  - `total: number`

    Total number of matching records.

### Example

```http
curl https://api.chamelaion.com/api/v1/video-translate/requests \
    -H "Authorization: Bearer $CHAMELAION_BEARER_TOKEN"
```

#### Response

```json
{
  "data": [
    {
      "id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
      "config": {
        "source_srt": {
          "supplied": true,
          "file_name": "file_name",
          "language": "language"
        },
        "speaker_voices": {
          "foo": "string"
        },
        "target_srts": {
          "foo": {
            "supplied": true,
            "file_name": "file_name",
            "language": "language"
          }
        },
        "tts_model": "eleven_v4"
      },
      "created_at": "2019-12-27T18:11:19.117Z",
      "disable_background_audio": true,
      "disable_lip_sync": true,
      "formality": true,
      "model": "expressive",
      "status": "queued",
      "target_languages": [
        "string"
      ],
      "error_message": "error_message",
      "finished_at": "2019-12-27T18:11:19.117Z",
      "outputs": {
        "foo": {
          "audio": "https://example.com",
          "srt": "https://example.com",
          "video": "https://example.com"
        }
      },
      "reference_id": "reference_id",
      "source_language": "source_language",
      "started_at": "2019-12-27T18:11:19.117Z",
      "webhook_url": "https://example.com"
    }
  ],
  "pagination": {
    "limit": 1,
    "offset": 0,
    "total": 0
  }
}
```

## Domain Types

### Request Retrieve Response

- `RequestRetrieveResponse object { id, config, created_at, 13 more }`

  - `id: string`

  - `config: object { source_srt, speaker_voices, target_srts, tts_model }`

    The request config as returned by the API. SRT content is replaced by metadata, `speaker_voices` keys are canonical speaker IDs, and other keys are returned unchanged.

    - `source_srt: optional object { supplied, file_name, language }`

      Public description of a supplied SRT. The subtitle content is never returned.

      - `supplied: true`

        - `true`

      - `file_name: optional string`

      - `language: optional string`

    - `speaker_voices: optional map[string]`

    - `target_srts: optional map[object { supplied, file_name, language } ]`

      - `supplied: true`

        - `true`

      - `file_name: optional string`

      - `language: optional string`

    - `tts_model: optional "eleven_v4" or "eleven_multilingual_v2"`

      The explicitly selected text-to-speech model. Omitted when the request used the default.

      - `"eleven_v4"`

      - `"eleven_multilingual_v2"`

  - `created_at: string`

  - `disable_background_audio: boolean`

    Whether background audio was disabled for this request.

  - `disable_lip_sync: boolean`

    Whether lip sync was disabled for this request.

  - `formality: boolean`

  - `model: "expressive" or "classic"`

    Product speech model selected for the request.

    - `"expressive"`

    - `"classic"`

  - `status: "queued" or "uploading" or "processing" or 2 more`

    - `"queued"`

    - `"uploading"`

    - `"processing"`

    - `"completed"`

    - `"failed"`

  - `target_languages: array of string`

  - `error_message: optional string`

    Present only when failed. A safe, bounded description, for example SRT or speaker validation errors found after the video was processed.

  - `finished_at: optional string`

  - `outputs: optional map[object { audio, srt, video } ]`

    Present only when completed, keyed by requested target language. URLs are signed on every read.

    - `audio: string`

    - `srt: string`

    - `video: string`

  - `reference_id: optional string`

  - `source_language: optional string`

    Explicit source language, or the detected one once processing starts.

  - `started_at: optional string`

  - `webhook_url: optional string`

    The configured terminal callback URL, when supplied.

### Request List Response

- `RequestListResponse object { data, pagination }`

  - `data: array of object { id, config, created_at, 13 more }`

    - `id: string`

    - `config: object { source_srt, speaker_voices, target_srts, tts_model }`

      The request config as returned by the API. SRT content is replaced by metadata, `speaker_voices` keys are canonical speaker IDs, and other keys are returned unchanged.

      - `source_srt: optional object { supplied, file_name, language }`

        Public description of a supplied SRT. The subtitle content is never returned.

        - `supplied: true`

          - `true`

        - `file_name: optional string`

        - `language: optional string`

      - `speaker_voices: optional map[string]`

      - `target_srts: optional map[object { supplied, file_name, language } ]`

        - `supplied: true`

          - `true`

        - `file_name: optional string`

        - `language: optional string`

      - `tts_model: optional "eleven_v4" or "eleven_multilingual_v2"`

        The explicitly selected text-to-speech model. Omitted when the request used the default.

        - `"eleven_v4"`

        - `"eleven_multilingual_v2"`

    - `created_at: string`

    - `disable_background_audio: boolean`

      Whether background audio was disabled for this request.

    - `disable_lip_sync: boolean`

      Whether lip sync was disabled for this request.

    - `formality: boolean`

    - `model: "expressive" or "classic"`

      Product speech model selected for the request.

      - `"expressive"`

      - `"classic"`

    - `status: "queued" or "uploading" or "processing" or 2 more`

      - `"queued"`

      - `"uploading"`

      - `"processing"`

      - `"completed"`

      - `"failed"`

    - `target_languages: array of string`

    - `error_message: optional string`

      Present only when failed. A safe, bounded description, for example SRT or speaker validation errors found after the video was processed.

    - `finished_at: optional string`

    - `outputs: optional map[object { audio, srt, video } ]`

      Present only when completed, keyed by requested target language. URLs are signed on every read.

      - `audio: string`

      - `srt: string`

      - `video: string`

    - `reference_id: optional string`

    - `source_language: optional string`

      Explicit source language, or the detected one once processing starts.

    - `started_at: optional string`

    - `webhook_url: optional string`

      The configured terminal callback URL, when supplied.

  - `pagination: object { limit, offset, total }`

    - `limit: number`

      Applied page size.

    - `offset: number`

      Applied result offset.

    - `total: number`

      Total number of matching records.

# Voices

## List voices for speaker assignment

**get** `/v1/video-translate/voices`

Lists the voices that can be used as values in `config.speaker_voices`.
`voice_id` is the external voice ID to submit. `name` and `is_favorite` reflect the
authenticated account's personalization; `default_name` is the catalog name.
Ordered by favorites first, then `name` (case-insensitive), then `voice_id`.

### Query Parameters

- `application: optional string`

  Case-insensitive exact match on the voice's application (use case), for example `narration` (max 100 characters).

- `favorite: optional boolean`

  Only favorites (`true`) or only non-favorites (`false`).

- `gender: optional "Male" or "Female" or "Other"`

  Case-insensitive gender filter.

  - `"Male"`

  - `"Female"`

  - `"Other"`

- `language: optional string`

  Case-insensitive exact match on the voice's primary language or any of its verified languages (max 100 characters).

- `limit: optional number`

  Page size. Non-integers and values below 1 are rejected with 400; values above 100 are clamped to 100.

- `offset: optional number`

  Number of items to skip. Non-integers and negative values are rejected with 400.

- `search: optional string`

  Case-insensitive substring of the voice's effective (account-personalized) name.

### Returns

- `pagination: object { limit, offset, total }`

  - `limit: number`

    Applied page size.

  - `offset: number`

    Applied result offset.

  - `total: number`

    Total number of matching records.

- `voices: array of object { application, default_name, description, 5 more }`

  - `application: string`

    Intended use case, for example `narration`.

  - `default_name: string`

  - `description: string`

  - `gender: "Male" or "Female" or "Other"`

    - `"Male"`

    - `"Female"`

    - `"Other"`

  - `is_favorite: boolean`

    Whether the authenticated account marked this voice as a favorite.

  - `language: string`

    Primary language label.

  - `name: string`

    Effective name (the account's custom name when set, otherwise `default_name`).

  - `voice_id: string`

    External voice ID. Use this value in `config.speaker_voices`.

### Example

```http
curl https://api.chamelaion.com/api/v1/video-translate/voices \
    -H "Authorization: Bearer $CHAMELAION_BEARER_TOKEN"
```

#### Response

```json
{
  "voices": [
    {
      "voice_id": "21m00Tcm4TlvDq8ikWAM",
      "name": "Rachel (narration)",
      "default_name": "Rachel",
      "description": "Calm, young adult female voice.",
      "language": "en",
      "application": "narration",
      "gender": "Female",
      "is_favorite": true
    }
  ],
  "pagination": {
    "limit": 50,
    "offset": 0,
    "total": 1
  }
}
```

## Domain Types

### Voice List Response

- `VoiceListResponse object { pagination, voices }`

  - `pagination: object { limit, offset, total }`

    - `limit: number`

      Applied page size.

    - `offset: number`

      Applied result offset.

    - `total: number`

      Total number of matching records.

  - `voices: array of object { application, default_name, description, 5 more }`

    - `application: string`

      Intended use case, for example `narration`.

    - `default_name: string`

    - `description: string`

    - `gender: "Male" or "Female" or "Other"`

      - `"Male"`

      - `"Female"`

      - `"Other"`

    - `is_favorite: boolean`

      Whether the authenticated account marked this voice as a favorite.

    - `language: string`

      Primary language label.

    - `name: string`

      Effective name (the account's custom name when set, otherwise `default_name`).

    - `voice_id: string`

      External voice ID. Use this value in `config.speaker_voices`.
