ElevenLabs Dubbing
Dub audio or video into other languages while preserving the original speakers' voices. Create a project from a file or URL, review and edit the source transcript, add one or more target languages, refine translations per segment, and regenerate outputs.
Important: Use the Dubbing Projects API —
elevenlabs.dubbing.project.*
in the SDKs, or the
REST endpoints. Do
not use the legacy v1 dubbing surface (
,
,
client.dubbing.audio.get()
, or bare
routes) — that is the older dubbing API, now under Legacy in the API reference.
Setup: See
Installation Guide. REST base URL is
https://api.elevenlabs.io
with your API key in the
header; the SDKs read
automatically.
Concepts
| Concept | Meaning |
|---|
| Project | One source of media (file or URL) plus its source transcript. Prepared (transcribed) once, then rests in while you add languages. |
| Source transcript | Editable segments (text, speaker, timing) transcribed from the source. The single source of truth every language is translated from. |
| Language (target) | One dubbed output language. Each has its own transcript (source segments + a translation per segment) and its own dubbed audio output. |
| Revisions | Independent monotonic counters. The project's bumps on source-transcript edits; a language's bumps on translation edits or source edits that affect it. A language's is the revision its current audio was generated from — when it's behind , the output is out of date. |
Recommended order of operations: finalize the source transcript
before adding any languages. Translations are produced from the source, so correcting the source first means every language starts from the right text — editing the source after a language completes marks it
and requires a (charged) regeneration.
Enterprise: Transcript editing and regeneration are available to enterprise workspaces only. Creating projects, adding languages, and downloading dubs work on all plans.
Workflow
- Create the project from a file or URL →
- Poll the project until
- Review and finalize the source transcript (edit/add/delete segments)
- Add one language per target → → →
- Download each language's when
- Refine translations per segment if needed → the language goes
- Regenerate the language → again with fresh output
Quick Start (Python)
python
import os
import time
import requests
from elevenlabs.client import ElevenLabs
elevenlabs = ElevenLabs(api_key=os.getenv("ELEVENLABS_API_KEY"))
# 1. Create a project from a local file (or pass source_url=... instead of file)
with open("promo.mp4", "rb") as f:
project = elevenlabs.dubbing.project.create(
file=f,
source_language="en",
reference="Q3 marketing video",
)
# 2. Wait for the source media to be transcribed
while True:
project = elevenlabs.dubbing.project.get(project.project_id)
if project.status == "ready":
break
if project.status == "failed":
raise RuntimeError("Project preparation failed")
time.sleep(5)
# 3. Add a Spanish language target
language = elevenlabs.dubbing.project.language.create(
project.project_id,
target_language="es",
)
# 4. Wait for the dub to finish generating
while True:
language = elevenlabs.dubbing.project.language.get(
project.project_id, language.language_id
)
if language.status == "completed":
break
if language.status == "failed":
raise RuntimeError("Dub generation failed")
time.sleep(5)
# 5. Download the dubbed audio (signed URL, valid ~1 hour — re-fetch the language for a fresh one)
audio = requests.get(language.outputs.lossless_audio)
with open("promo_es.wav", "wb") as f:
f.write(audio.content)
Quick Start (JavaScript)
typescript
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
import { writeFile } from "fs/promises";
const elevenlabs = new ElevenLabsClient();
// 1. Create a project (sourceUrl shown; file upload is also supported)
let project = await elevenlabs.dubbing.project.create({
sourceUrl: "https://example.com/promo.mp4",
sourceLanguage: "en",
reference: "Q3 marketing video",
});
// 2. Wait for the source media to be transcribed
while (true) {
project = await elevenlabs.dubbing.project.get(project.projectId);
if (project.status === "ready") break;
if (project.status === "failed") throw new Error("Project preparation failed");
await new Promise((resolve) => setTimeout(resolve, 5000));
}
// 3. Add a Spanish language target
let language = await elevenlabs.dubbing.project.language.create(project.projectId, {
targetLanguage: "es",
});
// 4. Wait for the dub to finish generating
while (true) {
language = await elevenlabs.dubbing.project.language.get(project.projectId, language.languageId);
if (language.status === "completed") break;
if (language.status === "failed") throw new Error("Dub generation failed");
await new Promise((resolve) => setTimeout(resolve, 5000));
}
// 5. Download the dubbed audio from the signed URL
const response = await fetch(language.outputs!.losslessAudio!);
await writeFile("promo_es.wav", Buffer.from(await response.arrayBuffer()));
Quick Start (cURL)
bash
# 1. Create a project (use -F "source_url=https://..." instead of file to dub from a URL)
curl -X POST "https://api.elevenlabs.io/v1/dubbing/project" \
-H "xi-api-key: $ELEVENLABS_API_KEY" \
-F "file=@promo.mp4" \
-F "source_language=en"
# → {"project_id": "proj_...", "status": "queued", ...}
# 2. Poll until status is "ready"
curl "https://api.elevenlabs.io/v1/dubbing/project/proj_..." \
-H "xi-api-key: $ELEVENLABS_API_KEY"
# 3. Add a target language
curl -X POST "https://api.elevenlabs.io/v1/dubbing/project/proj_.../language" \
-H "xi-api-key: $ELEVENLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"target_language": "es"}'
# 4. Poll the language until "completed", then download outputs.lossless_audio
curl "https://api.elevenlabs.io/v1/dubbing/project/proj_.../language/lang_..." \
-H "xi-api-key: $ELEVENLABS_API_KEY"
Create Options
takes
with
either or (not both):
| Field | Required | Notes |
|---|
| one of file/source_url | Source media to dub (audio or video), up to 3 GiB |
| one of file/source_url | Public URL to fetch the source media from |
| no | ISO 639 code (e.g. ). Omit to auto-detect — the detected language is reported on the source transcript's field |
| no | Free-form label to identify the project on your end (max 500 chars) |
| no | (default) |
| no | Optionally queue the first language target at creation; add more with |
| no | Terms to bias transcription/translation toward (product/brand names). Up to 100 terms of 200 chars each; repeat the field once per term in multipart |
Editing the Source Transcript
Once the project is
, read the transcript, then correct it before adding languages. Every edit bumps the project's
. Each segment has a stable
used to edit or delete it. (Enterprise workspaces only.)
python
# Read the source transcript
transcript = elevenlabs.dubbing.project.transcript.get(project_id)
# Correct a segment's text — send only the fields to change (text, speaker_id, start_s, end_s)
elevenlabs.dubbing.project.transcript.update_segment(
project_id,
segment_id=transcript.segments[0].id,
text="Welcome to our latest product demo.",
)
# Add a segment (reuse an existing speaker_id so it's dubbed with that speaker's voice)
added = elevenlabs.dubbing.project.transcript.create_segment(
project_id,
text="Thanks for watching.",
speaker_id=transcript.segments[0].speaker_id,
start_s=40.0,
end_s=42.0,
)
# Delete a segment
elevenlabs.dubbing.project.transcript.delete_segment(project_id, segment_id=added.segment.id)
Via REST:
GET /v1/dubbing/project/{project_id}/transcript
, then
PATCH .../transcript/segment/{segment_id}
with only the changed fields:
bash
curl -X PATCH "https://api.elevenlabs.io/v1/dubbing/project/{project_id}/transcript/segment/{segment_id}" \
-H "xi-api-key: $ELEVENLABS_API_KEY" -H "Content-Type: application/json" \
-d '{"text": "Welcome to our latest product demo."}'
Refining Translations and Regenerating
A language's transcript pairs each source segment with its
(
= not yet translated; segment ids match the source). Edit a single translation, then regenerate. (Enterprise workspaces only.)
python
# Read the language's translations
target = elevenlabs.dubbing.project.language.transcript.get(project_id, language_id)
# Refine a single translation (pass translation=None to clear it and mark for re-translation)
elevenlabs.dubbing.project.language.transcript.update_segment(
project_id,
language_id,
segment_id=target.segments[0].id,
translation="Bienvenido a nuestra última demostración de producto.",
)
# Regenerate the dub from the current transcript (charged like a generation)
elevenlabs.dubbing.project.language.transcript.regenerate(project_id, language_id)
Via REST:
PATCH /v1/dubbing/project/{project_id}/language/{language_id}/transcript/segment/{segment_id}
with
, then
POST .../language/{language_id}/transcript/regenerate
(returns
).
A translation edit affects only that language. After the edit, a
language becomes
— it keeps serving its previous output until you regenerate. Poll until
;
then equals
and
reflects the current transcript.
Dubbing into Multiple Languages
Add one language target per language — each generates independently. Track them all with
instead of polling one by one:
python
for lang in ["es", "fr", "de", "ja"]:
elevenlabs.dubbing.project.language.create(project_id, target_language=lang)
while True:
result = elevenlabs.dubbing.project.language.list(project_id)
if not any(l.status in ("queued", "processing") for l in result.languages):
break
time.sleep(5)
States
Project:
| Status | Meaning |
|---|
| Created; source fetch + preparation enqueued |
| Preparation (transcription) running |
| Source transcript available; add/generate languages. Projects stay — per-language progress lives on the languages |
| Preparation failed (e.g. source couldn't be fetched or decoded) |
Language:
| Status | Meaning |
|---|
| Waiting on the project becoming , or on a generation worker |
| The dub is being generated |
| Finished; populated with a signed download URL (valid ~1 hour — re-fetch for a fresh one) |
| Previously completed, but the transcript changed; keeps the last output until regenerated |
| Generation failed |
You can add a language before the project is
— it stays
and starts automatically once the project becomes
. Adding a language accepts optional
(defaults to the project's) and
(e.g.
, range 0–10, default 7 — controls how strongly dubbed speakers clone the source voices).
Error Handling
- 401: Invalid API key
- 409 Conflict on regenerate: The project isn't or the language isn't settled (e.g. already generating) — wait and retry
- Expired download URL: is signed and valid ~1 hour; re-fetch the language for a fresh URL
- Transcript editing / regeneration unavailable: These endpoints are enterprise-only — on other plans, create the project with a finalized source and add languages directly
References
- Installation Guide
- API Reference — every endpoint with full request/response schemas and SDK method names