This site does not allow itself to be embedded. You can still read it on the original site — the toolbar below keeps your place in the directory.
Your best video speaks one language. A coding agent extracts your vocabulary, the ElevenLabs Dubbing API transcribes, translates, and re-voices the file into 90+ languages, keeping each speaker, the timing, and the background audio intact. The complete one-hour build, from repo to a folder of market-ready dubs.
Every video you ship speaks one language, and the markets you have no video for are the ones you cannot reach at all. Your changelog video, your product demo, your 90-second explainer - they cost you real time to make, and they earn their keep once. The fix is not to record more, it is to dub what you have.
Dubbing used to mean a studio, voice actors, and a mixing session. It is now one asynchronous API call: the [ElevenLabs](https://dub.sh/dd-elevenlabs) Dubbing API transcribes your file, translates it, and re-voices it in the target language while preserving each speaker's identity, the timing, and the original background audio. The result is a track that sounds like your video, in a language it never had. This is the natural sequel to the [auto-narrated changelog video pipeline](/blog/auto-narrated-changelog-videos): that build made the English video, this build gives it an international audience.
There is one honest catch, and it is where the agent earns its place. Translation models mangle proper nouns: your product name becomes a literal translation, "Next.js" becomes a phonetic guess, your CLI name comes out as a word that means something else. ElevenLabs documents a `keyterms` parameter on the dubbing API specifically to bias transcription and translation toward product and brand names, and the fastest way to build a correct list is to have a coding agent read your repo and extract it. [OpenCode](https://opencode.ai/go?ref=M6HEHM4JM5) does that job headless in seconds, and the same `opencode run` pattern from our [cron automation guide](/blog/opencode-cron-automation-guide) runs it on a schedule if you dub every release.
Seven steps, under an hour, every step ending in something you can run. This is the full pipeline: agent-extracted vocabulary, project creation, one language target per market, and downloaded dubs ready to publish.
## Official Sources
| Resource | Description |
|----------|-------------|
| [ElevenLabs Dubbing overview](https://elevenlabs.io/docs/overview/capabilities/dubbing) | How dubbing works, languages, cloning strength, key facts |
| [Dubbing API quickstart](https://elevenlabs.io/docs/eleven-api/guides/cookbooks/dubbing) | The create-project, poll, and download flow with SDK examples |
| [Dub into multiple languages](https://elevenlabs.io/docs/eleven-api/guides/how-to/dubbing/multiple-languages) | Adding language targets to one project |
| [Manage dubbing projects](https://elevenlabs.io/docs/eleven-api/guides/how-to/dubbing/manage-projects) | Listing, refreshing expired URLs, deleting |
| [Create project API reference](https://elevenlabs.io/docs/api-reference/dubbing/create-project) | Every parameter, including `keyterms` |
| [ElevenLabs API pricing](https://elevenlabs.io/pricing/api) | Per-minute dubbing rates for v1 and v2 |
| [OpenCode Docs](https://opencode.ai/docs/) | Install and `opencode run` non-interactive mode |
## Step 1: Install OpenCode and prove headless mode works
Prerequisites: a machine with Node.js 18 or newer, an [ElevenLabs](https://dub.sh/dd-elevenlabs) account, an LLM provider key, and the video or audio file you want to dub (MP3, MP4, WAV, or MOV - the formats the API accepts per the pricing page).
Install OpenCode with the official one-liner from the [OpenCode docs](https://opencode.ai/docs/):
```bash
curl -fsSL https://opencode.ai/install | bash
```
Authenticate a provider (`opencode auth login`), then confirm the single capability the pipeline depends on - one task, one answer, no interactive session:
```bash
opencode run --model opencode/deepseek-v4-flash "print the current directory tree, two levels deep"
```
If that prints a tree and exits cleanly, the worker side is proven. On model choice: vocabulary extraction is a narrow, bounded task, which is exactly where a budget model earns its keep. The [DeepSeek V4 Flash 0731 release](/blog/deepseek-v4-flash-0731-opencode-guide) at $0.14/$0.28 per million tokens is the current sweet spot; a full extraction run costs fractions of a cent. The full CLI tour, if you are new to it, is in our [OpenCode developer guide](/blog/opencode-developer-guide-2026).
**What you have now:** a proven headless agent command that can read a repo and write a file.
## Step 2: Get an API key and install the SDK
In the ElevenLabs dashboard, create an API key (**Settings → API Keys**) and put it in a `.env` file - the SDK reads `ELEVENLABS_API_KEY` from the environment, exactly as the [quickstart](https://elevenlabs.io/docs/eleven-api/guides/cookbooks/dubbing) shows:
```bash
echo "ELEVENLABS_API_KEY=your-key-here" > .env
npm init -y
npm install @elevenlabs/elevenlabs-js dotenv tsx
```
`tsx` runs TypeScript directly, which keeps this whole build in one file. Prove the key works with a read-only call against a documented endpoint - list projects (empty is fine). Save this as `check.ts`:
```ts
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
import "dotenv/config";
const page = await new ElevenLabsClient().dubbing.project.list({ pageSize: 1 });
console.log("auth ok,", page.projects.length, "existing projects");
```
```bash
npx tsx check.ts
```
**What you have now:** an authenticated SDK that can talk to the dubbing API.
## Step 3: Have the agent extract your vocabulary
This is the step that decides whether the dub is watchable. The [create-project reference](https://elevenlabs.io/docs/api-reference/dubbing/create-project) documents `keyterms` as: "Key terms to bias transcription/translation toward (e.g. product or brand names)", up to 1000 terms, each at most 50 characters and 5 words, with the characters `<>{}[]` and backslash not allowed. Without them, the transcription hears "Clerk" and the translation renders a clerk; with them, the name survives every language.
Run this from your repo root:
```bash
opencode run --model opencode/deepseek-v4-flash \
"Read README.md, package.json, and the docs/ folder. Extract the vocabulary a dubbing AI must not mangle: product and brand names, library names, commands, and jargon. Write a JSON array of key terms. Rules: each term at most 50 characters and 5 words, no angle brackets, braces, brackets, or backslashes. Save it as keyterms.json."
```
Then review the output with your eyes - a 30-second read of a JSON array:
```bash
jq . keyterms.json
```
Expect 5 to 20 terms: your product name, your stack's libraries, the commands your narrator actually says on screen. If a term is missing, add it by hand before the next step; this file is the whole contract between your repo and the dub. The [audio briefs post](/blog/agent-audio-briefs-elevenlabs) covers the same shape from the TTS side - agent output becomes an API input, and the quality hinges on how you prompt the agent, not on which model runs it.
**What you have now:** a reviewed keyterms list that will keep your names intact in every language.
## Step 4: The dubbing pipeline, in one script
The API flow is short: create a project, wait for the source to be transcribed, add one language target per market, wait for each to finish, download. Save this as `dub.ts` - the section comments map to the next three steps:
```ts
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
import { readFile, writeFile } from "node:fs/promises";
import "dotenv/config";
const client = new ElevenLabsClient();
const SOURCE_URL = process.env.SOURCE_URL!; // public MP4/MP3 URL
const SOURCE_LANG = process.env.SOURCE_LANG ?? "en";
const TARGET_LANGS = (process.env.TARGET_LANGS ?? "es,fr,de,ja,pt-BR").split(",");
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
// Step 4: create the project
const keyterms = JSON.parse(await readFile("keyterms.json", "utf8"));
let project = await client.dubbing.project.create({
sourceUrl: SOURCE_URL,
sourceLanguage: SOURCE_LANG,
reference: "first dub run",
keyterms,
});
console.log("project:", project.projectId, project.status);
// Step 5: wait for the source to be transcribed
while (true) {
project = await client.dubbing.project.get(project.projectId);
if (project.status === "ready") break;
if (project.status === "failed") throw new Error("project failed");
console.log(`project ${project.status}...`);
await sleep(5000);
}
console.log("source transcript ready");
// Step 6: one language target per market
for (const lang of TARGET_LANGS) {
await client.dubbing.project.language.create(project.projectId, { targetLanguage: lang });
}
while (true) {
const list = await client.dubbing.project.language.list(project.projectId);
const pending = list.languages.filter((l) =>
l.status === "queued" || l.status === "processing");
if (pending.length === 0) break;
console.log(`${list.languages.length - pending.length}/${list.languages.length} languages done`);
await sleep(5000);
}
// Step 7: download every completed dub
const result = await client.dubbing.project.language.list(project.projectId);
for (const language of result.languages) {
if (language.status !== "completed") continue;
const res = await fetch(language.outputs!.losslessAudio!);
await writeFile(`dub_${language.targetLanguage}.flac`, Buffer.from(await res.arrayBuffer()));
console.log(`saved dub_${language.targetLanguage}.flac`);
}
```
Point `SOURCE_URL` at your video's public URL (your CDN, a release-page asset, your video host) and run it:
```bash
SOURCE_URL="https://your-cdn.example.com/demo.mp4" npx tsx dub.ts
```
If you would rather not host the file, the [API reference](https://elevenlabs.io/docs/api-reference/dubbing/create-project) also accepts a `file` upload as multipart form-data, up to 3 GB per source file. Either way the response is the same: a `project_id` and a status. **What you have now:** a dubbing project, its source queued for transcription.
## Step 5: Wait for the source transcript
A dubbing project is two-phase. The first phase prepares the source: the API transcribes your file and detects every speaker (up to 32 per file, per the [overview](https://elevenlabs.io/docs/overview/capabilities/dubbing)) while keeping the background audio out of the transcript. The project moves through `queued` and `preparing` to `processing`, and lands on `ready` once the transcript exists - or `failed`, with an `error` object whose `code` and `message` tell you why, and a `retryable` flag that says whether resubmitting could work.
The polling loop in Step 4 uses the documented [project endpoints](https://elevenlabs.io/docs/eleven-api/guides/how-to/dubbing/manage-projects) and checks exactly two terminal states: `ready` and `failed`. Nothing in this phase is interactive - you can run the script, walk away, and let it print its way to `ready`. A 90-second video typically prepares in a couple of minutes; a longer file takes proportionally longer.
**What you have now:** a project whose source transcript is ready - the dub foundation, reusable for any number of languages.
## Step 6: Add one language target per market
Each target language is an independent generation from that single transcript: you translate the source once and produce every dub from it. Language tags are BCP-47, so `es` and `pt-BR` are both valid; the v2 model supports 90+ languages with regional dialects like `es-MX`, `fr-CA`, `pt-BR`, and `zh-TW` (the [overview](https://elevenlabs.io/docs/overview/capabilities/dubbing) has the full table). Pass `en-GB` for the UK, `es-MX` for the Spanish that actually gets watched.
Targets generate in parallel and finish at different times, so the script polls the language list rather than checking one by one - each language moves `queued` → `processing` → `completed` (or `failed`). Two production notes from the docs:
- **Concurrency:** self-serve plans allow up to 3 concurrent dubbing jobs per workspace. If you hit the limit, the API returns a `too_many_concurrent_requests` error - wait for a running project to finish before starting another. Targets inside one project do not count against each other, so one project with five languages is the efficient shape.
- **Voice similarity:** `voice_settings.cloning_strength` (0 to 10, default 7) controls how closely the dub clones the original voices. Higher values keep the resemblance but can carry the original accent into the target language; lower values give a more natural delivery. Default 7 is right for most content, and per the overview, a higher setting is where accents leak through.
**What you have now:** a folder of `dub_ .flac` files appearing one by one as each language completes.
## Step 7: Download, mux for video, publish
The `outputs.lossless_audio` field is a signed URL that expires about an hour after it is issued, so the script downloads each file as soon as it completes rather than storing the link. If you come back later and a URL has expired, the [manage-projects guide](https://elevenlabs.io/docs/eleven-api/guides/how-to/dubbing/manage-projects) shows the fix: fetch the language again with `language.get` and download the fresh URL. Store the file, never the URL.
One detail worth knowing: the API returns the dubbed **audio track** - the lossless output is FLAC. If your source was a video, mux the track back over the original footage. One command, ffmpeg, video stream copied untouched:
```bash
ffmpeg -i demo.mp4 -i dub_es.flac -map 0:v -map 1:a -c:v copy -c:a aac -shortest dub_es.mp4
```
Then publish per market: the Spanish dub goes on the Spanish release page, the German one in the German changelog, and the captions from your English edit can be translated the same way when you have time. **What you have now:** a video that speaks your markets' languages, built in under an hour, with the source project kept on the account for adding more languages later.
### What it costs, honestly
Dubbing is billed per source audio minute, at two tiers on the [API pricing page](https://elevenlabs.io/pricing/api):
- **Dubbing v2** (end-to-end, 90+ languages): $2.20 per minute. A 90-second video is $3.30 per language.
- **Dubbing v1** (29 languages): $0.33 per minute with a watermark, $0.50 per minute without. A 90-second video is $0.75 per language, watermark-free.
Free-tier dubs are watermarked automatically on v2; paid-tier dubs are not. For a first test, run one language on the free tier and judge the voice similarity and timing before you spend. For a five-language release on v2, budget about $16.50 per 90-second video - cheaper than one human translation pass, and it re-voices as well. If you dub every release, the whole loop is cron-able: the [cron automation guide](/blog/opencode-cron-automation-guide) pattern wraps this exact script - vocabulary extraction plus dubbing - so a weekly release produces its multilingual dubs while you sleep.
## FAQ
### How much does it cost to dub a video with the ElevenLabs API?
Per source audio minute: $2.20 per minute on Dubbing v2, $0.33 per minute (watermarked) or $0.50 per minute (no watermark) on Dubbing v1. A 90-second video is $3.30 per language on v2, $0.75 per language on v1 without watermark. Free-tier dubs carry a watermark.
### What languages does ElevenLabs dubbing support?
90+ languages on Dubbing v2, using BCP-47 tags, including regional dialects such as `es-MX`, `pt-BR`, `fr-CA`, and `zh-TW`. Dubbing v1 covers 29 languages without dialect tags. The full table is in the [dubbing overview](https://elevenlabs.io/docs/overview/capabilities/dubbing).
### Why does the dub get my product name wrong?
Transcription and translation both mangle proper nouns. The fix is the `keyterms` parameter on project creation: up to 1000 terms that bias transcription and translation toward your product and brand names. The build above has OpenCode extract the list from your repo, and you review it before dubbing.
### Do I get a video file back?
The API returns the dubbed audio track as a lossless FLAC via a signed, expiring URL. For a video source, mux it back over the original footage with ffmpeg (`-map 0:v -map 1:a -c:v copy`) - the video stream is untouched.
### Can I dub without uploading the file?
Yes. Project creation accepts either a `file` (multipart upload, up to 3 GB per source file) or a `source_url` pointing at a public URL. The file stays on ElevenLabs only for the duration of the project; deleted projects are removed permanently via the API.
## Sources
| Source | URL |
|--------|-----|
| ElevenLabs Dubbing overview | https://elevenlabs.io/docs/overview/capabilities/dubbing |
| ElevenLabs Dubbing API quickstart | https://elevenlabs.io/docs/eleven-api/guides/cookbooks/dubbing |
| Dub into multiple languages | https://elevenlabs.io/docs/eleven-api/guides/how-to/dubbing/multiple-languages |
| Manage dubbing projects | https://elevenlabs.io/docs/eleven-api/guides/how-to/dubbing/manage-projects |
| Create project API reference | https://elevenlabs.io/docs/api-reference/dubbing/create-project |
| Get language target API reference | https://elevenlabs.io/docs/api-reference/dubbing/language-targets/get-language-target |
| ElevenLabs API pricing | https://elevenlabs.io/pricing/api |
| OpenCode Docs | https://opencode.ai/docs/ |
Some links to tools above are referral links - see our [affiliate disclosure](/affiliate-disclosure).
**Last updated:** August 12, 2026
## Continue Reading
- [Auto-Narrated Changelog Videos](/blog/auto-narrated-changelog-videos) - the English half of this pipeline: agent-written script, Screen Studio demo, Descript edit
- [Audio Briefs from Agent Runs](/blog/agent-audio-briefs-elevenlabs) - the same ElevenLabs account and key family, used for TTS summaries instead of dubbing
- [Text-to-Speech APIs for Developers in 2026](/blog/best-tts-apis-for-developers-2026) - where ElevenLabs sits on quality, latency, and price
- [Put an AI Agent on a Cron Job](/blog/opencode-cron-automation-guide) - schedule the dub pipeline so every release localizes itself
- [DeepSeek V4 Flash 0731 in OpenCode](/blog/deepseek-v4-flash-0731-opencode-guide) - the budget model doing the vocabulary extractionRead on developersdigest.tech ↗
Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.