---
title: Translating Videos | CHAMELAION
description: Translate a video into up to 10 languages with natural voices, lip sync, background audio, custom subtitles, and completion webhooks.
---

Video Translate turns one source video into translated versions in up to 10 target languages. CHAMELAION transcribes the speech, translates it, voices each speaker, keeps the background audio, and lip-syncs the result (background audio and lip sync are on by default). Each target language returns a video, an audio track, and an SRT subtitle file.

Send the video as a public URL or as a direct upload. Both endpoints validate the request, create an asynchronous job, and return a `request_id`. Poll it for the result, or set `webhook_url` to be notified.

## Translate a video URL

Use `POST /v1/video-translate/generate` when the source video is available at a public URL.

Terminal window

```
curl --request POST \
  https://api.chamelaion.com/api/v1/video-translate/generate \
  --header "Authorization: Bearer $CHAMELAION_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "reference_id": "translation-001",
    "source_language": "en",
    "target_languages": ["de", "fr"],
    "webhook_url": "https://example.com/webhooks/chamelaion",
    "inputs": [
      {
        "type": "video",
        "url": "https://storage.googleapis.com/chamelaion-test-media/video.mp4"
      }
    ]
  }'
```

The URL must be reachable by CHAMELAION. Signed URLs work as long as they stay valid until the video is downloaded.

### Parameters

