Guides
How to Dub a Video Into Multiple Languages with an API
- Written by
- Sonilo Team
- Published

Dub one finished video into several languages with one explicit async request: validate the source, send the exact Sonilo language-code list, preserve the returned task ID, poll the canonical task endpoint, and accept the job only when every requested language has one HTTPS video URL.
By Sonilo Team · Facts verified August 23, 2026
Disclosure: Sonilo publishes this guide and operates the API used as the concrete example. The `dubbing-batch-v1` client, list-price estimator, and output validator were tested with deterministic fixtures. The tests did not upload customer media, call a production account, judge translation or voice quality, measure lip synchronization, or verify provider latency.
Use one explicit multi-language job
The current Sonilo Dubbing API reference defines `POST /v1/dubbing` as one asynchronous request that translates and re-voices the existing speech into one or more requested languages. It returns one dubbed MP4 per language. It does not generate music or sound effects.
Send the complete target set in one `languages` field instead of opening one POST per language. The endpoint meters the video duration across the number of target languages, and the successful task returns an `outputs` object keyed by the exact requested codes.
| Workflow stage | Application-owned record | Acceptance condition |
|---|---|---|
| Preflight | Source identity, duration, target codes, ducking choice, list-price estimate | One eligible video and a non-empty, duplicate-free target set |
| Submission | Application request ID and provider task ID | One `202` response with `status: processing` |
| Polling | Task ID, last state, next read time, deadline | Terminal success before the local deadline, or a recoverable saved task ID |
| Delivery | Requested code-to-URL map and review status | Exactly one HTTPS MP4 URL for every requested code |
This page owns that multi-language delivery contract. It does not replace a localization brief, translated-script review, voice consent, legal review, or a native speaker’s approval.
Pass language codes explicitly
Do not omit `languages`. The current endpoint reference says omission defaults to `['zh_cn', 'es', 'fr']`, which creates and bills three target outputs. If the release needs only French, pass `['fr']`.
The currently documented codes are `en`, `zh_cn`, `ja`, `ko`, `pt`, `pt_br`, `es`, `es_419`, `de`, `fr`, `it`, `ru`, and `th`. These are Sonilo request codes. Preserve them exactly: `pt_br` is Brazilian Portuguese and `es_419` is Latin American Spanish, while `pt` and `es` are separate unqualified options.
The sample rejects `en-US`, `pt-BR`, mixed case, duplicates, and unknown values instead of guessing a conversion. A silent rewrite can route a paid job to the wrong locale. Map product locale IDs to provider codes in an explicit, versioned table that a localization owner can review.
Estimate the paid scope before submission
The current Sonilo pricing page lists dubbing at `$0.0985` per second, or `$5.91` per minute per language. The endpoint reference says the billable quantity is video duration multiplied by language count, with a ten-second floor applied after that multiplication. Dubbing has no free-trial run.
For the standard list-price estimate checked on August 23, 2026, use:
`estimated cost = max(ceil(video seconds) × language count, 10) × $0.0985`
| Video | Targets | Metered seconds | Standard list-price estimate |
|---|---|---|---|
| 5 seconds | 1 | 10 | $0.9850 |
| 5 seconds | 3 | 15 | $1.4775 |
| 60 seconds | 2 | 120 | $11.8200 |
| 120 seconds | 3 | 360 | $35.4600 |
The table is arithmetic, not an invoice. Recheck the live rate, account terms, discounts, taxes, and available balance before a paid run. Keep this estimator separate from the music-generation cost estimator, because endpoint prices, trial rules, and billing floors are not interchangeable.
The tested estimator uses integer microdollars rather than binary floating-point multiplication. It rounds a measured duration up to the next second for a conservative preflight and rejects video durations above the documented 300-second endpoint limit.
Validate one source before the paid POST
`POST https://api.sonilo.com/v1/dubbing` accepts `multipart/form-data`. Provide exactly one `video` upload or one `video_url`. A remote URL must use HTTPS; plain HTTP is rejected. The current Dubbing reference limits source videos to 300 seconds.
For a local file, apply the video-file preflight workflow before submission. Confirm the real byte size, container, video stream, and finite duration; renaming an audio file with an MP4 extension does not create a video track.
For a remote object, apply the presigned source-URL lifetime gate. The sample requires an absolute HTTPS URL without embedded credentials, but that check does not prove that the host is public, the object is safe, the signature will stay valid, or the provider can fetch it later.
Keep `SONILO_API_KEY` in a trusted server process. The Sonilo API introduction requires bearer authentication, and the Next.js server-boundary guide shows why a provider key does not belong in browser JavaScript.
Decide whether the original bed should duck
The Dubbing endpoint keeps the source background music and effects bed in the output. With `ducking=false`, the bed is left unchanged. With `ducking=true`, Sonilo lowers that bed while the dubbed voice speaks; the current endpoint reference says this option is free.
Ducking is a mix choice, not a translation-quality control. Use it when the existing music or effects compete with the replacement speech, then review every target output. Do not assume one setting will work equally well for languages whose sentence lengths, cadence, and pauses differ.
For a separate speech-and-music mix outside dubbing, use the dedicated audio-ducking API guide. Do not send a dubbing task merely to solve a mix problem.
Submit once and save the task ID
The endpoint returns `202 Accepted` with `task_id` and `status: processing`. Save the task ID before a queue worker acknowledges its lease or an HTTP handler promises that the job can be recovered.
The submission boundary in `dubbing-batch-v1` is intentionally one attempt:
- `const form = new FormData();`
- `form.set('video_url', input.videoUrl);`
- `form.set('languages', JSON.stringify(input.languages));`
- `form.set('ducking', String(input.ducking));`
- ``
- `const response = await fetch(`
- ` 'https://api.sonilo.com/v1/dubbing',`
- ` {`
- ` method: 'POST',`
- ` headers: {Authorization: \`Bearer ${apiKey}\`},`
- ` body: form,`
- ` signal: AbortSignal.timeout(30_000),`
- ` },`
- `);`
Node.js v22 supplies stable global `fetch` and `FormData`; its global API documentation also defines `AbortSignal.timeout()`. The local timeout limits how long the client waits for a response. It does not prove whether the paid POST reached the provider.
RFC 9110 says a client should not automatically retry a non-idempotent request unless it knows the request semantics are idempotent or knows the original request was not applied. A transport failure around this POST becomes `submission_outcome_unknown` in the sample, with no automatic replay.
Use the duplicate-job prevention contract to stop your own application from opening obvious duplicates. An application request key cannot manufacture a provider task ID if the `202` response is lost, so uncertain submissions still need an operations path.
Poll the canonical task endpoint
After a usable `202`, poll `GET https://api.sonilo.com/v1/tasks/{task_id}`. The current Get Task reference recommends a two-to-three-second interval and says to download output only after success. The OpenAPI contract describes the Dubbing result as an `outputs` map from language code to dubbed video URL.
The sample waits 2.5 seconds between ordinary reads, retries only task GETs, and stops after a 15-minute local deadline. A `429` read honors a bounded `Retry-After`; `502`, `503`, and `504` reads receive bounded retries. A `401`, `402`, `403`, `404`, or `422` is surfaced without a waiting loop.
| Failure | Local action | Why |
|---|---|---|
| `401` | Repair the server credential | Waiting cannot make an invalid key valid |
| `402` | Resolve the balance blocker | Retrying does not add credits |
| `403` | Check account or service access | The key was understood but lacks permission |
| `422` | Fix the source, duration, or language codes | The unchanged request is not eligible |
| `429` on submission | Record the rejection and retry guidance; do not hide it inside a blind POST loop | Paid POST retry policy belongs beside application deduplication |
| `429` on task GET | Honor bounded `Retry-After` | A status read is safe to repeat |
| Transport failure after POST | Mark the outcome uncertain | The client cannot prove whether work was accepted |
| Local polling deadline | Preserve the task ID | A client deadline is not a provider failure |
The broader async polling guide covers durable leases and restart recovery. If many jobs share an account, coordinate status reads with the rate-limit admission workflow.
Require a complete output map
A terminal success is necessary but not sufficient for delivery. `dubbing-batch-v1` accepts the result only when all five checks pass:
- The returned task ID matches the accepted task.
- The status is a documented success state.
- `outputs` is an object, not an array or a single `output_url`.
- Its keys exactly match the requested language codes, with no missing or unexpected entries.
- Every value is an absolute HTTPS URL without embedded credentials.
For a request with `['pt_br', 'es_419', 'th']`, the normalized record looks like this:
- `{`
- ` "version": "dubbing-batch-v1",`
- ` "taskId": "task-123",`
- ` "outputs": {`
- ` "pt_br": "https://cdn.example/pt-br.mp4",`
- ` "es_419": "https://cdn.example/es-419.mp4",`
- ` "th": "https://cdn.example/th.mp4"`
- ` }`
- `}`
Do not silently accept a two-file result for a three-language order, alias one regional code to another, or treat the first URL as the whole job. Preserve the provider payload in an access-controlled diagnostic record and stop the delivery step.
Copy accepted files into application-owned storage before temporary URLs expire, using the output durability workflow. Store the source asset ID, target code, provider task ID, durable object ID, checksum, byte count, reviewer, and release decision as separate fields.
Review every language before release
Structural validation does not establish translation accuracy, pronunciation, voice suitability, timing, or cultural fit. A release review should include a qualified speaker for every target locale and the actual final video, not only a transcript.
Check at least:
- names, brands, numbers, dates, prices, and calls to action;
- omitted, added, or mistranslated meaning;
- pronunciation, speaker changes, and audible artifacts;
- speech timing against cuts, on-screen actions, and captions;
- whether the original bed masks words or ducking is too aggressive;
- captions, on-screen text, metadata, thumbnails, landing pages, and legal copy for the same locale;
- consent and contractual limits for any recognizable or cloned voice workflow outside this endpoint.
Do not claim that an API response made a campaign localized. Dubbing is one media layer. The rest of the release surface still needs translation and locale QA.
Keep raw source URLs, scripts, transcripts, and provider payloads out of general logs. Use the safe API logging contract, the usage dashboard workflow for aggregate account observation, and the graceful worker shutdown pattern when accepted task IDs must survive a deploy.
Run the tested Node.js contract
Save the tested module as `dubbing-batch.mjs` and its tests as `dubbing-batch.test.mjs` in a trusted server environment running Node.js v22. Then:
- Run `node --test dubbing-batch.test.mjs`.
- Set `SONILO_API_KEY` and an HTTPS `SONILO_VIDEO_URL` in the server environment.
- Set `SONILO_DUBBING_LANGUAGES` to an explicit JSON array such as `["pt_br","es_419","th"]`.
- Optionally set `SONILO_DUCKING=true`.
- Run `node dubbing-batch.mjs`.
The program prints only the normalized task ID and output map. It does not print the bearer key, request headers, source query string, or full provider response.
Eighteen deterministic tests ran with the stable Node.js v22 test runner on August 23, 2026:
- Preserve supported Sonilo language codes in request order.
- Reject generic or unsupported tags instead of rewriting them.
- Reject duplicate target languages.
- Require an HTTPS source without embedded credentials.
- Apply the ten-second billing floor after language multiplication.
- Submit once with bearer authentication and explicit multipart fields.
- Require the key before fetch.
- Avoid replaying an ambiguous submission failure.
- Surface a rejected POST and `Retry-After` without resending.
- Poll processing to success and preserve requested output order.
- Retry a task GET after `Retry-After`.
- Avoid retrying a `401` task read.
- Reject a missing requested output.
- Reject an unexpected output.
- Reject insecure or credential-bearing output URLs.
- Preserve the task ID when the local deadline expires.
- Surface a failed provider task with its task ID.
- Parse numeric and HTTP-date `Retry-After` values.
These tests prove local guards, price arithmetic, secret placement, the single-submit boundary, bounded GET behavior, and output-map checks against controlled fixtures. They do not prove production availability, invoice accuracy, latency, translation quality, speaker similarity, lip synchronization, source safety, tenant authorization, or commercial suitability.
When not to use Sonilo dubbing
Do not use this endpoint for a language outside the current supported-code list. Choose a workflow that supports the required locale rather than mapping it to a merely similar language.
Do not use automated dubbing as the final authority for regulated instructions, medical or legal statements, crisis communications, safety training, or a brand-critical performance without qualified human translation and review.
Do not use it when a contract requires a named actor, a directed studio performance, or a specific voice-consent chain that the project cannot document.
Do not use one request for a source over the current 300-second limit. Segmenting a longer program may create continuity and timing problems; compare that approach with a long-form localization service before committing.
Do not treat `ducking=true` as automatic mix approval, or a complete URL map as proof that every video is release-ready.
Ship a language-complete, reviewable batch
The smallest defensible workflow is: name the exact target codes, estimate the paid scope, validate one source, submit once, save the task ID, poll the documented path, require one HTTPS output per target, copy each file to durable storage, and obtain locale-specific release approval.
Review the Dubbing endpoint and current pricing before a paid run. For a production localization workflow with account-specific volume, review, or support requirements, talk to Sonilo.


