lipsyn.cc API
Generate AI lip-sync videos programmatically. Asynchronous, minute-based pricing, webhook or polling delivery.
Contents
Authentication
All API requests require a valid API key sent via the Authorization header:
Authorization: Bearer lip_your_api_key_here
Create and manage API keys in your account dashboard. Each key shows its usage (minutes processed) and last-used timestamp. You can create multiple keys for different applications, and disable or delete them individually.
Minute-Based Pricing
Each API call consumes prepaid minutes from your account. Minutes are deducted based on the output video length and resolution:
| Resolution | Multiplier |
|---|---|
| Below 720p | 1× video length |
| 720p and higher | 2× video length |
Purchase minute bundles from your account dashboard. Minutes never expire.
Create a Lip-Sync Video
POST https://www.lipsyn.cc/api/lipsync
JSON Request (URL-based media)
{
"audio": {
"uri": "https://example.com/audio.mp3"
// OR use TTS:
// "voice": "en-US-JennyNeural",
// "text": "Hello, this is a lip-sync test."
},
"video": {
"uri": "https://example.com/video.mp4"
// OR use an image:
// "imageUri": "https://example.com/photo.jpg"
// OR use a preset:
// "preset": "https://lipsyn.cc/presets/talking_avatar.mp4"
},
"width": 1280, // optional requested output width in pixels
"height": 720, // optional requested output height in pixels
"webhookUrl": "https://your-app.com/webhook",
"language": "en-US"
}Optional width and height request output dimensions in pixels. For example, use 1280 × 720 for landscape or 720 × 1280 for portrait. The API accepts dimensions rather than named resolution presets. If omitted, it uses the source dimensions and processing defaults. For multipart requests, pass width and height as numeric form fields.
Multipart Form Data (file uploads)
POST /api/lipsync Content-Type: multipart/form-data Authorization: Bearer lip_your_api_key_here Fields: audio - Audio file (MP3, MP4, M4A, WAV, FLAC, AAC, OGG, WEBM, MPG, MPEG) video - Video file (MP4, M4V, WebM, MPG, MPEG) OR image - Image file (JPG, JPEG, PNG, GIF, WEBP) presetLink - Preset video URL (instead of video/image) voice - TTS voice name (instead of audio file) text - TTS text (required with voice) language - Language code (default: en-US) width - Optional output width in pixels height - Optional output height in pixels webhookUrl - Optional webhook URL
Response (202 Accepted)
{
"success": true,
"videoId": "550e8400-e29b-41d4-a716-446655440000",
"message": "Video queued for processing. Poll GET /api/video/:videoId to check status.",
"pollingUrl": "https://www.lipsyn.cc/api/video/550e8400-e29b-41d4-a716-446655440000"
}Polling for Results
Poll the video status endpoint to check when processing is complete:
GET https://www.lipsyn.cc/api/video/:videoId
Authorization: Bearer lip_your_api_key_here
Response
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": 3, // numeric status code (see below)
"videoUrl": "https://...", // preview or full video URL
"fullUrl": "https://...", // full video URL (when status is 5)
"previewUrl": "https://...", // preview URL
"length": 15, // duration in seconds
"height": 720, // video height in pixels
"errorCode": null,
"errorMessage": null
}Status Values
0— New — Video created, waiting for processing2— Processing — Currently being processed3— Preview Ready — Preview is available atpreviewUrl5— Full Ready — Full video is available atfullUrl99— Error — Processing failed, checkerrorMessage
Poll every 2-5 seconds. Processing typically takes 30 seconds to 5 minutes depending on video length and queue depth.
Webhooks
Pass a webhookUrl in the create request to receive a POST when processing completes. The webhook payload:
POST {your_webhook_url}
Content-Type: application/json
{
"event": "video.completed",
"videoId": "550e8400-e29b-41d4-a716-446655440000",
"status": "full_ready",
"videoUrl": "https://...",
"fullUrl": "https://...",
"previewUrl": "https://...",
"length": 15,
"height": 720
}Your webhook endpoint should return a 200 status to acknowledge receipt. Webhooks are sent on both success and failure (status will be error).
Code Samples
cURL — Create & Poll
# Step 1: Create a lip-sync video
curl -X POST https://www.lipsyn.cc/api/lipsync \
-H "Authorization: Bearer lip_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"audio": { "uri": "https://example.com/audio.mp3" },
"video": { "uri": "https://example.com/video.mp4" },
"webhookUrl": "https://your-app.com/webhook"
}'
# Response: { "videoId": "abc-123", "pollingUrl": "..." }
# Step 2: Poll for results (same API key)
curl -H "Authorization: Bearer lip_your_api_key_here" \
https://www.lipsyn.cc/api/video/abc-123Python — Polling
import requests, time
API_KEY = "lip_your_api_key_here"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
# Create video
resp = requests.post(
"https://www.lipsyn.cc/api/lipsync",
headers=HEADERS,
json={
"audio": {"uri": "https://example.com/audio.mp3"},
"video": {"uri": "https://example.com/video.mp4"},
},
)
data = resp.json()
video_id = data["videoId"]
print(f"Video queued: {video_id}")
# Poll until ready (same API key works for both endpoints)
while True:
resp = requests.get(
f"https://www.lipsyn.cc/api/video/{video_id}",
headers=HEADERS,
)
video = resp.json()
status = video["status"]
print(f"Status code: {status}")
if status == 5: # FULL_PROCESSED
video_url = video["fullUrl"]
print(f"Video ready: {video_url}")
# Download the video
r = requests.get(video_url)
with open("output.mp4", "wb") as f:
f.write(r.content)
break
elif status == 99: # ERROR
print(f"Error: {video['errorMessage']}")
break
time.sleep(3)Node.js — Webhook Receiver
import express from 'express';
const app = express();
app.post('/webhook', express.json(), (req, res) => {
const { event, videoId, status, videoUrl } = req.body;
if (event === 'video.completed') {
console.log(`Video ${videoId} is ${status}`);
if (status === 'full_ready') {
// Download or process the video
console.log(`Download from: ${videoUrl}`);
}
}
res.status(200).send('OK');
});
app.listen(3000);Python — Create with Webhook
import requests
API_KEY = "lip_your_api_key_here"
resp = requests.post(
"https://www.lipsyn.cc/api/lipsync",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"audio": {"voice": "en-US-JennyNeural", "text": "Hello world!"},
"video": {"imageUri": "https://example.com/photo.jpg"},
"webhookUrl": "https://your-server.com/webhook",
},
)
print(resp.json())
# Response includes videoId — you can also poll as a fallbackPython — File Upload (Multipart)
import requests
API_KEY = "lip_your_api_key_here"
with open("video.mp4", "rb") as vf, open("audio.mp3", "rb") as af:
resp = requests.post(
"https://www.lipsyn.cc/api/lipsync",
headers={"Authorization": f"Bearer {API_KEY}"},
files={
"video": vf,
"audio": af,
},
data={
"webhookUrl": "https://your-server.com/webhook",
},
)
print(resp.json())Error Handling
| Status | Meaning |
|---|---|
| 202 | Accepted — video queued for processing |
| 400 | Bad request — missing or invalid parameters |
| 401 | Unauthorized — invalid or missing API key |
| 402 | Payment required — insufficient minute balance |
| 413 | File too large — max 512 MB |
| 429 | Too many requests — rate limit exceeded |
| 500 | Internal server error |
Rate Limits
- 60 requests per minute per API key
- Max file size: 512 MB per upload
- Max source video and audio duration: 120 minutes; avatar duration: 5 minutes
- Concurrent processing: 5 videos per account
Full examples: Clone the lipsyn-cc/api-examples repo for complete Python and Node.js client libraries with ready-to-run examples.
Questions? Contact support@lipsyn.cc