> ## Documentation Index
> Fetch the complete documentation index at: https://docs.snapgen.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Dubbing

> Translate and re-voice a video or audio file into another language as an asynchronous task, then download the dubbed file.

`POST /v1/audio/dubbing` translates the speech in a video or audio file into another language and re-voices it. Dubbing runs as an asynchronous task: submit the job, poll [`GET /v1/tasks/{id}`](/api-reference/task-status), then download the dubbed file from `GET /v1/tasks/{id}/content`. Output has no watermark.

| Model | Source | Price |
| - | - | - |
| [`eleven-dubbing-v1`](/api-manual/audio/eleven-dubbing-v1) | Up to 1,800 seconds (30 minutes) | \$0.0125 per source second (\$0.75 per minute) |

<ParamField body="model" type="string" required>
  Set to `eleven-dubbing-v1`.
</ParamField>

<ParamField body="source_url" type="string" required>
  Public `http` or `https` URL of the video or audio to dub, 50 MB or smaller.
  SnapGen downloads it once, measures its length for billing, and sends that
  same file to the provider.
</ParamField>

<ParamField body="target_language" type="string" required>
  The language to dub into, as a code from the [language list](#languages),
  for example `es`.
</ParamField>

<ParamField body="source_language" type="string" default="auto">
  The language spoken in the source, from the same list. `auto` detects it.
</ParamField>

<ParamField body="num_speakers" type="integer">
  Number of speakers, from `0` to `32`. `0` detects it automatically.
</ParamField>

<ParamField body="highest_resolution" type="boolean">
  Uses the highest available resolution for video output.
</ParamField>

<ParamField body="drop_background_audio" type="boolean">
  Drops the background audio from the dub. This can help speeches and
  monologues that shouldn't carry a background track.
</ParamField>

<ParamField body="webhook" type="string">
  Optional public `https` URL that receives one signed event when the task
  reaches a final state. See [Task webhooks](/api-reference/webhooks).
</ParamField>

<ParamField body="webhook_secret" type="string">
  Required with `webhook`: 16 to 256 characters, used to sign the delivery.
</ParamField>

Send the body as JSON. Add an `Idempotency-Key` header (1 to 200 characters)
so that a retried submission returns the same task instead of starting a
second, billed job.

## Source files

SnapGen downloads the source once, measures its length from the media itself
(never from a declared duration), and uploads that exact file to the provider.
The source must be 50 MB or smaller and in MP4, MOV, WebM, MP3, WAV, M4A, AAC,
FLAC, or OGG. If its length can't be read, the request fails with
`unsupported_audio_format` and nothing is charged.

## Languages

`target_language` takes one of these 32 codes. `source_language` takes the
same codes or `auto`.

| Code | Language | Code | Language |
| - | - | - | - |
| `en` | English | `sv` | Swedish |
| `hi` | Hindi | `fil` | Filipino |
| `pt` | Portuguese | `ms` | Malay |
| `zh` | Chinese | `ro` | Romanian |
| `es` | Spanish | `uk` | Ukrainian |
| `fr` | French | `el` | Greek |
| `de` | German | `cs` | Czech |
| `ja` | Japanese | `da` | Danish |
| `ar` | Arabic | `fi` | Finnish |
| `ru` | Russian | `bg` | Bulgarian |
| `ko` | Korean | `hr` | Croatian |
| `id` | Indonesian | `sk` | Slovak |
| `it` | Italian | `ta` | Tamil |
| `nl` | Dutch | `hu` | Hungarian |
| `tr` | Turkish | `no` | Norwegian |
| `pl` | Polish | `vi` | Vietnamese |

## Submit a task

A successful submission returns HTTP `202` with a [task object](/api-reference/task-status)
whose `status` is `queued`. The `Location` header holds `/v1/tasks/{id}`, and
`x-gateway-task-id` holds the task ID. Save the ID with your job record.

## Poll the task

Poll `GET /v1/tasks/{id}` every 10 to 20 seconds until `status` is `succeeded`
or `failed`, or pass `webhook` to be notified instead. When the task succeeds,
`result` describes the dub:

<ResponseField name="result.content_path" type="string">
  The download path, `/v1/tasks/{id}/content`.
</ResponseField>

<ResponseField name="result.target_language" type="string">
  The language of the dub.
</ResponseField>

<ResponseField name="result.source_language" type="string | null">
  The source language the provider used or detected.
</ResponseField>

<ResponseField name="result.content_type" type="string | null">
  The media type the provider reports, such as `video/mp4`. Treat it as a
  hint: it can be missing, and the download's own `Content-Type` is
  authoritative.
</ResponseField>

<ResponseField name="result.has_video" type="boolean | null">
  Whether the dub is a video, derived from `content_type`.
</ResponseField>

<ResponseField name="result.duration_seconds" type="number | null">
  The dub's length when the provider reports it.
</ResponseField>

<ResponseField name="result.urls" type="string[]">
  Always empty for dubbing. Download the file from the content endpoint.
</ResponseField>

## Download the dub

`GET /v1/tasks/{id}/content` streams the dubbed file with the same API key.
The response carries the file's `Content-Type` (an `audio/` or `video/` type)
and `Content-Disposition: attachment`, for example
`filename="dubbed-es.mp4"`. Send a `Range` header to resume a partial
download. Before the task succeeds, the endpoint returns HTTP `409` with
`task_not_ready`.

```bash theme={null}
curl -L https://api.snapgen.org/v1/tasks/task_9QmR2vX7kLp4Tz8wYc1Nb6/content \
  -H "Authorization: Bearer $SNAPGEN_API_KEY" \
  --output dubbed.mp4
```

## Billing

The job is billed for the measured length of the source at \$0.0125 per
second, rounded up to whole seconds after a 0.1-second allowance. The gateway
reserves that amount at submission and settles it when the task succeeds. A
61.2-second clip bills 62 seconds (\$0.775); a 30-minute source costs \$22.50.
Failed tasks release the reservation and aren't charged.

## Errors

| Status | `error.code` | Cause |
| - | - | - |
| 400 | `invalid_request` | `source_url` or `target_language` is missing or invalid, or a field is unknown. |
| 400 | `invalid_task_command` | `webhook` or `webhook_secret` is missing or breaks the [webhook rules](/api-reference/webhooks). |
| 400 | `unsupported_audio_format` | The source length can't be read. Use MP4, MOV, WebM, MP3, WAV, M4A, AAC, FLAC, or OGG. |
| 400 | `media_too_long` | The source is longer than 1,800 seconds. |
| 400 | `audio_download_failed` | The source could not be downloaded. |
| 402 | `insufficient_funds` | Your balance can't cover the reservation. |
| 409 | `idempotency_conflict` | The `Idempotency-Key` was already used with a different body. |
| 409 | `task_not_ready` | The content endpoint was called before the task succeeded. |
| 413 | `audio_too_large` | The source is larger than 50 MB. |

A task that fails at the provider reports `status: "failed"` with
`error.code` `provider_task_failed`. See [Errors](/errors) for rate limits and
other failures.

<RequestExample>
  ```bash Curl theme={null}
  curl https://api.snapgen.org/v1/audio/dubbing \
    -H "Authorization: Bearer $SNAPGEN_API_KEY" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: product-demo-es" \
    -d '{
      "model": "eleven-dubbing-v1",
      "source_url": "https://example.com/product-demo.mp4",
      "target_language": "es"
    }'
  ```

  ```python Python theme={null}
  import json
  import os
  import time
  from urllib.request import Request, urlopen

  API = "https://api.snapgen.org/v1"
  HEADERS = {"Authorization": f"Bearer {os.environ['SNAPGEN_API_KEY']}"}

  submit = Request(
      f"{API}/audio/dubbing",
      data=json.dumps({
          "model": "eleven-dubbing-v1",
          "source_url": "https://example.com/product-demo.mp4",
          "target_language": "es",
      }).encode(),
      headers={**HEADERS, "Content-Type": "application/json"},
  )
  with urlopen(submit) as response:
      task = json.load(response)

  while task["status"] not in ("succeeded", "failed", "expired", "reconciliation_required"):
      time.sleep(15)
      with urlopen(Request(f"{API}/tasks/{task['id']}", headers=HEADERS)) as response:
          task = json.load(response)

  if task["status"] != "succeeded":
      raise RuntimeError(task["error"])

  with urlopen(Request(f"{API}/tasks/{task['id']}/content", headers=HEADERS)) as response:
      with open("dubbed.mp4", "wb") as file:
          file.write(response.read())
  ```

  ```javascript Node.js theme={null}
  import { writeFile } from "node:fs/promises";

  const API = "https://api.snapgen.org/v1";
  const headers = { Authorization: `Bearer ${process.env.SNAPGEN_API_KEY}` };

  const submitted = await fetch(`${API}/audio/dubbing`, {
    method: "POST",
    headers: { ...headers, "Content-Type": "application/json" },
    body: JSON.stringify({
      model: "eleven-dubbing-v1",
      source_url: "https://example.com/product-demo.mp4",
      target_language: "es",
    }),
  });
  if (!submitted.ok) throw new Error(await submitted.text());
  let task = await submitted.json();

  const done = ["succeeded", "failed", "expired", "reconciliation_required"];
  while (!done.includes(task.status)) {
    await new Promise((resolve) => setTimeout(resolve, 15_000));
    task = await (await fetch(`${API}/tasks/${task.id}`, { headers })).json();
  }
  if (task.status !== "succeeded") throw new Error(JSON.stringify(task.error));

  const media = await fetch(`${API}/tasks/${task.id}/content`, { headers });
  await writeFile("dubbed.mp4", Buffer.from(await media.arrayBuffer()));
  ```
</RequestExample>

<ResponseExample>
  ```json 202 Queued theme={null}
  {
    "id": "task_9QmR2vX7kLp4Tz8wYc1Nb6",
    "object": "task",
    "status": "queued",
    "model": "eleven-dubbing-v1",
    "modality": "audio",
    "progress": null,
    "result": null,
    "error": null,
    "reserved_microusd": "775000",
    "charged_microusd": null,
    "created_at": 1790000000,
    "completed_at": null
  }
  ```

  ```json Succeeded theme={null}
  {
    "id": "task_9QmR2vX7kLp4Tz8wYc1Nb6",
    "object": "task",
    "status": "succeeded",
    "model": "eleven-dubbing-v1",
    "modality": "audio",
    "progress": 100,
    "result": {
      "urls": [],
      "content_path": "/v1/tasks/task_9QmR2vX7kLp4Tz8wYc1Nb6/content",
      "target_language": "es",
      "source_language": "en",
      "content_type": "video/mp4",
      "has_video": true,
      "duration_seconds": 61.2
    },
    "error": null,
    "reserved_microusd": "775000",
    "charged_microusd": "775000",
    "created_at": 1790000000,
    "completed_at": 1790000240
  }
  ```
</ResponseExample>