| Parameter                  | Type    | Required | Description                                                                                                  |
| :------------------------- | :------ | :------- | :----------------------------------------------------------------------------------------------------------- |
| `inputs`                   | array   | Yes      | Exactly one item with `type` set to `"video"` and the video `url`                                            |
| `target_languages`         | array   | Yes      | 1–10 unique [language codes](#supported-languages), excluding the source language                            |
| `source_language`          | string  | No       | Language spoken in the video. Detected automatically when omitted                                            |
| `reference_id`             | string  | No       | Your own identifier (max 255 characters), usable for retrieval and filtering                                 |
| `webhook_url`              | string  | No       | HTTPS URL that receives a `POST` when the request completes or fails                                         |
| `disable_background_audio` | boolean | No       | Set to `true` to drop the original background audio (default: `false`)                                       |
| `disable_lip_sync`         | boolean | No       | Set to `true` to skip lip sync and only replace the audio (default: `false`)                                 |
| `formality`                | boolean | No       | Set to `true` for a formal tone in translations, `false` for a casual tone (default: `false`)                |
| `model`                    | string  | No       | `expressive` (default) or `classic`; see [Choose a speech model](#choose-a-speech-model)                     |
| `config`                   | object  | No       | Optional [subtitles](#supply-srt-files), [speaker voices](#assign-voices-to-speakers), and advanced settings |

## Choose a speech model

Set the top-level `model` field to choose the speech model. When you omit it, CHAMELAION uses `expressive`.

| Value        | Product name          | Target-language support                    |
| :----------- | :-------------------- | :----------------------------------------- |
| `expressive` | CHAMELAION Expressive | All 91 canonical target languages; default |
| `classic`    | CHAMELAION Classic    | The legacy 29 canonical target languages   |

The product UI may show 93 Expressive and 31 Classic languages because it also counts accepted regional aliases. Existing integrations may continue to send `config.tts_model` (`eleven_v4` or `eleven_multilingual_v2`), but that field is deprecated. If both fields are sent, they must select the same model.

This example selects Classic, translates to English, and disables both background audio and lip sync:

Terminal window

```
curl --request POST \
  https://api.chamelaion.com/api/v1/video-translate/generate \
  --header "Authorization: Bearer $CHAMELAION_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "source_language": "de",
    "target_languages": ["en"],
    "disable_background_audio": true,
    "disable_lip_sync": true,
    "model": "classic",
    "inputs": [{
      "type": "video",
      "url": "https://storage.googleapis.com/chamelaion-test-media/video.mp4"
    }]
  }'
```

This example selects Expressive and translates to Kannada with background audio and lip sync enabled:

Terminal window

```
curl --request POST \
  https://api.chamelaion.com/api/v1/video-translate/generate \
  --header "Authorization: Bearer $CHAMELAION_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "source_language": "en",
    "target_languages": ["kn"],
    "disable_background_audio": false,
    "disable_lip_sync": false,
    "model": "expressive",
    "inputs": [{
      "type": "video",
      "url": "https://storage.googleapis.com/chamelaion-test-media/video.mp4"
    }]
  }'
```

The model and every target language are validated before a job is created. An unsupported model value or a target language unavailable for that model returns a `400` response.

## Upload a video directly

Use `POST /v1/video-translate/generate-with-media` to send the video as `multipart/form-data`. It accepts the same settings as form fields. `target_languages` can be a JSON array or a comma-separated string, and `config` is a JSON-encoded object.

Terminal window

```
curl --request POST \
  https://api.chamelaion.com/api/v1/video-translate/generate-with-media \
  --header "Authorization: Bearer $CHAMELAION_API_KEY" \
  --form "video=@/absolute/path/source-video.mp4;type=video/mp4" \
  --form 'target_languages=["de","fr"]' \
  --form 'model=expressive' \
  --form "source_language=en" \
  --form "reference_id=translation-upload-001" \
  --form "webhook_url=https://example.com/webhooks/chamelaion"
```

The video can be up to 2 GiB and must contain a video stream.

## Supply SRT files

By default, CHAMELAION transcribes and translates the speech automatically. To control the exact wording, upload the source subtitles as `source_srt` and translated subtitles as `target_srt.<language>`. Every target SRT language must also appear in `target_languages`. Languages without a target SRT are translated automatically.

Terminal window

```
curl --request POST \
  https://api.chamelaion.com/api/v1/video-translate/generate-with-media \
  --header "Authorization: Bearer $CHAMELAION_API_KEY" \
  --form "video=@/absolute/path/source-video.mp4;type=video/mp4" \
  --form 'target_languages=["de"]' \
  --form "source_language=en" \
  --form "source_srt=@/absolute/path/source-en.srt;type=application/x-subrip" \
  --form "target_srt.de=@/absolute/path/target-de.srt;type=application/x-subrip"
```

Each cue’s text must start with `Speaker N:`, where `N` is a non-negative integer:

```
1
00:00:00,000 --> 00:00:02,500
Speaker 0: Welcome back.


2
00:00:02,600 --> 00:00:05,000
Speaker 1: Thanks for having me.
```

- SRT files must be UTF-8 encoded, with file names ending in `.srt`.
- Source and target files must have the same number of cues and the same speakers.
- Cue timings must fit within the video’s duration.

Syntax errors return `400` immediately. Cue, speaker, and timing mismatches are checked once the video is processed, and fail the request with an `error_message`.

You can also send subtitles inline in `config` on either endpoint, as `config.source_srt` and `config.target_srts.<language>`. Each is an object with a `content` string and optional `file_name` and `language`. Don’t send the same subtitle both inline and as a file.

## Assign voices to speakers

List the voices available to your account. Filter by `language`, `gender`, `application`, `search`, or `favorite`:

Terminal window

```
curl --request GET \
  "https://api.chamelaion.com/api/v1/video-translate/voices?language=en&limit=50" \
  --header "Authorization: Bearer $CHAMELAION_API_KEY"
```

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

Map speaker IDs to a `voice_id` in `config.speaker_voices`. Keys are the speaker numbers from the `Speaker N:` cues, written as strings such as `"0"` and `"1"`; names such as `"speaker-1"` are rejected. You can assign up to 32 speakers.

Terminal window

```
curl --request POST \
  https://api.chamelaion.com/api/v1/video-translate/generate-with-media \
  --header "Authorization: Bearer $CHAMELAION_API_KEY" \
  --form "video=@/absolute/path/source-video.mp4;type=video/mp4" \
  --form 'target_languages=["de"]' \
  --form "source_srt=@/absolute/path/source-en.srt;type=application/x-subrip" \
  --form 'config={"speaker_voices":{"0":"21m00Tcm4TlvDq8ikWAM","1":"AZnzlk1XvdvUeBnXmlld"}}'
```

Voice IDs are checked before the request is created. Speaker IDs are checked against the source subtitles once the video is processed, so include a `source_srt` when you assign voices. If a speaker ID is not found, the request fails before translation starts.

## Poll a request

Generation returns immediately:

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

Retrieve the request by its UUID or exact `reference_id`. If several requests share a `reference_id`, the newest is returned.

Terminal window

```
curl --request GET \
  https://api.chamelaion.com/api/v1/video-translate/requests/4c98dd00-a982-47d9-a8d6-e1f0b35a8944 \
  --header "Authorization: Bearer $CHAMELAION_API_KEY"
```

A request moves through `queued` → `uploading` → `processing` → `completed` or `failed`. When it is `completed`, `outputs` is keyed by target language and contains video, audio, and SRT URLs. URLs are signed on every read and expire after one hour, so fetch the request again rather than storing them.

```
{
  "id": "4c98dd00-a982-47d9-a8d6-e1f0b35a8944",
  "reference_id": "translation-001",
  "webhook_url": "https://example.com/webhooks/chamelaion",
  "source_language": "en",
  "target_languages": ["de", "fr"],
  "disable_background_audio": false,
  "disable_lip_sync": false,
  "formality": false,
  "config": {},
  "status": "completed",
  "created_at": "2026-10-09T10:00:00Z",
  "started_at": "2026-10-09T10:00:01Z",
  "finished_at": "2026-10-09T10: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"
    }
  }
}
```

When a request fails, `error_message` explains why. Subtitle content is never returned; `config` shows only the file name and language of each supplied SRT.

## List requests

List requests newest first, with pagination and optional filters:

Terminal window

```
curl --request GET \
  "https://api.chamelaion.com/api/v1/video-translate/requests?limit=20&offset=0&status=completed&target_language=de" \
  --header "Authorization: Bearer $CHAMELAION_API_KEY"
```

| Parameter         | Description                                                                     |
| :---------------- | :------------------------------------------------------------------------------ |
| `limit`           | Page size, 1–100 (default: 20)                                                  |
| `offset`          | Number of results to skip (default: 0)                                          |
| `reference_id`    | Exact `reference_id` match                                                      |
| `status`          | `queued`, `uploading`, `processing`, `completed`, or `failed`                   |
| `source_language` | Explicit or detected source language                                            |
| `target_language` | Requests that include this target language                                      |
| `created_from`    | Created at or after this RFC 3339 timestamp, for example `2026-10-01T00:00:00Z` |
| `created_to`      | Created at or before this RFC 3339 timestamp                                    |

The response has a `data` array and a `pagination` object with `limit`, `offset`, and `total`.

## Receive completion webhooks

Set `webhook_url` to a public HTTPS endpoint. CHAMELAION sends one JSON `POST` when the request reaches `completed` or `failed`:

```
{
  "id": "5b0e7f7c-3c1e-4a5e-9f5d-0c6f1f0e2a11",
  "type": "video_translate.completed",
  "created_at": "2026-10-09T10:04:00Z",
  "data": {
    "id": "4c98dd00-a982-47d9-a8d6-e1f0b35a8944",
    "status": "completed",
    "outputs": { "de": { "video": "https://...", "audio": "https://...", "srt": "https://..." } }
  }
}
```

- `data` has the same fields as [`GET /v1/video-translate/requests/{id}`](#poll-a-request), without `webhook_url`.
- The `X-Chamelaion-Event` header is `video_translate.completed` or `video_translate.failed`.
- The `X-Chamelaion-Delivery` header matches the body’s `id` and stays the same across retries. Use it to deduplicate.
- Each attempt times out after 10 seconds. Network errors, HTTP 429, and HTTP 5xx are retried, for up to three attempts in total.
- Redirects are not followed. Redirects and other non-2xx responses (except 429) are not retried.
- Webhooks are not signed. Before acting on an event, confirm it by retrieving the request from the API.

Return a 2xx response quickly and process the event asynchronously. Delivery is best effort, so [poll the request](#poll-a-request) as a fallback.

## Limits

| Item                         | Limit                                        |
| :--------------------------- | :------------------------------------------- |
| Target languages per request | 10                                           |
| Video size (URL or upload)   | 2 GiB                                        |
| JSON request body            | 8 MiB                                        |
| SRT file                     | 1 MiB and 5,000 cues each, 5 MiB per request |
| Speakers in `speaker_voices` | 32                                           |
| `webhook_url` length         | 2,048 bytes                                  |

## Supported languages

Source and target support differ. Use canonical lowercase codes; regional English and Portuguese codes are normalized where supported.

**Source languages (84):** `af`, `am`, `ar`, `as`, `az`, `be`, `bg`, `bn`, `bs`, `ca`, `cs`, `cy`, `da`, `de`, `el`, `en`, `es`, `et`, `fa`, `fi`, `fil`, `fr`, `gl`, `gu`, `ha`, `he`, `hi`, `hr`, `hu`, `hy`, `id`, `is`, `it`, `ja`, `jv`, `ka`, `kk`, `km`, `kn`, `ko`, `lb`, `ln`, `lo`, `lt`, `lv`, `mi`, `mk`, `ml`, `mn`, `mr`, `ms`, `mt`, `my`, `ne`, `nl`, `no`, `oc`, `or`, `pa`, `pl`, `ps`, `pt`, `ro`, `ru`, `sd`, `si`, `sk`, `sl`, `sn`, `so`, `sr`, `sv`, `sw`, `ta`, `te`, `tg`, `th`, `tr`, `uk`, `ur`, `uz`, `vi`, `yo`, `zh`.

**Expressive target languages (91):** `af`, `am`, `ar`, `as`, `az`, `be`, `bg`, `bn`, `bs`, `ca`, `ceb`, `ckb`, `cs`, `cy`, `da`, `de`, `el`, `en`, `es`, `et`, `fa`, `ff`, `fi`, `fil`, `fr`, `ga`, `gl`, `gu`, `ha`, `he`, `hi`, `hr`, `hu`, `hy`, `id`, `is`, `it`, `ja`, `jv`, `ka`, `kk`, `kn`, `ko`, `ky`, `lb`, `lg`, `ln`, `lo`, `lt`, `lv`, `mi`, `mk`, `ml`, `mn`, `mr`, `ms`, `mt`, `my`, `ne`, `nl`, `no`, `ny`, `oc`, `or`, `pa`, `pl`, `ps`, `pt`, `ro`, `ru`, `sd`, `sk`, `sl`, `sn`, `so`, `sr`, `sv`, `sw`, `ta`, `te`, `tg`, `th`, `tr`, `uk`, `ur`, `uz`, `vi`, `yo`, `yue`, `zh`, `zu`.

**Classic target languages (29):** `ar`, `bg`, `zh`, `hr`, `cs`, `da`, `nl`, `en`, `fi`, `fr`, `de`, `el`, `hi`, `id`, `it`, `ja`, `ko`, `ms`, `fil`, `pl`, `pt`, `ro`, `ru`, `sk`, `es`, `sv`, `ta`, `tr`, `uk`.

The aliases `en-gb`, `en-us`, `pt-br`, `pt-pt`, and `tl` are accepted and normalized to their base language. `jw` is accepted as a legacy alias for `jv`.

## SDK examples

The official [TypeScript](https://www.npmjs.com/package/chamelaion), [Python](https://pypi.org/project/chamelaion/), and [Go](https://github.com/chamelaion/chamelaion-go) SDKs support Video Translate starting with `v1.1.0`. Each client reads your key from `CHAMELAION_API_KEY` when you don’t pass one.

### TypeScript

```
import Chamelaion from "chamelaion";


const client = new Chamelaion(); // reads CHAMELAION_API_KEY


const job = await client.videoTranslate.generate({
  source_language: "en",
  target_languages: ["kn"],
  model: "expressive",
  inputs: [{ type: "video", url: "https://storage.googleapis.com/chamelaion-test-media/video.mp4" }],
});


const result = await client.videoTranslate.requests.retrieve(job.request_id);
```

### Python

```
from chamelaion import Chamelaion


client = Chamelaion()  # reads CHAMELAION_API_KEY


job = client.video_translate.generate(
    source_language="en",
    target_languages=["kn"],
    model="expressive",
    inputs=[{"type": "video", "url": "https://storage.googleapis.com/chamelaion-test-media/video.mp4"}],
)


result = client.video_translate.requests.retrieve(job.request_id)
```

### Go

```
ctx := context.Background()
client := chamelaion.NewClient() // reads CHAMELAION_API_KEY


job, err := client.VideoTranslate.Generate(ctx, chamelaion.VideoTranslateGenerateParams{
  SourceLanguage:  chamelaion.String("en"),
  TargetLanguages: []string{"kn"},
  Model:            chamelaion.VideoTranslateModelExpressive,
  Inputs: []chamelaion.VideoTranslateGenerateParamsInput{
    {Type: "video", URL: "https://storage.googleapis.com/chamelaion-test-media/video.mp4"},
  },
})
if err != nil {
  log.Fatal(err)
}


result, err := client.VideoTranslate.Requests.Get(ctx, job.RequestID)
if err != nil {
  log.Fatal(err)
}
fmt.Println(result.Status)
```

The SDKs support Video Translate starting with `v1.1.0`; the product-facing `model` field is available in `v2.0.0`. See the [Video Translate API reference](/api/resources/video_translate/index.md) for complete request and response schemas and SDK methods.
