Dub Project
curl --request POST \
--url https://api.voicecheap.ai/v1/projects/{projectId}/dub \
--header 'Content-Type: application/json' \
--header 'x-api-key: <x-api-key>' \
--data '
{
"targetLanguage": "<string>",
"translatedSrt": "<string>",
"webhookUrl": "<string>",
"keepBackgroundMusic": true,
"keepOriginalVoice": true,
"originalVoiceVolume": 123,
"voiceMode": "<string>",
"voiceId": "<string>",
"voiceIsolatorOption": "<string>",
"subtitles": true,
"subtitlesSource": "<string>",
"lipSyncMode": "<string>",
"activeSpeakerDetectionEnabled": true,
"voiceCloningSettings": {},
"translationTimeSkips": [
{}
]
}
'import requests
url = "https://api.voicecheap.ai/v1/projects/{projectId}/dub"
payload = {
"targetLanguage": "<string>",
"translatedSrt": "<string>",
"webhookUrl": "<string>",
"keepBackgroundMusic": True,
"keepOriginalVoice": True,
"originalVoiceVolume": 123,
"voiceMode": "<string>",
"voiceId": "<string>",
"voiceIsolatorOption": "<string>",
"subtitles": True,
"subtitlesSource": "<string>",
"lipSyncMode": "<string>",
"activeSpeakerDetectionEnabled": True,
"voiceCloningSettings": {},
"translationTimeSkips": [{}]
}
headers = {
"x-api-key": "<x-api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<x-api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
targetLanguage: '<string>',
translatedSrt: '<string>',
webhookUrl: '<string>',
keepBackgroundMusic: true,
keepOriginalVoice: true,
originalVoiceVolume: 123,
voiceMode: '<string>',
voiceId: '<string>',
voiceIsolatorOption: '<string>',
subtitles: true,
subtitlesSource: '<string>',
lipSyncMode: '<string>',
activeSpeakerDetectionEnabled: true,
voiceCloningSettings: {},
translationTimeSkips: [{}]
})
};
fetch('https://api.voicecheap.ai/v1/projects/{projectId}/dub', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.voicecheap.ai/v1/projects/{projectId}/dub",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'targetLanguage' => '<string>',
'translatedSrt' => '<string>',
'webhookUrl' => '<string>',
'keepBackgroundMusic' => true,
'keepOriginalVoice' => true,
'originalVoiceVolume' => 123,
'voiceMode' => '<string>',
'voiceId' => '<string>',
'voiceIsolatorOption' => '<string>',
'subtitles' => true,
'subtitlesSource' => '<string>',
'lipSyncMode' => '<string>',
'activeSpeakerDetectionEnabled' => true,
'voiceCloningSettings' => [
],
'translationTimeSkips' => [
[
]
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <x-api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.voicecheap.ai/v1/projects/{projectId}/dub"
payload := strings.NewReader("{\n \"targetLanguage\": \"<string>\",\n \"translatedSrt\": \"<string>\",\n \"webhookUrl\": \"<string>\",\n \"keepBackgroundMusic\": true,\n \"keepOriginalVoice\": true,\n \"originalVoiceVolume\": 123,\n \"voiceMode\": \"<string>\",\n \"voiceId\": \"<string>\",\n \"voiceIsolatorOption\": \"<string>\",\n \"subtitles\": true,\n \"subtitlesSource\": \"<string>\",\n \"lipSyncMode\": \"<string>\",\n \"activeSpeakerDetectionEnabled\": true,\n \"voiceCloningSettings\": {},\n \"translationTimeSkips\": [\n {}\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<x-api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.voicecheap.ai/v1/projects/{projectId}/dub")
.header("x-api-key", "<x-api-key>")
.header("Content-Type", "application/json")
.body("{\n \"targetLanguage\": \"<string>\",\n \"translatedSrt\": \"<string>\",\n \"webhookUrl\": \"<string>\",\n \"keepBackgroundMusic\": true,\n \"keepOriginalVoice\": true,\n \"originalVoiceVolume\": 123,\n \"voiceMode\": \"<string>\",\n \"voiceId\": \"<string>\",\n \"voiceIsolatorOption\": \"<string>\",\n \"subtitles\": true,\n \"subtitlesSource\": \"<string>\",\n \"lipSyncMode\": \"<string>\",\n \"activeSpeakerDetectionEnabled\": true,\n \"voiceCloningSettings\": {},\n \"translationTimeSkips\": [\n {}\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.voicecheap.ai/v1/projects/{projectId}/dub")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<x-api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"targetLanguage\": \"<string>\",\n \"translatedSrt\": \"<string>\",\n \"webhookUrl\": \"<string>\",\n \"keepBackgroundMusic\": true,\n \"keepOriginalVoice\": true,\n \"originalVoiceVolume\": 123,\n \"voiceMode\": \"<string>\",\n \"voiceId\": \"<string>\",\n \"voiceIsolatorOption\": \"<string>\",\n \"subtitles\": true,\n \"subtitlesSource\": \"<string>\",\n \"lipSyncMode\": \"<string>\",\n \"activeSpeakerDetectionEnabled\": true,\n \"voiceCloningSettings\": {},\n \"translationTimeSkips\": [\n {}\n ]\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"message": "<string>",
"projectId": "<string>",
"workId": "<string>",
"estimatedDuration": 123
}Translation and dubbing
Dub Project
Dub an already transcribed project from your own translated SRT
Dub Project
curl --request POST \
--url https://api.voicecheap.ai/v1/projects/{projectId}/dub \
--header 'Content-Type: application/json' \
--header 'x-api-key: <x-api-key>' \
--data '
{
"targetLanguage": "<string>",
"translatedSrt": "<string>",
"webhookUrl": "<string>",
"keepBackgroundMusic": true,
"keepOriginalVoice": true,
"originalVoiceVolume": 123,
"voiceMode": "<string>",
"voiceId": "<string>",
"voiceIsolatorOption": "<string>",
"subtitles": true,
"subtitlesSource": "<string>",
"lipSyncMode": "<string>",
"activeSpeakerDetectionEnabled": true,
"voiceCloningSettings": {},
"translationTimeSkips": [
{}
]
}
'import requests
url = "https://api.voicecheap.ai/v1/projects/{projectId}/dub"
payload = {
"targetLanguage": "<string>",
"translatedSrt": "<string>",
"webhookUrl": "<string>",
"keepBackgroundMusic": True,
"keepOriginalVoice": True,
"originalVoiceVolume": 123,
"voiceMode": "<string>",
"voiceId": "<string>",
"voiceIsolatorOption": "<string>",
"subtitles": True,
"subtitlesSource": "<string>",
"lipSyncMode": "<string>",
"activeSpeakerDetectionEnabled": True,
"voiceCloningSettings": {},
"translationTimeSkips": [{}]
}
headers = {
"x-api-key": "<x-api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<x-api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
targetLanguage: '<string>',
translatedSrt: '<string>',
webhookUrl: '<string>',
keepBackgroundMusic: true,
keepOriginalVoice: true,
originalVoiceVolume: 123,
voiceMode: '<string>',
voiceId: '<string>',
voiceIsolatorOption: '<string>',
subtitles: true,
subtitlesSource: '<string>',
lipSyncMode: '<string>',
activeSpeakerDetectionEnabled: true,
voiceCloningSettings: {},
translationTimeSkips: [{}]
})
};
fetch('https://api.voicecheap.ai/v1/projects/{projectId}/dub', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.voicecheap.ai/v1/projects/{projectId}/dub",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'targetLanguage' => '<string>',
'translatedSrt' => '<string>',
'webhookUrl' => '<string>',
'keepBackgroundMusic' => true,
'keepOriginalVoice' => true,
'originalVoiceVolume' => 123,
'voiceMode' => '<string>',
'voiceId' => '<string>',
'voiceIsolatorOption' => '<string>',
'subtitles' => true,
'subtitlesSource' => '<string>',
'lipSyncMode' => '<string>',
'activeSpeakerDetectionEnabled' => true,
'voiceCloningSettings' => [
],
'translationTimeSkips' => [
[
]
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <x-api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.voicecheap.ai/v1/projects/{projectId}/dub"
payload := strings.NewReader("{\n \"targetLanguage\": \"<string>\",\n \"translatedSrt\": \"<string>\",\n \"webhookUrl\": \"<string>\",\n \"keepBackgroundMusic\": true,\n \"keepOriginalVoice\": true,\n \"originalVoiceVolume\": 123,\n \"voiceMode\": \"<string>\",\n \"voiceId\": \"<string>\",\n \"voiceIsolatorOption\": \"<string>\",\n \"subtitles\": true,\n \"subtitlesSource\": \"<string>\",\n \"lipSyncMode\": \"<string>\",\n \"activeSpeakerDetectionEnabled\": true,\n \"voiceCloningSettings\": {},\n \"translationTimeSkips\": [\n {}\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<x-api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.voicecheap.ai/v1/projects/{projectId}/dub")
.header("x-api-key", "<x-api-key>")
.header("Content-Type", "application/json")
.body("{\n \"targetLanguage\": \"<string>\",\n \"translatedSrt\": \"<string>\",\n \"webhookUrl\": \"<string>\",\n \"keepBackgroundMusic\": true,\n \"keepOriginalVoice\": true,\n \"originalVoiceVolume\": 123,\n \"voiceMode\": \"<string>\",\n \"voiceId\": \"<string>\",\n \"voiceIsolatorOption\": \"<string>\",\n \"subtitles\": true,\n \"subtitlesSource\": \"<string>\",\n \"lipSyncMode\": \"<string>\",\n \"activeSpeakerDetectionEnabled\": true,\n \"voiceCloningSettings\": {},\n \"translationTimeSkips\": [\n {}\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.voicecheap.ai/v1/projects/{projectId}/dub")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<x-api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"targetLanguage\": \"<string>\",\n \"translatedSrt\": \"<string>\",\n \"webhookUrl\": \"<string>\",\n \"keepBackgroundMusic\": true,\n \"keepOriginalVoice\": true,\n \"originalVoiceVolume\": 123,\n \"voiceMode\": \"<string>\",\n \"voiceId\": \"<string>\",\n \"voiceIsolatorOption\": \"<string>\",\n \"subtitles\": true,\n \"subtitlesSource\": \"<string>\",\n \"lipSyncMode\": \"<string>\",\n \"activeSpeakerDetectionEnabled\": true,\n \"voiceCloningSettings\": {},\n \"translationTimeSkips\": [\n {}\n ]\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"message": "<string>",
"projectId": "<string>",
"workId": "<string>",
"estimatedDuration": 123
}Dub Project
Generate the voices and mix the output of an existing project from a translated SRT that you provide. VoiceCheap does not translate anything on this endpoint: no translation engine, no SmartSync rephrasing, no glossary. Each cue is voiced verbatim on the matching segment of the project transcript, then mixed with the original background, subtitles and optional lip-sync exactly like a regular translation. Use it when your translation is produced elsewhere (a human translator, your own tooling, an automation flow) and you only need VoiceCheap for voice generation. The run is asynchronous: poll Get Translation Status or listen to the webhooks.Prerequisites
- The project must exist and its transcription must be complete: create it with Create Project
(transcription only) or Start Translation, and wait for
transcriptionStatus: "success". - Your SRT must cover the project transcript: every transcript segment needs at least one cue, and every cue must fall on a segment. Export the transcript with Export Project Transcript and translate it cue by cue to keep the timecodes.
- The request is billed like a translation of the same media, with the same credit check. Lip-sync modes add their usual extra credits.
Concurrency Limit
Project creation, translation, and dubbing all check your account’s active-translation limit (at most 10, possibly lower). Only in-progress translation or dubbing runs occupy slots; transcription-only projects do not. If the limit is reached, the API returnsCONCURRENT_TRANSLATION_LIMIT_REACHED (HTTP 429). The applied limit is in details.limit.
A second run for the same project and target language is also rejected while one is in progress (TRANSLATION_ALREADY_IN_PROGRESS, HTTP 409).
Request
This endpoint accepts a JSON body (Content-Type: application/json). Boolean and numeric options are sent as JSON values,
not as strings.
Headers
string
required
Your VoiceCheap API key. Get one from app.voicecheap.ai/page-api.
Path Parameters
string
required
The project identifier returned by Create Project or Start Translation.
Body Parameters
string
required
The language of
translatedSrt, lowercase, from the same list as Start Translation. When the
project has no work for this language yet, one is created and the language is added to the project.string
required
The final target-language SRT content (cue number, timing line, text). Maximum 1,000,000 characters. Cues are matched to
the transcript segments by timestamps: a cue starting within 50 ms of a segment start is assigned to that segment,
otherwise to the segment it overlaps the most. Several cues on one segment are joined with a space. The timing, speaker
and order of every voiced segment always come from the project transcript; your timecodes are only used for matching.
string
An https endpoint that receives the webhook events for this project from now on, overriding
the endpoint configured on your account.Default: The endpoint already stored on the project, then the account webhook endpoint.
boolean
Keep the original background audio (music, ambience) under the generated voices.Default:
trueboolean
Keep the original speaker quietly underneath the generated voice.Default:
falsenumber
Original-voice volume from
1 through 70 when keepOriginalVoice is enabled.Default: 30string
Voice strategy:
cloning (clone the speakers of the project) or custom (use a catalog voice or your saved custom voice).Default: cloningstring
Required when
voiceMode is custom. Use an ID from dubbing_voices_list: a catalog voice or a custom voice saved by the authenticated account.
Team billing does not grant access to another member’s saved voices.string
Voice isolation mode when
keepBackgroundMusic is enabled: studio or realistic.Default: studioboolean
Burn subtitles into the output video. Requires a video project.Default:
falsestring
Subtitle text when
subtitles is enabled: translated (your SRT) or original (the source transcript).Default: translatedstring
Run lip-sync once the dub is ready:
standard, pro or studio. Plan, duration and credit rules are the same as on
Start Translation. Requires a video project.Default: omit the field to skip lip-sync.boolean
Enable active-speaker detection for the requested lip-sync run.Default:
falseobject
Voice cloning fine-tuning, as a JSON object with
stability and similarity between 0 and 1. Only
with voiceMode: "cloning". See Start Translation for guidance on each value.array
Source-time ranges to leave in the original language, as
[{ "startTime": 10.2, "endTime": 12.8 }]. Requires a video
project and keepBackgroundMusic: true, cannot be combined with keepOriginalVoice, and cannot overlap transcript segments.Response
boolean
required
Always
true for successful requests.string
required
A human-readable confirmation.
string
required
The project being dubbed.
string
required
The translation work used for
targetLanguage. It matches workId in the status, details and transcript endpoints.number
required
Rough processing estimate in minutes: five minutes of processing per started minute of source media.
Response
{
"success": true,
"message": "Dubbing started successfully from the provided translation. Use the status endpoint to track progress.",
"projectId": "project_123",
"workId": "work_456",
"estimatedDuration": 10
}
Tracking the run
The project switches to thetranslation workflow as soon as the dub starts, so Get Translation Status
follows the new run: status moves from processing to success (with translatedVideoUrl and translatedAudioUrl)
or failed (with error). The project.translation.completed and project.translation.failed webhooks are sent with the
same targetLanguage. In Get Project Details, the translated version reports
settings.dubbingMode: "dub-only" and settings.smartSync: false.
Alignment failures appear after the POST has been accepted. The status endpoint returns status: "failed" with error.code and error.message; the project.translation.failed webhook carries the same code and message. TRANSLATED_SRT_UNMATCHED_CUES names unmatched cue numbers in the message, and TRANSLATED_SRT_MISSING_SEGMENTS names segment indexes without a cue in the message. These asynchronous responses do not include structured details. Fix the SRT and call the endpoint again: the failed version stays in the project history and a new one is created.
Checking the pacing
Export the translated transcript as JSON with Export Project Transcript. Each segment carriesfinalSpeedFactor, the ratio between the generated speech and the segment window. Shorten the cues above roughly
1.3, then run the dub again with the revised SRT.
Example flow
A typical automation (for example an n8n or Make scenario) chains the endpoints as follows:POST /v1/projectswith the media,targetLanguageand an explicitoriginalLanguage(transcription only).- Poll
GET /v1/translate/{projectId}/statusuntiltranscriptionStatusissuccess, or wait for theproject.createdwebhook. POST /v1/projects/{projectId}/transcriptwith{ "source": "original", "outputFormat": "srt" }and translate the cues, keeping the numbering and timecodes.POST /v1/projects/{projectId}/dubwith the translated SRT.- Poll the status endpoint until
statusissuccess, or wait forproject.translation.completed. - Download
translatedVideoUrl(orlipSync.videoUrlwhen lip-sync was requested). DELETE /v1/translate/{projectId}once the outputs are stored on your side.
curl -X POST "https://api.voicecheap.ai/v1/projects/project_123/dub" \
-H "x-api-key: vc_your-api-key" \
-H "Content-Type: application/json" \
-d '{
"targetLanguage": "english",
"translatedSrt": "1\n00:00:00,000 --> 00:00:02,400\nWelcome to this lesson.\n\n2\n00:00:02,400 --> 00:00:05,100\nToday we talk about fractions.",
"keepBackgroundMusic": true,
"subtitles": true,
"subtitlesSource": "translated"
}'
interface DubProjectResponse {
success: boolean;
message: string;
projectId: string;
workId: string;
estimatedDuration: number;
}
const API_BASE_URL = 'https://api.voicecheap.ai/v1';
const headers = { 'x-api-key': 'vc_your-api-key', 'Content-Type': 'application/json' };
async function waitForStatus(projectId: string, isReady: (status: any) => boolean): Promise<any> {
while (true) {
const status = await fetch(`${API_BASE_URL}/translate/${projectId}/status`, { headers }).then((response) => response.json());
if (status.status === 'failed') throw new Error(`${status.error?.code}: ${status.error?.message}`);
if (isReady(status)) return status;
await new Promise((resolve) => setTimeout(resolve, 15_000));
}
}
async function dubExistingProject(projectId: string, translatedSrt: string): Promise<string> {
// 1. The project was created earlier with POST /v1/projects; wait for its transcript.
await waitForStatus(projectId, (status) => status.transcriptionStatus === 'success');
// 2. Send the translation you produced from the exported transcript.
const dubResponse = await fetch(`${API_BASE_URL}/projects/${projectId}/dub`, {
method: 'POST',
headers,
body: JSON.stringify({ targetLanguage: 'english', translatedSrt, keepBackgroundMusic: true }),
});
if (!dubResponse.ok) {
const error = await dubResponse.json();
throw new Error(`${error.code}: ${error.message}`);
}
const { workId } = (await dubResponse.json()) as DubProjectResponse;
console.log('Dubbing started on work', workId);
// 3. Wait for the dubbed output.
const completed = await waitForStatus(projectId, (status) => status.status === 'success');
return completed.translatedVideoUrl as string;
}
import time
import requests
API_BASE_URL = 'https://api.voicecheap.ai/v1'
HEADERS = {'x-api-key': 'vc_your-api-key'}
def wait_for_status(project_id, is_ready):
while True:
status = requests.get(f'{API_BASE_URL}/translate/{project_id}/status', headers=HEADERS).json()
if status['status'] == 'failed':
raise RuntimeError(f"{status['error']['code']}: {status['error']['message']}")
if is_ready(status):
return status
time.sleep(15)
def dub_existing_project(project_id, translated_srt):
wait_for_status(project_id, lambda status: status['transcriptionStatus'] == 'success')
response = requests.post(
f'{API_BASE_URL}/projects/{project_id}/dub',
headers=HEADERS,
json={'targetLanguage': 'english', 'translatedSrt': translated_srt, 'keepBackgroundMusic': True},
)
response.raise_for_status()
print('Dubbing started on work', response.json()['workId'])
completed = wait_for_status(project_id, lambda status: status['status'] == 'success')
return completed['translatedVideoUrl']
Errors
| Status | Code | Description |
|---|---|---|
| 400 | VALIDATION_ERROR | A body field is missing or invalid (details lists each field) |
| 400 | INVALID_TRANSLATED_SRT | translatedSrt does not contain valid SRT cues |
| 400 | INVALID_BOOLEAN_VALUE | A boolean option was not sent as a JSON boolean |
| 400 | INVALID_NUMBER_VALUE | A numeric option was not sent as a JSON number |
| 400 | DURATION_DETECTION_FAILED | The stored media duration of the project is unavailable |
| 400 | VOICE_ID_REQUIRED | Custom voice mode was selected without a voice ID |
| 400 | VOICE_ID_NOT_ALLOWED_WITH_CLONING | A voice ID was combined with voice cloning |
| 400 | VOICE_CLONING_SETTINGS_NOT_ALLOWED | Clone settings were combined with custom voice mode |
| 400 | TRANSLATION_TIME_SKIP_CONFLICT | Time skips conflict with another audio option |
| 400 | TRANSLATION_TIME_SKIP_REQUIRES_BACKGROUND | Time skips require background audio |
| 400 | TRANSLATION_TIME_SKIP_OUT_OF_RANGE | A time skip extends beyond the media duration |
| 400 | SUBTITLES_NOT_AVAILABLE_FOR_AUDIO | Burned-in subtitles were requested for an audio project |
| 400 | LIPSYNC_NOT_AVAILABLE_FOR_AUDIO | Lip sync was requested for an audio project |
| 400 | TRANSLATION_TIME_SKIP_NOT_AVAILABLE_FOR_AUDIO | Time skips were requested for an audio project |
| 400 | LIPSYNC_VIDEO_TOO_LONG | Lip-sync was requested for media longer than the supported duration |
| 401 | MISSING_API_KEY | API key is required |
| 401 | INVALID_API_KEY_FORMAT | API key must start with vc_ |
| 401 | INVALID_API_KEY | The provided API key is invalid |
| 403 | SUBSCRIPTION_REQUIRED | API access requires a Starter plan or higher |
| 403 | FORBIDDEN | You do not have permission to access this project |
| 403 | INSUFFICIENT_CREDITS | Not enough credits to dub this project |
| 403 | CUSTOM_VOICE_ACCESS_DENIED | The selected voice is neither a catalog voice nor a custom voice saved by the authenticated account. |
| 403 | LIPSYNC_MODE_ACCESS_DENIED | The premium lip-sync mode is unavailable on the current plan |
| 404 | PROJECT_NOT_FOUND | The project does not exist |
| 409 | DUB_ONLY_REQUIRES_TRANSCRIPTION | The project transcription is still processing or failed |
| 409 | TRANSLATION_ALREADY_IN_PROGRESS | A dubbing run is already in progress for this language on the project |
| 429 | RATE_LIMIT_EXCEEDED | Too many requests (limit: 10 requests per minute) |
| 429 | CONCURRENT_TRANSLATION_LIMIT_REACHED | Account concurrency limit reached; see details.limit |
| 500 | INTERNAL_ERROR | Unexpected server error |
TRANSLATED_SRT_UNMATCHED_CUES, TRANSLATED_SRT_MISSING_SEGMENTS) are
returned by the status endpoint and the failed webhook, not by this request.Was this page helpful?

