Dubbing API Reference
Overview
The Dubbing API is an asynchronous dubbing service. Submit an audio or video URL, and receive a fully dubbed file in your target language.
- Pricing: $0.50 per minute of input media
- Max duration: 10 minutes
- Max file size: 500 MB
- Min duration: 1 second
- Supported languages: English (
en), Spanish (es), French (fr), German (de), Italian (it), Portuguese (pt), Russian (ru), Japanese (ja), Korean (ko), Chinese (zh)
Supported Formats
Input requirements
Video files
| Constraint | Accepted values |
|---|---|
| Containers | MP4, MOV, MKV, WebM, AVI, FLV, TS |
| Video codecs | H.264, H.265/HEVC, VP8, VP9, AV1, ProRes, MPEG-4 |
| Audio codecs | AAC, MP3, Opus, Vorbis, PCM, FLAC, AC-3 |
| Audio | Must contain at least one audio track with speech |
Audio files
| Constraint | Accepted values |
|---|---|
| Formats | WAV, MP3, FLAC, OGG, M4A, AAC, WMA |
| Audio | Must contain speech |
The input must contain audible speech in a supported language. Background music, sound effects, and multiple speakers are all handled automatically.
Output format
The output format depends on the input type:
- Video input — returned as MP4 (H.264 video + AAC audio at 192 kbps). Resolution and frame rate match the input.
- Audio-only input — returned as MP3 (192 kbps) or WAV depending on the input format.
The output URL is a presigned S3 link valid for 48 hours.
Providing a source URL
The source_url must be a publicly accessible HTTP(S) URL that returns the media file directly (not an HTML page). Good options:
- Pinch upload endpoint — Use the Upload Media endpoint to get a presigned S3 URL. This is the simplest approach.
- Your own S3 bucket — Generate a presigned GET URL with a long enough expiry (we recommend at least 1 hour):
import boto3
s3 = boto3.client("s3")
url = s3.generate_presigned_url(
"get_object",
Params={"Bucket": "my-bucket", "Key": "media/input.mp4"},
ExpiresIn=3600,
)
- Other cloud storage — Any direct download URL works (Google Cloud Storage signed URLs, Azure Blob SAS URLs, Cloudflare R2 presigned URLs, etc.)
- CDN / public URL — Direct links to media files (e.g.,
https://cdn.example.com/video.mp4)
Note: Private network URLs (localhost, internal IPs) are rejected for security. The URL must resolve to a public IP address.
Authentication
All API requests require authentication using a Bearer token in the Authorization header.
Authorization: Bearer <your-api-token>
Base URL:https://api.startpinch.com
Endpoints
Upload Media (optional)
Returns a presigned S3 PUT URL for direct upload. Use this if you need to upload a local file before creating a dubbing job.
POST /api/dubbing/upload-url Try it
Request Body
JSON schema Example request:
{
"filename": "string",
"content_type": "string"
}
{
"filename": "video.mp4",
"content_type": "video/mp4"
}
Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
filename |
string | Yes | Name of the file (e.g., "video.mp4") |
content_type |
string | Yes | MIME type of the file (e.g., "video/mp4") |
Response
200 JSON schema Example response:
{
"upload_url": "string",
"source_url": "string",
"upload_id": "string",
"max_file_size_bytes": 0,
"expires_in_sec": 0
}
Create Dubbing Job
Submit an audio or video file for dubbing. The file will be processed asynchronously.
POST /api/dubbing/jobs Try it
Request Body
JSON schema Example request:
{
"source_url": "string",
"target_lang": "string",
"source_lang": "string",
"reduce_accent": "boolean",
"translation_lag_time": "number",
"original_speech_volume": "number"
}
{
"source_url": "https://s3.amazonaws.com/pinch-dubbing/uploads/abc123",
"target_lang": "es",
"source_lang": "auto",
"reduce_accent": false,
"translation_lag_time": 1.0,
"original_speech_volume": 0.25
}
Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
source_url |
string | Yes | URL of the audio or video file to dub |
target_lang |
string | Yes | Target language code |
source_lang |
string | No | Source language code or "auto" |
reduce_accent |
boolean | No | Reduce source language accent in dubbed audio. When set to True, produces more natural target language pronunciation. |
translation_lag_time |
number | No | Seconds to delay translated speech |
original_speech_volume |
number | No | Mix original speech back in at this volume. |
Response (201 Created)
201 JSON schema Example response:
{
"job_id": "string",
"status": "string",
"source_lang": "string",
"target_lang": "string",
"created_at": "string",
"limits": {
"max_duration_sec": 0,
"max_file_size_bytes": 0
}
}
Get Job Status
Poll this endpoint to track the progress of a dubbing job.
GET /api/dubbing/jobs/{id} Try it
Response
200 JSON schema Example response:
{
"job_id": "string",
"status": "string",
"source_lang": "string",
"target_lang": "string",
"error": "string | null",
"progress": {},
"input_duration_sec": 0,
"cost_usd": 0,
"output_url": "string | null",
"output_expires_at": "string | null",
"created_at": "string",
"updated_at": "string"
}
List Jobs
Retrieve a paginated list of your dubbing jobs.
GET /api/dubbing/jobs?limit=20&offset=0 Try it
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit |
number | No | Number of jobs to return |
offset |
number | No | Offset for pagination |
Response
200 JSON schema Example response:
{
"jobs": [...],
"total": 5,
"limit": 20,
"offset": 0
}
Get Download URL (refresh)
Generates a fresh presigned download URL for a completed dubbing job.
GET /api/dubbing/jobs/{id}/result Try it
Response
200 JSON schema Example response:
{
"job_id": "string",
"download_url": "string",
"expires_at": "string"
}
Status Flow
Jobs progress through these statuses:
| Status | Description |
|---|---|
pending |
Job created, waiting to start |
downloading |
Downloading the source media |
processing |
Dubbing in progress |
uploading |
Uploading the dubbed output |
completed |
Done — output_url is available |
failed |
An error occurred — check error field |
Error Codes
400 401 402 429
// invalid_url
{
"error": {
"code": "invalid_url",
"message": "The provided source URL is not a valid URL"
}
}
{
"error": {
"code": "unauthorized",
"message": "Invalid or expired API token"
}
}
Job-Level Errors
| Error Code | Description |
|---|---|
video_too_long |
Input exceeds 10 minute limit |
video_too_short |
Input is less than 1 second |
video_too_large |
File size exceeds 500 MB limit |
unsupported_format |
Media format or codec is not supported |
download_failed |
Could not download the source media from the provided URL |
processing_failed |
An internal error occurred during dubbing |
Example: Full Workflow
const API_KEY = '<your-api-token>';
const BASE_URL = 'https://api.startpinch.com';
const headers = {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
};
// Step 1: Get a presigned upload URL
const uploadRes = await fetch(`${BASE_URL}/api/dubbing/upload-url`, {
method: 'POST',
headers,
body: JSON.stringify({
filename: 'my-video.mp4',
content_type: 'video/mp4'
})
});
const { upload_url, source_url } = await uploadRes.json();
// Step 2: Upload the file via PUT
const videoFile = /* your File or Blob */;
await fetch(upload_url, {
method: 'PUT',
headers: { 'Content-Type': 'video/mp4' },
body: videoFile
});
// Step 3: Create a dubbing job
const jobRes = await fetch(`${BASE_URL}/api/dubbing/jobs`, {
method: 'POST',
headers,
body: JSON.stringify({
source_url: source_url,
target_lang: 'es',
source_lang: 'auto'
})
});
const job = await jobRes.json();
console.log('Job created:', job.job_id);
// Step 4: Poll for job completion
let status = job.status;
while (status !== 'completed' && status !== 'failed') {
await new Promise(r => setTimeout(r, 5000)); // wait 5 seconds
const pollRes = await fetch(`${BASE_URL}/api/dubbing/jobs/${job.job_id}`, {
headers: { 'Authorization': `Bearer ${API_KEY}` }
});
const pollData = await pollRes.json();
status = pollData.status;
console.log(`Status: ${status}`, pollData.progress);
if (status === 'completed') {
// Step 5: Download the dubbed output
console.log('Download URL:', pollData.output_url);
const video = await fetch(pollData.output_url);
const blob = await video.blob();
// Save or use the blob as needed
}
if (status === 'failed') {
console.error('Job failed:', pollData.error);
}
}