curl --request POST \
--url https://api.moda.app/v1/media/generate-video \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"prompt": "<string>",
"model": "<string>",
"start_image": "<string>",
"end_image": "<string>",
"reference_images": [
"<string>"
],
"reference_videos": [
"<string>"
],
"reference_audios": [
"<string>"
],
"extend_video": "<string>",
"lipsync_video": "<string>",
"duration_seconds": 123,
"aspect_ratio": "<string>",
"width": 1,
"height": 1,
"resolution": "<string>",
"generate_audio": true,
"seed": 123,
"model_params": {},
"idempotency_key": "<string>",
"wait": true
}
'import requests
url = "https://api.moda.app/v1/media/generate-video"
payload = {
"prompt": "<string>",
"model": "<string>",
"start_image": "<string>",
"end_image": "<string>",
"reference_images": ["<string>"],
"reference_videos": ["<string>"],
"reference_audios": ["<string>"],
"extend_video": "<string>",
"lipsync_video": "<string>",
"duration_seconds": 123,
"aspect_ratio": "<string>",
"width": 1,
"height": 1,
"resolution": "<string>",
"generate_audio": True,
"seed": 123,
"model_params": {},
"idempotency_key": "<string>",
"wait": True
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
prompt: '<string>',
model: '<string>',
start_image: '<string>',
end_image: '<string>',
reference_images: ['<string>'],
reference_videos: ['<string>'],
reference_audios: ['<string>'],
extend_video: '<string>',
lipsync_video: '<string>',
duration_seconds: 123,
aspect_ratio: '<string>',
width: 1,
height: 1,
resolution: '<string>',
generate_audio: true,
seed: 123,
model_params: {},
idempotency_key: '<string>',
wait: true
})
};
fetch('https://api.moda.app/v1/media/generate-video', 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.moda.app/v1/media/generate-video",
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([
'prompt' => '<string>',
'model' => '<string>',
'start_image' => '<string>',
'end_image' => '<string>',
'reference_images' => [
'<string>'
],
'reference_videos' => [
'<string>'
],
'reference_audios' => [
'<string>'
],
'extend_video' => '<string>',
'lipsync_video' => '<string>',
'duration_seconds' => 123,
'aspect_ratio' => '<string>',
'width' => 1,
'height' => 1,
'resolution' => '<string>',
'generate_audio' => true,
'seed' => 123,
'model_params' => [
],
'idempotency_key' => '<string>',
'wait' => true
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"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.moda.app/v1/media/generate-video"
payload := strings.NewReader("{\n \"prompt\": \"<string>\",\n \"model\": \"<string>\",\n \"start_image\": \"<string>\",\n \"end_image\": \"<string>\",\n \"reference_images\": [\n \"<string>\"\n ],\n \"reference_videos\": [\n \"<string>\"\n ],\n \"reference_audios\": [\n \"<string>\"\n ],\n \"extend_video\": \"<string>\",\n \"lipsync_video\": \"<string>\",\n \"duration_seconds\": 123,\n \"aspect_ratio\": \"<string>\",\n \"width\": 1,\n \"height\": 1,\n \"resolution\": \"<string>\",\n \"generate_audio\": true,\n \"seed\": 123,\n \"model_params\": {},\n \"idempotency_key\": \"<string>\",\n \"wait\": true\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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.moda.app/v1/media/generate-video")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"prompt\": \"<string>\",\n \"model\": \"<string>\",\n \"start_image\": \"<string>\",\n \"end_image\": \"<string>\",\n \"reference_images\": [\n \"<string>\"\n ],\n \"reference_videos\": [\n \"<string>\"\n ],\n \"reference_audios\": [\n \"<string>\"\n ],\n \"extend_video\": \"<string>\",\n \"lipsync_video\": \"<string>\",\n \"duration_seconds\": 123,\n \"aspect_ratio\": \"<string>\",\n \"width\": 1,\n \"height\": 1,\n \"resolution\": \"<string>\",\n \"generate_audio\": true,\n \"seed\": 123,\n \"model_params\": {},\n \"idempotency_key\": \"<string>\",\n \"wait\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.moda.app/v1/media/generate-video")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"prompt\": \"<string>\",\n \"model\": \"<string>\",\n \"start_image\": \"<string>\",\n \"end_image\": \"<string>\",\n \"reference_images\": [\n \"<string>\"\n ],\n \"reference_videos\": [\n \"<string>\"\n ],\n \"reference_audios\": [\n \"<string>\"\n ],\n \"extend_video\": \"<string>\",\n \"lipsync_video\": \"<string>\",\n \"duration_seconds\": 123,\n \"aspect_ratio\": \"<string>\",\n \"width\": 1,\n \"height\": 1,\n \"resolution\": \"<string>\",\n \"generate_audio\": true,\n \"seed\": 123,\n \"model_params\": {},\n \"idempotency_key\": \"<string>\",\n \"wait\": true\n}"
response = http.request(request)
puts response.read_body{}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}Generate Video
Generate one short video (metered).
wait (default true) runs the provider render inside this call, which takes 30-300s.
wait: false returns as soon as the render is submitted, with a task_id and a
poll_url; poll it about every retry_after_ms until status is terminal. Nothing is
charged at submission — the charge lands when a poll collects the finished video, so an
abandoned task costs nothing.
curl --request POST \
--url https://api.moda.app/v1/media/generate-video \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"prompt": "<string>",
"model": "<string>",
"start_image": "<string>",
"end_image": "<string>",
"reference_images": [
"<string>"
],
"reference_videos": [
"<string>"
],
"reference_audios": [
"<string>"
],
"extend_video": "<string>",
"lipsync_video": "<string>",
"duration_seconds": 123,
"aspect_ratio": "<string>",
"width": 1,
"height": 1,
"resolution": "<string>",
"generate_audio": true,
"seed": 123,
"model_params": {},
"idempotency_key": "<string>",
"wait": true
}
'import requests
url = "https://api.moda.app/v1/media/generate-video"
payload = {
"prompt": "<string>",
"model": "<string>",
"start_image": "<string>",
"end_image": "<string>",
"reference_images": ["<string>"],
"reference_videos": ["<string>"],
"reference_audios": ["<string>"],
"extend_video": "<string>",
"lipsync_video": "<string>",
"duration_seconds": 123,
"aspect_ratio": "<string>",
"width": 1,
"height": 1,
"resolution": "<string>",
"generate_audio": True,
"seed": 123,
"model_params": {},
"idempotency_key": "<string>",
"wait": True
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
prompt: '<string>',
model: '<string>',
start_image: '<string>',
end_image: '<string>',
reference_images: ['<string>'],
reference_videos: ['<string>'],
reference_audios: ['<string>'],
extend_video: '<string>',
lipsync_video: '<string>',
duration_seconds: 123,
aspect_ratio: '<string>',
width: 1,
height: 1,
resolution: '<string>',
generate_audio: true,
seed: 123,
model_params: {},
idempotency_key: '<string>',
wait: true
})
};
fetch('https://api.moda.app/v1/media/generate-video', 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.moda.app/v1/media/generate-video",
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([
'prompt' => '<string>',
'model' => '<string>',
'start_image' => '<string>',
'end_image' => '<string>',
'reference_images' => [
'<string>'
],
'reference_videos' => [
'<string>'
],
'reference_audios' => [
'<string>'
],
'extend_video' => '<string>',
'lipsync_video' => '<string>',
'duration_seconds' => 123,
'aspect_ratio' => '<string>',
'width' => 1,
'height' => 1,
'resolution' => '<string>',
'generate_audio' => true,
'seed' => 123,
'model_params' => [
],
'idempotency_key' => '<string>',
'wait' => true
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"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.moda.app/v1/media/generate-video"
payload := strings.NewReader("{\n \"prompt\": \"<string>\",\n \"model\": \"<string>\",\n \"start_image\": \"<string>\",\n \"end_image\": \"<string>\",\n \"reference_images\": [\n \"<string>\"\n ],\n \"reference_videos\": [\n \"<string>\"\n ],\n \"reference_audios\": [\n \"<string>\"\n ],\n \"extend_video\": \"<string>\",\n \"lipsync_video\": \"<string>\",\n \"duration_seconds\": 123,\n \"aspect_ratio\": \"<string>\",\n \"width\": 1,\n \"height\": 1,\n \"resolution\": \"<string>\",\n \"generate_audio\": true,\n \"seed\": 123,\n \"model_params\": {},\n \"idempotency_key\": \"<string>\",\n \"wait\": true\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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.moda.app/v1/media/generate-video")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"prompt\": \"<string>\",\n \"model\": \"<string>\",\n \"start_image\": \"<string>\",\n \"end_image\": \"<string>\",\n \"reference_images\": [\n \"<string>\"\n ],\n \"reference_videos\": [\n \"<string>\"\n ],\n \"reference_audios\": [\n \"<string>\"\n ],\n \"extend_video\": \"<string>\",\n \"lipsync_video\": \"<string>\",\n \"duration_seconds\": 123,\n \"aspect_ratio\": \"<string>\",\n \"width\": 1,\n \"height\": 1,\n \"resolution\": \"<string>\",\n \"generate_audio\": true,\n \"seed\": 123,\n \"model_params\": {},\n \"idempotency_key\": \"<string>\",\n \"wait\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.moda.app/v1/media/generate-video")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"prompt\": \"<string>\",\n \"model\": \"<string>\",\n \"start_image\": \"<string>\",\n \"end_image\": \"<string>\",\n \"reference_images\": [\n \"<string>\"\n ],\n \"reference_videos\": [\n \"<string>\"\n ],\n \"reference_audios\": [\n \"<string>\"\n ],\n \"extend_video\": \"<string>\",\n \"lipsync_video\": \"<string>\",\n \"duration_seconds\": 123,\n \"aspect_ratio\": \"<string>\",\n \"width\": 1,\n \"height\": 1,\n \"resolution\": \"<string>\",\n \"generate_audio\": true,\n \"seed\": 123,\n \"model_params\": {},\n \"idempotency_key\": \"<string>\",\n \"wait\": true\n}"
response = http.request(request)
puts response.read_body{}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}Authorizations
API key from Settings > Developer > REST API
Headers
Calendar-dated API version pin. New integrations should pin 2026-05-01 to opt into the newest response shapes. For back-compat the server also accepts requests with no header and resolves them to the current default (today: 2026-04-12); that default advances on each sunset date. Any unsupported value returns 400 unsupported_version.
2026-04-12, 2026-05-01 "2026-05-01"
Body
Sent verbatim. For MiniMax H3 reference mode, bind inputs by modality and 1-based list order: Image 1, Video 1, Audio 1, and so on.
1Concrete video model id; see GET /v1/media/models.
Subject/style references. For MiniMax H3, name them Image 1, Image 2, and so on in the prompt.
Short reference clips: file_ ids from an upload or http(s) video URLs. Only some models accept them, and each states its own clip-count and length limits — read them off the reference_videos block on GET /v1/media/models, which a 422 also names in full. Naming the models here instead would drift on every model add. Each clip becomes a durable file in the team's library; the model card states whether its running time is billed too. For MiniMax H3, name them Video 1, Video 2, and so on in the prompt.
Short reference audio: file_ ids from an upload or http(s) audio URLs. See the reference_audios capability on GET /v1/media/models. For MiniMax H3, name them Audio 1, Audio 2, and so on in the prompt.
Make an EXISTING clip longer: a file_ id from an upload or an http(s) video URL, plus a prompt for what happens next. The response carries the source with new footage on the end. Only models whose card lists a video_extend mode accept it, and it cannot be combined with start/end frames or references. There duration_seconds is the length of the ADDED segment — the delivered clip is longer than it by the source's own running time. Read billing.duration_quantity and billing.input_seconds_priced_separately, plus the available framing controls, from the video_extend block on GET /v1/media/models.
Re-cut an EXISTING clip's mouth to a track you supply: a file_ id from an upload or an http(s) video URL, plus exactly one reference_audios entry — the speech being synced to. Only models whose card lists a video_lipsync mode accept it. Nothing else may accompany the pair: no frames, no other references, and not extend_video. The clip is returned at its OWN length, so duration_seconds is not a control here and prompt is not used at all. Billing meters the SOURCE clip rather than the returned file — read the video_lipsync block on GET /v1/media/models for its envelope, its basis, and any block rounding.
x > 0x > 0Resolution tier from the selected mode's resolutions on GET /v1/media/models.
Keys the provider-job checkpoint: a retried call with the same key resumes the existing render instead of paying for a duplicate.
200Whether the provider render runs inside this call. The default holds the request open for the whole render (30-300s). Pass false to get a task envelope back immediately and poll GET /v1/media/generate-video/{task_id} — the right choice for long renders and for starting several candidates at once. Nothing is charged until a poll collects the finished video.
Response
Successful Response
The response is of type Response Mediageneratevideo · object.