curl --request POST \
--url https://api.goenhance.ai/api/v1/videos/generations \
--header 'Content-Type: application/json' \
--data '
{
"model": "lipsync-video",
"video_url": "https://example.com/speaker.mp4",
"audio_url": "https://example.com/speech.mp3",
"video_mode": "normal"
}
'import requests
url = "https://api.goenhance.ai/api/v1/videos/generations"
payload = {
"model": "lipsync-video",
"video_url": "https://example.com/speaker.mp4",
"audio_url": "https://example.com/speech.mp3",
"video_mode": "normal"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
model: 'lipsync-video',
video_url: 'https://example.com/speaker.mp4',
audio_url: 'https://example.com/speech.mp3',
video_mode: 'normal'
})
};
fetch('https://api.goenhance.ai/api/v1/videos/generations', 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.goenhance.ai/api/v1/videos/generations",
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([
'model' => 'lipsync-video',
'video_url' => 'https://example.com/speaker.mp4',
'audio_url' => 'https://example.com/speech.mp3',
'video_mode' => 'normal'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$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.goenhance.ai/api/v1/videos/generations"
payload := strings.NewReader("{\n \"model\": \"lipsync-video\",\n \"video_url\": \"https://example.com/speaker.mp4\",\n \"audio_url\": \"https://example.com/speech.mp3\",\n \"video_mode\": \"normal\"\n}")
req, _ := http.NewRequest("POST", url, payload)
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.goenhance.ai/api/v1/videos/generations")
.header("Content-Type", "application/json")
.body("{\n \"model\": \"lipsync-video\",\n \"video_url\": \"https://example.com/speaker.mp4\",\n \"audio_url\": \"https://example.com/speech.mp3\",\n \"video_mode\": \"normal\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.goenhance.ai/api/v1/videos/generations")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"model\": \"lipsync-video\",\n \"video_url\": \"https://example.com/speaker.mp4\",\n \"audio_url\": \"https://example.com/speech.mp3\",\n \"video_mode\": \"normal\"\n}"
response = http.request(request)
puts response.read_body{
"code": 0,
"msg": "Success",
"data": {
"img_uuid": "c12b656c-747a-44fd-9c80-add79b0c52d5"
}
}Lip Sync Video
Lip Sync Video. How it works — one talking-head video plus one audio track: the person in the video is re-animated to speak your audio. Only the mouth region is redrawn; everything else in the frame is left untouched, so the output keeps the resolution, framing and background of your source video.
⚠️ There is no duration and no resolution parameter, and this endpoint rejects neither — it simply ignores them. Both are measured. Before any tokens are deducted, GoEnhance transfers video_url and audio_url to its own storage, then reads the real duration and the real video resolution. Those measurements set the price and enforce the limits below. Because the files are measured and then sent onward from GoEnhance’s storage, swapping a URL afterwards has no effect.
Output length — depends on video_mode:
| video_mode | output length |
|---|---|
normal (default) | the shorter of your video and your audio |
loop | the full length of your audio; the video is played forward and backward on repeat to fill it |
Video requirements — up to 300MB, short side at most 1080 pixels (540p / 720p / 1080p, portrait or landscape). A face must be visible; frames without a detectable face are passed through unchanged. Anything above 1080p is rejected.
Audio requirements — up to 100MB. Clear speech with little background noise gives the best lip sync.
Maximum output length, by the resolution of your source video:
| source resolution | max output |
|---|---|
| 540p | 420s |
| 720p | 420s |
| 1080p | 300s |
Going over the limit is rejected before any tokens are deducted — the clip is never silently truncated.
Pricing — per second of output, in tokens (USD at $0.02/token):
| source resolution | tokens/s | USD/s |
|---|---|---|
| 540p | 0.2 | $0.004 |
| 720p | 0.3 | $0.006 |
| 1080p | 0.5 | $0.010 |
A 60-second output therefore costs 12 tokens at 540p, 18 tokens at 720p, or 30 tokens at 1080p. Partial seconds are rounded up.
Returns an img_uuid; poll GET /api/v1/jobs/detail (or use custom_callback_url) to get the generated video.
curl --request POST \
--url https://api.goenhance.ai/api/v1/videos/generations \
--header 'Content-Type: application/json' \
--data '
{
"model": "lipsync-video",
"video_url": "https://example.com/speaker.mp4",
"audio_url": "https://example.com/speech.mp3",
"video_mode": "normal"
}
'import requests
url = "https://api.goenhance.ai/api/v1/videos/generations"
payload = {
"model": "lipsync-video",
"video_url": "https://example.com/speaker.mp4",
"audio_url": "https://example.com/speech.mp3",
"video_mode": "normal"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
model: 'lipsync-video',
video_url: 'https://example.com/speaker.mp4',
audio_url: 'https://example.com/speech.mp3',
video_mode: 'normal'
})
};
fetch('https://api.goenhance.ai/api/v1/videos/generations', 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.goenhance.ai/api/v1/videos/generations",
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([
'model' => 'lipsync-video',
'video_url' => 'https://example.com/speaker.mp4',
'audio_url' => 'https://example.com/speech.mp3',
'video_mode' => 'normal'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$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.goenhance.ai/api/v1/videos/generations"
payload := strings.NewReader("{\n \"model\": \"lipsync-video\",\n \"video_url\": \"https://example.com/speaker.mp4\",\n \"audio_url\": \"https://example.com/speech.mp3\",\n \"video_mode\": \"normal\"\n}")
req, _ := http.NewRequest("POST", url, payload)
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.goenhance.ai/api/v1/videos/generations")
.header("Content-Type", "application/json")
.body("{\n \"model\": \"lipsync-video\",\n \"video_url\": \"https://example.com/speaker.mp4\",\n \"audio_url\": \"https://example.com/speech.mp3\",\n \"video_mode\": \"normal\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.goenhance.ai/api/v1/videos/generations")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"model\": \"lipsync-video\",\n \"video_url\": \"https://example.com/speaker.mp4\",\n \"audio_url\": \"https://example.com/speech.mp3\",\n \"video_mode\": \"normal\"\n}"
response = http.request(request)
puts response.read_body{
"code": 0,
"msg": "Success",
"data": {
"img_uuid": "c12b656c-747a-44fd-9c80-add79b0c52d5"
}
}Headers
Body
Model name. Must be lipsync-video.
lipsync-video Video containing the person to re-animate (up to 300MB, short side at most 1080 pixels). Required. Its measured resolution sets the price tier and the maximum output length.
Speech audio to lip-sync to (up to 100MB). Required. Together with the video it determines the output length, which is what you are billed for.
What to do when the video and the audio are not the same length. normal trims to whichever is shorter. loop keeps all of your audio and repeats the video (forward, then reversed, ping-pong style) to fill it.
normal, loop Optional. A publicly accessible HTTPS URL. When the task status changes (processing / success / failed), GoEnhance sends a POST request to this URL. The request body is identical to the response of GET /api/v1/jobs/detail. If your server does not respond with HTTP 200, the notification is retried up to 3 times, with a 3-second timeout per attempt.
"https://your-server.com/goenhance/callback"
