
TL;DR
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 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: 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 does that job headless in seconds, and the same opencode run pattern from our 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.
| Resource | Description |
|---|---|
| ElevenLabs Dubbing overview | How dubbing works, languages, cloning strength, key facts |
| Dubbing API quickstart | The create-project, poll, and download flow with SDK examples |
| Dub into multiple languages | Adding language targets to one project |
| Manage dubbing projects | Listing, refreshing expired URLs, deleting |
| Create project API reference | Every parameter, including keyterms |
| ElevenLabs API pricing | Per-minute dubbing rates for v1 and v2 |
| OpenCode Docs | Install and opencode run non-interactive mode |
Prerequisites: a machine with Node.js 18 or newer, an 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:
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:
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 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.
What you have now: a proven headless agent command that can read a repo and write a file.
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 shows:
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:
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");
npx tsx check.ts
What you have now: an authenticated SDK that can talk to the dubbing API.
This is the step that decides whether the dub is watchable. The create-project reference 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:
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:
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 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.
From the archive
Aug 12, 2026 • 7 min read
Aug 12, 2026 • 7 min read
Aug 12, 2026 • 7 min read
Aug 12, 2026 • 11 min read
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:
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:
SOURCE_URL="https://your-cdn.example.com/demo.mp4" npx tsx dub.ts
If you would rather not host the file, the API reference 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.
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) 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 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.
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 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:
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_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_<lang>.flac files appearing one by one as each language completes.
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 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:
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.
Dubbing is billed per source audio minute, at two tiers on the API pricing page:
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 pattern wraps this exact script - vocabulary extraction plus dubbing - so a weekly release produces its multilingual dubs while you sleep.
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.
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.
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.
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.
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.
| 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.
Last updated: August 12, 2026
Read next
Release notes nobody reads are a content problem with a mechanical fix: have a coding agent write the narration script from real git history, record the demo with Screen Studio, and let Descript narrate and edit it. A complete one-hour build.
8 min readThe agent finishes, the summary scrolls past, and you will read it later. Build the fix: a coding agent that ends every run with a plain-language summary, piped into ElevenLabs text-to-speech and out as an MP3 you can listen to on the way to work. The complete one-hour build.
9 min readA fair, sourced comparison of the TTS APIs developers reach for in 2026: OpenAI, ElevenLabs, xAI Grok, and Cartesia. Quality vs latency vs price, streaming, voice cloning policies, and whether to route through an AI gateway or go direct.
8 min readTechnical content at the intersection of AI and development. Building with AI agents, Claude Code, and modern dev tools - then showing you exactly how it works.
A hosted infinite canvas your headless AI agents drive over MCP. Any MCP-speaking agent - Claude Code, Codex, Cursor, or...
View ToolReactive backend - database, server functions, real-time sync, cron jobs, file storage. All TypeScript. This site's ba...
View ToolVercel's high-performance monorepo build system. Remote caching, task pipelines, and incremental builds. Drop into any p...
View ToolScore every coding agent on your own tasks. Catch regressions in CI.
View AppReplay every MCP tool call to find why your agent went sideways.
View AppPlan and track the short-form clipping pipeline from source video to publish queue.
View AppWhat MCP servers are, how they work, and how to build your own in 5 minutes.
AI AgentsStep-by-step guide to building an MCP server in TypeScript - from project setup to tool definitions, resource handling, testing, and deployment.
AI AgentsInstall the dd CLI and scaffold your first AI-powered app in under a minute.
Getting Started
Creating an AI-Enhanced Podcast Web App: Comprehensive Tutorial Repo: https://github.com/developersdigest/llm-podcast-engine You can obtain these API keys from the following sources: ...

In this video, I walk through my first experience with Runway ML's new Gen-3 Alpha video generation model. I opted for the $15/month plan and show the process from a first-time user's perspective....

Release notes nobody reads are a content problem with a mechanical fix: have a coding agent write the narration script f...

The agent finishes, the summary scrolls past, and you will read it later. Build the fix: a coding agent that ends every...

A fair, sourced comparison of the TTS APIs developers reach for in 2026: OpenAI, ElevenLabs, xAI Grok, and Cartesia. Qua...

An agent CLI plus a cron schedule turns recurring dev chores into background work: dependency bumps, doc freshness check...

DeepSeek shipped the official V4 Flash release on July 31, 2026. The re-post-trained 0731 build beats V4-Pro-Preview on...

Your coding agent can write the code. With Railway's official MCP server it can ship it too: create the project, deploy...

New tutorials, open-source projects, and deep dives on coding agents - delivered weekly.