Appearance
Dubbing
Dubs a video or recording into another language. Each speaker gets a matched library voice, and the background sound is kept.
A dub runs in phases:
output.phase | stageLabel |
|---|---|
extract | Taking the audio out of the video (videos only) |
normalize | Preparing the audio |
transcribe | Transcribing and finding the speakers |
profile | Choosing voices for the speakers |
translate | Translating |
speak | Recording the new voices |
assemble | Separating the background |
compose | Mixing the new voices with the background |
loudness | Balancing the volume |
encode | Finishing the audio |
merge | Putting the new audio on the video (videos only) |
During translate, speak and assemble, each read of the job does a bounded piece of work, so the dub only moves forward while you poll. stageLabel shows progress, such as Translating (20 of 64 lines) or Recording the new voices (41 of 64 lines).
Endpoints:
GET /v1/dubbing/optionsPOST /v1/dubbingGET /v1/dubbing/{id}POST /v1/dubbing/{id}/reviseGET /v1/dubbing/{id}/files/{name}GET /v1/dubbing/{id}/subtitles/{format}
Get dubbing options
GET /v1/dubbing/options
Response
200 OK
json
{
"languages": ["English", "Hindi", "Spanish", "French", "Portuguese", "German", "Arabic", "Russian", "Japanese", "Korean", "Chinese", "Chinese,Yue", "Italian", "Turkish", "Dutch", "Ukrainian", "Vietnamese", "Indonesian", "Thai", "Polish", "Romanian", "Greek", "Czech", "Finnish", "Bulgarian", "Danish", "Hebrew", "Malay", "Slovak", "Swedish", "Croatian", "Hungarian", "Norwegian", "Slovenian", "Catalan", "Nynorsk", "Afrikaans"],
"creditsPerMinute": 2000,
"maxSeconds": 600,
"maxUploadBytes": 2147483648,
"maxLanguages": 5,
"revision": { "creditsPerCharacter": 1, "remixCredits": 100, "lineMaxChars": 1000 }
}Credits
Free.
Example
bash
curl https://api.cinara.ai/v1/dubbing/options \
-H "Authorization: Bearer $CINARA_API_KEY"Create a dub
POST /v1/dubbing
Starts dubbing an uploaded file into one or more languages. Each language is its own generation, with its own progress, files and charge.
Needs a verified phone.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
key | string | yes | The storage key of a finished upload: an MP4 or MOV video, or an audio file. Up to 10 minutes. |
languages | string[] | one of | 1–5 target languages from the options list. Duplicates are removed. |
language | string | one of | One target language, used when languages isn't an array. |
durationSeconds | number | no | The file's length. Used only when the length can't be read from the file. |
fileName | string | no | Up to 200 characters. Defaults to the upload's file name. |
Response
201 Created
json
{
"generation": {
"id": "d4a9f2c6-8e1b-4d3a-b7f5-3c0e6a9d2b18",
"module": "dubbing",
"status": "running",
"input": {
"key": "workspaces/3f6b2a1c-8d4e-4b7a-9c2d-5e1f0a9b8c7d/uploads/0c7f3a9e-6d2b-4e8a-a1c5-4b9e7d2f0a36/kitchen-tour.mp4",
"fileName": "kitchen-tour.mp4",
"contentType": "video/mp4",
"size": 84213760,
"isVideo": true,
"estimatedSeconds": 180.04,
"measured": true,
"language": "Hindi"
},
"output": { "stage": "queued", "step": 1, "phase": "extract", "stageLabel": "Taking the audio out of the video" },
"credits": 6002,
"error": null,
"created_at": "2026-09-15T20:30:00.118320+00:00",
"completed_at": null
},
"generations": [
{ "id": "d4a9f2c6-8e1b-4d3a-b7f5-3c0e6a9d2b18", "module": "dubbing", "status": "running", "input": { "language": "Hindi", "…": "…" }, "output": { "phase": "extract", "…": "…" }, "credits": 6002, "error": null, "created_at": "2026-09-15T20:30:00.118320+00:00", "completed_at": null },
{ "id": "f81b3e5d-2c7a-4a9f-8e6b-0d4c1a7e3f52", "module": "dubbing", "status": "running", "input": { "language": "Spanish", "…": "…" }, "output": { "phase": "extract", "…": "…" }, "credits": 6002, "error": null, "created_at": "2026-09-15T20:30:01.402917+00:00", "completed_at": null }
],
"notStarted": [],
"credits": 30432
}generation is the first language's dub. generations lists one dub per language that started.
Before any dub starts, the balance is checked against every language together. If it can't cover them all, nothing starts and you get 402. If a language still can't start (for example the credits were spent elsewhere a moment before), the other dubs go ahead and that language is listed in notStarted with the reason:
json
"notStarted": [
{ "language": "Spanish", "reason": "It couldn't start. Its credits were refunded." }
]notStarted is empty when every language started.
Errors
| Status | error | Message |
|---|---|---|
| 400 | invalid_request | Upload a video or recording first |
| 400 | invalid_request | Choose a language to dub into |
| 400 | invalid_request | Dub into up to 5 languages at once |
| 404 | not_found | That upload isn't finished. Upload the file again. |
| 400 | invalid_request | Use an MP4 or MOV video, or an audio file |
| 400 | invalid_request | Upload a video or audio file |
| 400 | invalid_request | Dubbing takes files up to 10 minutes |
| 402 | insufficient_credits | This needs 12,004 credits (6,002 for each of 2 languages); the workspace has 9,000. |
| 403 | phone_unverified | Verify your phone number to start creating. |
| 502 | generation_failed | Dub couldn't start. Your credits were refunded. (no language started) |
Credits
Per language: max(100, ceil(minutes × 2000)).
- Reserved on the length read from the file (MP3, WAV, MP4, M4A or MOV), else
durationSeconds, else an estimate from file size (at least 10 seconds, at most 10 minutes). - Once the audio is prepared and the exact length is known, any extra is charged. If the balance is too low, the dub fails with
This file is 4 minutes and needs 8,000 credits.and is refunded. - Final: settled to the exact length.
- Refunded in full if the dub fails.
Example
bash
curl -X POST https://api.cinara.ai/v1/dubbing \
-H "Authorization: Bearer $CINARA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"key": "workspaces/3f6b2a1c-8d4e-4b7a-9c2d-5e1f0a9b8c7d/uploads/0c7f3a9e-6d2b-4e8a-a1c5-4b9e7d2f0a36/kitchen-tour.mp4", "languages": ["Hindi", "Spanish"]}'Get a dub
GET /v1/dubbing/{id}
Reads a dub, does the next piece of work, and returns it with its files once finished. Keep polling until status isn't running.
Response
200 OK
json
{
"generation": {
"id": "d4a9f2c6-8e1b-4d3a-b7f5-3c0e6a9d2b18",
"module": "dubbing",
"status": "succeeded",
"input": {
"key": "workspaces/3f6b2a1c-8d4e-4b7a-9c2d-5e1f0a9b8c7d/uploads/0c7f3a9e-6d2b-4e8a-a1c5-4b9e7d2f0a36/kitchen-tour.mp4",
"fileName": "kitchen-tour.mp4",
"contentType": "video/mp4",
"size": 84213760,
"isVideo": true,
"estimatedSeconds": 180.04,
"measured": true,
"language": "Hindi"
},
"output": {
"phase": "merge",
"step": 5,
"stageLabel": "Done",
"totalSeconds": 180.06,
"sourceLanguage": "eng",
"speakers": [
{ "id": "speaker_0", "gender": "female", "age": "adult", "voiceId": "haven", "voiceName": "Haven · Claire" }
],
"lines": [
{
"id": 0,
"speaker": "speaker_0",
"start": 0.52,
"end": 3.9,
"room": 4.4,
"original": "Welcome to my kitchen.",
"text": "मेरी रसोई में आपका स्वागत है।",
"adapted": false,
"take": { "seconds": 2.84, "speed": 1 },
"done": true,
"placedAt": 0.52
}
]
},
"credits": 6002,
"error": null,
"created_at": "2026-09-15T20:30:00.118320+00:00",
"completed_at": "2026-09-15T20:36:41.550276+00:00"
},
"files": [
{ "name": "video", "label": "Dubbed video (Hindi)", "contentType": "video/mp4", "size": 83901442, "url": "https://api.cinara.ai/media/1789548201/Kc8rV…/workspaces/…/dubbing/…/video.mp4" },
{ "name": "audio", "label": "Dubbed audio (Hindi)", "contentType": "audio/mpeg", "size": 4322880, "url": "https://api.cinara.ai/media/1789548201/Ps1dG…/workspaces/…/dubbing/…/audio.mp3" },
{ "name": "voices", "label": "New voices only", "contentType": "audio/mpeg", "size": 1840128, "url": "https://api.cinara.ai/media/1789548201/Hy6tN…/workspaces/…/dubbing/…/voices.mp3" }
],
"mediaUrl": "https://api.cinara.ai/media/1789548201/Fm2qB…/workspaces/…/uploads/…/kitchen-tour.mp4",
"credits": 30436
}| Field | Description |
|---|---|
output.totalSeconds | The source's exact length, once known. |
output.sourceLanguage | The detected source language. |
output.speakers | Speakers found, their detected gender and age, and the voice chosen for each. |
output.lines | Each line: start/end in the source, room (seconds it may use), original text, translated text, adapted (shortened or edited), the recorded take, and placedAt (where it starts in the dub; later than start when the line before ran over). |
files | Video source: video, audio, voices. Audio source: audio, voices. Empty until the dub succeeds. |
mediaUrl | A signed link (6 hours) to the uploaded source. |
Failures:
This file has no audio Cinara can read.Dubbing takes files up to 10 minutes; this one is 11.This file is 4 minutes and needs 8,000 credits.No speech was found to dub.Dub took too long and was stopped.This file couldn't be read. Try MP4, MOV, MP3, WAV or M4A.Dub failed.
Errors
| Status | error | Message |
|---|---|---|
| 404 | not_found | Not found |
Credits
Free to read.
Polling example
bash
while :; do
RES=$(curl -s "https://api.cinara.ai/v1/dubbing/$ID" -H "Authorization: Bearer $CINARA_API_KEY")
STATUS=$(echo "$RES" | jq -r .generation.status)
[ "$STATUS" != "running" ] && break
echo "$(echo "$RES" | jq -r .generation.output.stageLabel)"
sleep 3
done
echo "$RES" | jq '{status: .generation.status, error: .generation.error, files: [.files[].name]}'Revise a dub
POST /v1/dubbing/{id}/revise
Makes a new version of a finished dub with edited lines or new voices. Only the changed lines are said again; then the dub is re-mixed. The earlier version stays as it is.
Needs a verified phone.
Edited wording is kept as written: it's sped up to fit if needed, never shortened.
Request body
Send at least one change.
| Field | Type | Description |
|---|---|---|
lines | object[] | Lines to reword. |
lines[].id | integer | A line id from output.lines. |
lines[].text | string | The new wording, 1–1,000 characters. |
voices | object | Speaker id → new voice id, for example {"speaker_0": "kindred"}. Library voice ids or a workspace voice's providerVoiceId. Every line of that speaker is said again. |
Response
201 Created. A new dubbing generation that starts in the speak phase. Poll it with Get a dub.
json
{
"generation": {
"id": "a2c7e9f4-5b1d-4e3a-9c8f-6d0b2e4a7c15",
"module": "dubbing",
"status": "running",
"input": {
"key": "workspaces/3f6b2a1c-8d4e-4b7a-9c2d-5e1f0a9b8c7d/uploads/0c7f3a9e-6d2b-4e8a-a1c5-4b9e7d2f0a36/kitchen-tour.mp4",
"fileName": "kitchen-tour.mp4",
"contentType": "video/mp4",
"size": 84213760,
"isVideo": true,
"estimatedSeconds": 180.04,
"measured": true,
"language": "Hindi",
"revisionOf": "d4a9f2c6-8e1b-4d3a-b7f5-3c0e6a9d2b18",
"revision": 2
},
"output": { "phase": "speak", "stage": "processing", "step": 1, "stageLabel": "Recording the new voices", "speakers": [], "lines": [] },
"credits": 128,
"error": null,
"created_at": "2026-09-15T21:02:17.774902+00:00",
"completed_at": null
},
"credits": 30308
}output carries the parent's speakers and lines with your changes (shortened above).
Errors
| Status | error | Message |
|---|---|---|
| 404 | not_found | Dub not found |
| 400 | invalid_request | That line isn't in this dub |
| 400 | invalid_request | Line 12 needs some words |
| 400 | invalid_request | Keep each line under 1,000 characters |
| 400 | invalid_request | That speaker isn't in this dub |
| 400 | invalid_request | Choose a voice |
| 400 | invalid_request | Change a line or a voice first |
| 402 | insufficient_credits | This needs 128 credits |
| 403 | phone_unverified | Verify your phone number to start creating. |
Credits
max(1, ceil(characters said again × 1)) + 100: speech credits for every line that needs a new recording, plus 100 for the new mix. Not adjusted afterwards. Refunded if the revision fails.
Example
bash
curl -X POST "https://api.cinara.ai/v1/dubbing/d4a9f2c6-8e1b-4d3a-b7f5-3c0e6a9d2b18/revise" \
-H "Authorization: Bearer $CINARA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"lines": [{"id": 0, "text": "मेरी रसोई में स्वागत है!"}], "voices": {"speaker_0": "kindred"}}'Download a dub file
GET /v1/dubbing/{id}/files/{name}
Downloads one finished file (video, audio or voices) as an attachment.
Response
200 OK with the file and Content-Disposition: attachment; filename="kitchen-tour-video.mp4".
Errors
| Status | error | Message |
|---|---|---|
| 404 | not_found | File not found |
Credits
Free.
Example
bash
curl -L "https://api.cinara.ai/v1/dubbing/d4a9f2c6-8e1b-4d3a-b7f5-3c0e6a9d2b18/files/video" \
-H "Authorization: Bearer $CINARA_API_KEY" -o kitchen-tour-hindi.mp4Download dub subtitles
GET /v1/dubbing/{id}/subtitles/{format}
Downloads subtitles of the dubbed lines, timed to where each line sits in the dub.
Path parameters
| Name | Description |
|---|---|
format | srt or vtt. |
Response
200 OK with Content-Type: application/x-subrip; charset=utf-8 (SRT) or text/vtt; charset=utf-8 (VTT), and Content-Disposition: attachment; filename="kitchen-tour-hindi.srt". Speaker names aren't shown.
Errors
| Status | error | Message |
|---|---|---|
| 404 | not_found | Subtitles not found (unknown dub, not finished, or another format) |
Credits
Free.
Example
bash
curl "https://api.cinara.ai/v1/dubbing/d4a9f2c6-8e1b-4d3a-b7f5-3c0e6a9d2b18/subtitles/vtt" \
-H "Authorization: Bearer $CINARA_API_KEY" -o kitchen-tour-hindi.vtt