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);
  }
}