Skip to content
DashboardGet API Key

Generating Lip Sync

Learn how to generate AI lip sync videos using URLs or direct file uploads.

Chamelaion provides two endpoints for generating lip sync videos — one that accepts media URLs and another that accepts direct file uploads. Both create an asynchronous job and return a request_id for tracking.

Use POST /v1/lipsync/generate when your video and audio are hosted at publicly accessible URLs. This is the most common integration pattern.

{
"reference_id": "optional-your-id",
"disable_active_speaker_detection": false,
"inputs": [
{
"type": "video",
"url": "https://example.com/source-video.mp4"
},
{
"type": "audio",
"url": "https://example.com/target-audio.wav"
}
]
}
ParameterTypeRequiredDescription
inputsarrayYesExactly two items — one video and one audio input (order doesn’t matter)
inputs[].typestringYesEither "video" or "audio"
inputs[].urlstringYesPublicly accessible URL to the media file
reference_idstringNoYour own identifier for this request — useful for linking to your systems
disable_active_speaker_detectionbooleanNoSet to true to skip speaker detection and use max-face mode (default: false)
Terminal window
curl -X POST https://api.chamelaion.com/api/v1/lipsync/generate \
-H "Authorization: Bearer $CHAMELAION_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"reference_id": "dub-episode-42",
"inputs": [
{"type": "video", "url": "https://cdn.example.com/episodes/42.mp4"},
{"type": "audio", "url": "https://cdn.example.com/dubs/42-japanese.wav"}
]
}'
import requests
import os
response = requests.post(
"https://api.chamelaion.com/api/v1/lipsync/generate",
headers={
"Authorization": f"Bearer {os.environ['CHAMELAION_API_KEY']}",
"Content-Type": "application/json",
},
json={
"reference_id": "dub-episode-42",
"inputs": [
{"type": "video", "url": "https://cdn.example.com/episodes/42.mp4"},
{"type": "audio", "url": "https://cdn.example.com/dubs/42-japanese.wav"},
],
},
)
result = response.json()
print(f"Request ID: {result['request_id']}")
const response = await fetch(
"https://api.chamelaion.com/api/v1/lipsync/generate",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CHAMELAION_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
reference_id: "dub-episode-42",
inputs: [
{ type: "video", url: "https://cdn.example.com/episodes/42.mp4" },
{ type: "audio", url: "https://cdn.example.com/dubs/42-japanese.wav" },
],
}),
}
);
const result = await response.json();
console.log(`Request ID: ${result.request_id}`);
{
"status": "success",
"request_id": "6f82a2d8-a6d4-4e8a-a0fa-e8b09823a2d8"
}

Use POST /v1/lipsync/generate-with-media when you need to upload files directly rather than providing URLs. This endpoint accepts multipart/form-data.

FieldTypeRequiredDescription
videofileYesSource video file (MP4)
audiofileYesTarget audio file (WAV or MP3)
modelstringNoModel to use (currently "lipsync-2")
reference_idstringNoYour own identifier for this request
disable_active_speaker_detectionbooleanNoSet to true to skip speaker detection (default: false)
Terminal window
curl -X POST https://api.chamelaion.com/api/v1/lipsync/generate-with-media \
-H "Authorization: Bearer $CHAMELAION_API_KEY" \
-F "video=@/path/to/source-video.mp4" \
-F "audio=@/path/to/target-audio.wav" \
-F "reference_id=upload-demo-01"
import requests
import os
with open("source-video.mp4", "rb") as video, open("target-audio.wav", "rb") as audio:
response = requests.post(
"https://api.chamelaion.com/api/v1/lipsync/generate-with-media",
headers={"Authorization": f"Bearer {os.environ['CHAMELAION_API_KEY']}"},
files={
"video": ("video.mp4", video, "video/mp4"),
"audio": ("audio.wav", audio, "audio/wav"),
},
data={
"reference_id": "upload-demo-01",
},
)
result = response.json()
print(f"Request ID: {result['request_id']}")
import { createReadStream } from "fs";
const formData = new FormData();
formData.append("video", createReadStream("source-video.mp4"));
formData.append("audio", createReadStream("target-audio.wav"));
formData.append("reference_id", "upload-demo-01");
const response = await fetch(
"https://api.chamelaion.com/api/v1/lipsync/generate-with-media",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CHAMELAION_API_KEY}`,
},
body: formData,
}
);
const result = await response.json();
console.log(`Request ID: ${result.request_id}`);

The response format is identical to the URL-based endpoint.

By default, Chamelaion uses active speaker detection to identify which face in the video is speaking and only applies lip sync to that face. This is useful for:

  • Multi-person interview or conversation videos
  • Videos with background faces or audience members
  • News broadcasts with anchor and on-screen graphics

To disable this and use max-face mode (syncs the largest detected face), set disable_active_speaker_detection to true:

{
"disable_active_speaker_detection": true,
"inputs": [
{"type": "video", "url": "https://example.com/single-speaker.mp4"},
{"type": "audio", "url": "https://example.com/new-audio.wav"}
]
}

The reference_id field lets you tag requests with your own identifiers. This is useful for:

  • Linking Chamelaion requests to your internal database records
  • Retrieving requests by your own ID instead of the Chamelaion UUID
  • Batch tracking and reporting
{
"reference_id": "order-12345-japanese-dub",
"inputs": [
{"type": "video", "url": "https://example.com/video.mp4"},
{"type": "audio", "url": "https://example.com/audio.wav"}
]
}

You can then retrieve the request using:

Terminal window
curl https://api.chamelaion.com/api/v1/lipsync/requests/order-12345-japanese-dub \
-H "Authorization: Bearer $CHAMELAION_API_KEY"

Or filter your request list:

Terminal window
curl "https://api.chamelaion.com/api/v1/lipsync/requests?reference_id=order-12345-japanese-dub" \
-H "Authorization: Bearer $CHAMELAION_API_KEY"
Use URL-based generation when…
  • Your media is already hosted (S3, GCS, CDN, etc.)
  • You’re building a server-side pipeline
  • You want to avoid upload overhead
  • You’re processing many files in a batch workflow
Use file upload when…
  • You’re working with local files
  • Your media isn’t publicly accessible
  • You’re building a desktop or CLI tool
  • You need to process files before they’re stored permanently