curl --request POST \
--url https://apis.viggle.ai/v1/videos \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form 'prompt=A paper airplane gliding through a sunlit office' \
--form quality=low \
--form first_frame_image='@example-file' \
--form 'first_frame_image_url=<string>' \
--form last_frame_image='@example-file' \
--form 'last_frame_image_url=<string>' \
--form duration_s=5 \
--form resolution=768p \
--form aspect_ratio=16:9 \
--form seed=1 \
--form watermark=falseimport requests
url = "https://apis.viggle.ai/v1/videos"
files = {
"first_frame_image": ("example-file", open("example-file", "rb")),
"last_frame_image": ("example-file", open("example-file", "rb"))
}
payload = {
"prompt": "A paper airplane gliding through a sunlit office",
"quality": "low",
"first_frame_image_url": "<string>",
"last_frame_image_url": "<string>",
"duration_s": "5",
"resolution": "768p",
"aspect_ratio": "16:9",
"seed": "1",
"watermark": "false"
}
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('prompt', 'A paper airplane gliding through a sunlit office');
form.append('quality', 'low');
form.append('first_frame_image', '<string>');
form.append('first_frame_image_url', '<string>');
form.append('last_frame_image', '<string>');
form.append('last_frame_image_url', '<string>');
form.append('duration_s', '5');
form.append('resolution', '768p');
form.append('aspect_ratio', '16:9');
form.append('seed', '1');
form.append('watermark', 'false');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://apis.viggle.ai/v1/videos', 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://apis.viggle.ai/v1/videos",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nA paper airplane gliding through a sunlit office\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"quality\"\r\n\r\nlow\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"first_frame_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"first_frame_image_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"last_frame_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"last_frame_image_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"duration_s\"\r\n\r\n5\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"resolution\"\r\n\r\n768p\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"aspect_ratio\"\r\n\r\n16:9\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"seed\"\r\n\r\n1\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"watermark\"\r\n\r\nfalse\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data"
],
]);
$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://apis.viggle.ai/v1/videos"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nA paper airplane gliding through a sunlit office\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"quality\"\r\n\r\nlow\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"first_frame_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"first_frame_image_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"last_frame_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"last_frame_image_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"duration_s\"\r\n\r\n5\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"resolution\"\r\n\r\n768p\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"aspect_ratio\"\r\n\r\n16:9\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"seed\"\r\n\r\n1\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"watermark\"\r\n\r\nfalse\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://apis.viggle.ai/v1/videos")
.header("Authorization", "Bearer <token>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nA paper airplane gliding through a sunlit office\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"quality\"\r\n\r\nlow\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"first_frame_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"first_frame_image_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"last_frame_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"last_frame_image_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"duration_s\"\r\n\r\n5\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"resolution\"\r\n\r\n768p\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"aspect_ratio\"\r\n\r\n16:9\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"seed\"\r\n\r\n1\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"watermark\"\r\n\r\nfalse\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://apis.viggle.ai/v1/videos")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nA paper airplane gliding through a sunlit office\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"quality\"\r\n\r\nlow\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"first_frame_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"first_frame_image_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"last_frame_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"last_frame_image_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"duration_s\"\r\n\r\n5\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"resolution\"\r\n\r\n768p\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"aspect_ratio\"\r\n\r\n16:9\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"seed\"\r\n\r\n1\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"watermark\"\r\n\r\nfalse\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"id": "<string>",
"status": "queued",
"progress": 50,
"created_at": "2023-11-07T05:31:56Z"
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}Generate Video (from First-Last Frames and/or Text)
Generate a MiniMax H3 video anchored on both a starting and an ending frame.
curl --request POST \
--url https://apis.viggle.ai/v1/videos \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form 'prompt=A paper airplane gliding through a sunlit office' \
--form quality=low \
--form first_frame_image='@example-file' \
--form 'first_frame_image_url=<string>' \
--form last_frame_image='@example-file' \
--form 'last_frame_image_url=<string>' \
--form duration_s=5 \
--form resolution=768p \
--form aspect_ratio=16:9 \
--form seed=1 \
--form watermark=falseimport requests
url = "https://apis.viggle.ai/v1/videos"
files = {
"first_frame_image": ("example-file", open("example-file", "rb")),
"last_frame_image": ("example-file", open("example-file", "rb"))
}
payload = {
"prompt": "A paper airplane gliding through a sunlit office",
"quality": "low",
"first_frame_image_url": "<string>",
"last_frame_image_url": "<string>",
"duration_s": "5",
"resolution": "768p",
"aspect_ratio": "16:9",
"seed": "1",
"watermark": "false"
}
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('prompt', 'A paper airplane gliding through a sunlit office');
form.append('quality', 'low');
form.append('first_frame_image', '<string>');
form.append('first_frame_image_url', '<string>');
form.append('last_frame_image', '<string>');
form.append('last_frame_image_url', '<string>');
form.append('duration_s', '5');
form.append('resolution', '768p');
form.append('aspect_ratio', '16:9');
form.append('seed', '1');
form.append('watermark', 'false');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://apis.viggle.ai/v1/videos', 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://apis.viggle.ai/v1/videos",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nA paper airplane gliding through a sunlit office\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"quality\"\r\n\r\nlow\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"first_frame_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"first_frame_image_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"last_frame_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"last_frame_image_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"duration_s\"\r\n\r\n5\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"resolution\"\r\n\r\n768p\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"aspect_ratio\"\r\n\r\n16:9\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"seed\"\r\n\r\n1\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"watermark\"\r\n\r\nfalse\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data"
],
]);
$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://apis.viggle.ai/v1/videos"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nA paper airplane gliding through a sunlit office\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"quality\"\r\n\r\nlow\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"first_frame_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"first_frame_image_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"last_frame_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"last_frame_image_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"duration_s\"\r\n\r\n5\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"resolution\"\r\n\r\n768p\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"aspect_ratio\"\r\n\r\n16:9\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"seed\"\r\n\r\n1\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"watermark\"\r\n\r\nfalse\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://apis.viggle.ai/v1/videos")
.header("Authorization", "Bearer <token>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nA paper airplane gliding through a sunlit office\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"quality\"\r\n\r\nlow\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"first_frame_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"first_frame_image_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"last_frame_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"last_frame_image_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"duration_s\"\r\n\r\n5\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"resolution\"\r\n\r\n768p\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"aspect_ratio\"\r\n\r\n16:9\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"seed\"\r\n\r\n1\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"watermark\"\r\n\r\nfalse\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://apis.viggle.ai/v1/videos")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nA paper airplane gliding through a sunlit office\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"quality\"\r\n\r\nlow\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"first_frame_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"first_frame_image_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"last_frame_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"last_frame_image_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"duration_s\"\r\n\r\n5\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"resolution\"\r\n\r\n768p\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"aspect_ratio\"\r\n\r\n16:9\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"seed\"\r\n\r\n1\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"watermark\"\r\n\r\nfalse\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"id": "<string>",
"status": "queued",
"progress": 50,
"created_at": "2023-11-07T05:31:56Z"
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}multipart/form-data with both a first frame and a last frame, each independently as an uploaded file or a URL. Supplying both is what selects this mode of POST /v1/videos — a last frame is only valid alongside a first frame. Generates a Viggle-optimized MiniMax H3 video with native audio.
This uses the same POST /v1/videos endpoint as Generate Video (from Text) and Generate Video (from First Frame and/or Text) — which mode runs is selected by which frame fields you send, not by a separate parameter.
low and high. Image-conditioned generation costs the same as text-only for the same quality and duration; resolution and aspect_ratio don’t affect price. See Pricing and retention.Request parameters
Sendmultipart/form-data.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
prompt | string | Yes | — | Non-empty text description of the desired video. Still required even with frame images supplied. |
quality | string | Yes | — | low for faster generation and quicker iteration, or high for higher-fidelity output. Both tiers are billed at the same $0.01/sec rate. |
first_frame_image | file | One of | — | First-frame image uploaded directly. Supply exactly one of first_frame_image or first_frame_image_url. |
first_frame_image_url | string (URI) | One of | — | Publicly reachable URL of the first-frame image; the service fetches and re-hosts it. Supply instead of first_frame_image. |
last_frame_image | file | One of | — | Last-frame image uploaded directly. Supply exactly one of last_frame_image or last_frame_image_url. Requires a first frame. |
last_frame_image_url | string (URI) | One of | — | Publicly reachable URL of the last-frame image; the service fetches and re-hosts it. Supply instead of last_frame_image. Requires a first frame. |
duration_s | number | No | 5 | Target duration in seconds, 3–15. Rounded up to determine the credit charge. |
resolution | string | No | 768p | 480p, 768p, or 1080p. Does not affect price. |
aspect_ratio | string | No | 16:9 | 16:9, 9:16, 1:1, 4:3, 3:4, or 21:9. Does not affect price. |
seed | integer | No | — | ≥ 0. Omit for a random seed. |
watermark | boolean | No | false | Whether to burn in a Viggle watermark. |
first_frame_image/first_frame_image_url, and exactly one of last_frame_image/last_frame_image_url. Supplying both forms of the same slot answers 400 INVALID_REQUEST — “supply only one of first_frame_image or first_frame_image_url” (or the last_frame_* equivalent). Supplying a last frame with no first frame answers 400 INVALID_REQUEST — “a last frame requires a first frame”.
Response parameters
Returns200 OK — an acceptance acknowledgment, not the final result.
| Field | Type | Always present | Description |
|---|---|---|---|
id | string | Yes | Public video ID, vid_-prefixed. |
status | string | Yes | Always queued on acceptance. |
progress | integer or null | Yes | Always null on acceptance. |
created_at | string | Yes | ISO 8601 creation timestamp, second precision. |
{
"id": "vid_3f2a9c",
"status": "queued",
"progress": null,
"created_at": "2026-08-24T09:12:03Z"
}
GET /v1/videos/{video_id} returns (stage, video_url, alpha_url, completed_at, error). See Get Video for the full shape once the video is processing or done.
Examples
curl https://apis.viggle.ai/v1/videos \
-H "Authorization: Bearer $VIGGLE_API_KEY" \
-F prompt="camera pans across the room" \
-F quality=low \
-F first_frame_image_url=https://example.test/first.png \
-F last_frame_image_url=https://example.test/last.png
import os, requests
response = requests.post(
"https://apis.viggle.ai/v1/videos",
headers={"Authorization": f"Bearer {os.environ['VIGGLE_API_KEY']}"},
data={
"prompt": "camera pans across the room",
"quality": "low",
"first_frame_image_url": "https://example.test/first.png",
"last_frame_image_url": "https://example.test/last.png",
},
)
response.raise_for_status()
video = response.json()
const form = new FormData();
form.append("prompt", "camera pans across the room");
form.append("quality", "low");
form.append("first_frame_image_url", "https://example.test/first.png");
form.append("last_frame_image_url", "https://example.test/last.png");
const response = await fetch("https://apis.viggle.ai/v1/videos", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.VIGGLE_API_KEY}` },
body: form,
});
const video = await response.json();
Common errors
| Error | Cause |
|---|---|
400 INVALID_REQUEST — “a last frame requires a first frame” | last_frame_image/last_frame_image_url supplied without a first frame. |
400 INVALID_REQUEST — “supply only one of first_frame_image or first_frame_image_url” | Both the file and URL form of the same frame slot supplied (the last_frame_* pair answers the equivalent message). |
Next step
Use Get Video with the returnedid to poll for the result.
Quick Start: First-Last Frames to Video
Generate Video (from Text)
Generate Video (from First Frame and/or Text)
Authorizations
Server-side SDK clients use a project API key. Remote MCP clients use an OAuth access token. Never expose a project API key in browser code.
Headers
Optional caller-supplied correlation ID, up to 128 characters. The service returns the effective value in the response header for tracing and support.
1 - 128Optional source-channel label, up to 128 characters, used to attribute traffic to an SDK, integration, product surface, or internal workflow.
1 - 128Body
Body of POST /v1/videos, multipart/form-data only. Which frame
fields are supplied selects the generation mode:
- Neither
first_frame_image/first_frame_image_urlnorlast_frame_image/last_frame_image_urlsupplied: text-to-video. - A first frame supplied, no last frame: first-frame-to-video.
- Both a first frame and a last frame supplied: first+last-frame-to-video.
Within a frame slot, supply at most one of the file and the URL
form. Supplying both first_frame_image and first_frame_image_url
(or both last_frame_image and last_frame_image_url) answers
400 INVALID_REQUEST with "supply only one of first_frame_image or
first_frame_image_url" (or the last_frame_* equivalent). A last
frame requires a first frame; supplying a last frame with no first
frame answers 400 INVALID_REQUEST with "a last frame requires a
first frame".
Billed at ⌈duration_s⌉ × the per-second credit rate for quality
(currently 1 credit/second — $0.01/sec — for both low and high).
resolution and aspect_ratio do not affect price, and
image-conditioned generation (first-frame or first-last-frame) costs
the same as text-only for the same quality and duration.
Every generated video includes native audio; there is no separate audio flag or field.
Text description of the desired video. Required and non-empty in every mode, including first-frame and first+last-frame modes.
1"A paper airplane gliding through a sunlit office"
Generation quality tier — also selects the per-second credit rate charged for this request. low generates faster, for quicker iteration; high generates slower but produces higher-fidelity output. Both tiers are billed at the same $0.01/sec rate.
low, high "low"
First-frame image uploaded directly. Mutually exclusive with first_frame_image_url. Supplying either switches on first-frame-to-video mode.
Publicly reachable URL of the first-frame image; the service fetches and re-hosts it. Mutually exclusive with first_frame_image.
Last-frame image uploaded directly. Mutually exclusive with last_frame_image_url. Valid only alongside a first frame.
Publicly reachable URL of the last-frame image; the service fetches and re-hosts it. Mutually exclusive with last_frame_image. Valid only alongside a first frame.
Target video duration in seconds.
3 <= x <= 15Output resolution tier.
480p, 768p, 1080p Output aspect ratio.
16:9, 9:16, 1:1, 4:3, 3:4, 21:9 Deterministic generation seed. Omit for a random seed.
x >= 0Whether to burn in a Viggle watermark.
Response
Video generation accepted and queued.
Acceptance acknowledgment for POST /v1/videos, shared by all three
generation modes. This is a queued confirmation, not the final
result — poll GET /v1/videos/{video_id} (or watch it via
GET /v1/videos) until status reaches ready, then read
video_url from that response.
Public video ID, vid_-prefixed.
1Lifecycle state at acceptance time; always queued.
queued, processing, ready, failed, cancelled Always null on acceptance.
0 <= x <= 100ISO 8601 creation timestamp, second precision.

