curl --request POST \
--url https://apis.viggle.ai/v1/renders \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form image='@example-file' \
--form 'image_url=<string>' \
--form 'character_id=<string>' \
--form motion_video='@example-file' \
--form 'motion_video_url=<string>' \
--form 'motion_id=<string>' \
--form background_mode=original \
--form 'bg_color=0,177,64'import requests
url = "https://apis.viggle.ai/v1/renders"
files = {
"image": ("example-file", open("example-file", "rb")),
"motion_video": ("example-file", open("example-file", "rb"))
}
payload = {
"image_url": "<string>",
"character_id": "<string>",
"motion_video_url": "<string>",
"motion_id": "<string>",
"background_mode": "original",
"bg_color": "0,177,64"
}
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('image', '<string>');
form.append('image_url', '<string>');
form.append('character_id', '<string>');
form.append('motion_video', '<string>');
form.append('motion_video_url', '<string>');
form.append('motion_id', '<string>');
form.append('background_mode', 'original');
form.append('bg_color', '0,177,64');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://apis.viggle.ai/v1/renders', 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/renders",
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=\"image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"character_id\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"motion_video\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"motion_video_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"motion_id\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"background_mode\"\r\n\r\noriginal\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"bg_color\"\r\n\r\n0,177,64\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/renders"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"character_id\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"motion_video\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"motion_video_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"motion_id\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"background_mode\"\r\n\r\noriginal\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"bg_color\"\r\n\r\n0,177,64\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/renders")
.header("Authorization", "Bearer <token>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"character_id\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"motion_video\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"motion_video_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"motion_id\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"background_mode\"\r\n\r\noriginal\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"bg_color\"\r\n\r\n0,177,64\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://apis.viggle.ai/v1/renders")
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=\"image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"character_id\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"motion_video\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"motion_video_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"motion_id\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"background_mode\"\r\n\r\noriginal\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"bg_color\"\r\n\r\n0,177,64\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"id": "<string>",
"status": "queued",
"stage": "queued",
"progress": 50,
"video_url": "<string>",
"alpha_url": "<string>",
"created_at": "2023-11-07T05:31:56Z",
"completed_at": "2023-11-07T05:31:56Z",
"error": {
"code": "<string>",
"message": "<string>",
"details": {},
"request_id": "<string>"
},
"links": {
"self": "<string>",
"events": "<string>",
"download": "<string>"
}
}{
"id": "<string>",
"status": "queued",
"stage": "queued",
"progress": 50,
"video_url": "<string>",
"alpha_url": "<string>",
"created_at": "2023-11-07T05:31:56Z",
"completed_at": "2023-11-07T05:31:56Z",
"error": {
"code": "<string>",
"message": "<string>",
"details": {},
"request_id": "<string>"
},
"links": {
"self": "<string>",
"events": "<string>",
"download": "<string>"
}
}{
"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
}
}
}{
"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
}
}
}Render Video (from Character and/or Motion)
Create a render directly, or finalize one prepared with Prepare Render.
curl --request POST \
--url https://apis.viggle.ai/v1/renders \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form image='@example-file' \
--form 'image_url=<string>' \
--form 'character_id=<string>' \
--form motion_video='@example-file' \
--form 'motion_video_url=<string>' \
--form 'motion_id=<string>' \
--form background_mode=original \
--form 'bg_color=0,177,64'import requests
url = "https://apis.viggle.ai/v1/renders"
files = {
"image": ("example-file", open("example-file", "rb")),
"motion_video": ("example-file", open("example-file", "rb"))
}
payload = {
"image_url": "<string>",
"character_id": "<string>",
"motion_video_url": "<string>",
"motion_id": "<string>",
"background_mode": "original",
"bg_color": "0,177,64"
}
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('image', '<string>');
form.append('image_url', '<string>');
form.append('character_id', '<string>');
form.append('motion_video', '<string>');
form.append('motion_video_url', '<string>');
form.append('motion_id', '<string>');
form.append('background_mode', 'original');
form.append('bg_color', '0,177,64');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://apis.viggle.ai/v1/renders', 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/renders",
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=\"image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"character_id\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"motion_video\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"motion_video_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"motion_id\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"background_mode\"\r\n\r\noriginal\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"bg_color\"\r\n\r\n0,177,64\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/renders"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"character_id\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"motion_video\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"motion_video_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"motion_id\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"background_mode\"\r\n\r\noriginal\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"bg_color\"\r\n\r\n0,177,64\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/renders")
.header("Authorization", "Bearer <token>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"character_id\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"motion_video\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"motion_video_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"motion_id\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"background_mode\"\r\n\r\noriginal\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"bg_color\"\r\n\r\n0,177,64\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://apis.viggle.ai/v1/renders")
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=\"image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"character_id\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"motion_video\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"motion_video_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"motion_id\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"background_mode\"\r\n\r\noriginal\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"bg_color\"\r\n\r\n0,177,64\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"id": "<string>",
"status": "queued",
"stage": "queued",
"progress": 50,
"video_url": "<string>",
"alpha_url": "<string>",
"created_at": "2023-11-07T05:31:56Z",
"completed_at": "2023-11-07T05:31:56Z",
"error": {
"code": "<string>",
"message": "<string>",
"details": {},
"request_id": "<string>"
},
"links": {
"self": "<string>",
"events": "<string>",
"download": "<string>"
}
}{
"id": "<string>",
"status": "queued",
"stage": "queued",
"progress": 50,
"video_url": "<string>",
"alpha_url": "<string>",
"created_at": "2023-11-07T05:31:56Z",
"completed_at": "2023-11-07T05:31:56Z",
"error": {
"code": "<string>",
"message": "<string>",
"details": {},
"request_id": "<string>"
},
"links": {
"self": "<string>",
"events": "<string>",
"download": "<string>"
}
}{
"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
}
}
}{
"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
}
}
}Content-Type.
multipart/form-data creates a render directly from character and motion inputs, each supplied as an uploaded file, a URL, or the ID of a saved asset. This form answers 200 and ignores Idempotency-Key.
application/json creates a render from a draft prepared by Prepare Render. This form answers 202, requires Idempotency-Key, and is idempotent within the authenticated project or OAuth principal: reusing the same key with the same normalized input returns the original render, while reusing it with different input returns IDEMPOTENCY_KEY_REUSED.
Request parameters — multipart/form-data
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
character_id | string | One character source | — | ID of a ready Character owned by the caller, normally beginning with char_. Use instead of uploading or linking a new image. |
image | file | One character source | — | Character source image uploaded directly. Use exactly one character source among image, image_url, and character_id. |
image_url | string | One character source | — | Publicly reachable URL of a character image that the service can fetch. Use instead of image or character_id. |
motion_id | string | One motion source | — | ID of a ready Motion owned by the caller, normally beginning with mot_. Use instead of uploading or linking a new driving video. |
motion_video | file | One motion source | — | Driving video uploaded directly. Use exactly one motion source among motion_video, motion_video_url, and motion_id. |
motion_video_url | string | One motion source | — | Publicly reachable URL of a driving video that the service can fetch. Use instead of motion_video or motion_id. |
background_mode | string | No | original | Background treatment: original preserves or reconstructs the source background, solid uses bg_color, transparent requests alpha output, and inpaint is accepted as an alias of original. |
bg_color | string | With solid | — | Solid background colour as "R,G,B", for example "0,177,64". Valid only with background_mode=solid. |
character_id/motion_id and renders that instead of rejecting the request. Supplying either group partially — for example a character source with no motion source — is still an error. bg_color is only valid with background_mode=solid.
character_id/motion_id are configured per project/environment, not fixed across all of Viggle — two API keys can send an all-empty request and get different results. Don’t rely on this fallback for anything beyond a quick smoke test; always supply an explicit character and motion source in production traffic.Request parameters — application/json (draft-based)
| Header | Required | Description |
|---|---|---|
Idempotency-Key | Yes | Caller-generated stable key of 1–255 characters. Reusing it with the same normalized draft returns the original Render; reusing it with different input returns IDEMPOTENCY_KEY_REUSED. |
| Parameter | Type | Required | Description |
|---|---|---|---|
draft_id | string | Yes | Opaque draft ID returned by Prepare Render. Pass it unchanged after completing every required direct upload; a draft can be consumed only once. |
upload_completions | array | With uploads | One entry for every upload plan returned by Prepare Render. Required when the draft’s state was awaiting_uploads; omit or send an empty array when no upload was required. |
upload_completions[].upload_handle | string | Yes per entry | Opaque handle from the matching upload plan. It identifies which character or motion upload was completed. |
upload_completions[].etag | string | No | Optional ETag returned by the object-store PUT response. Preserve surrounding quotes when the storage service includes them. |
Response parameters
multipart/form-data returns 200 OK; the application/json draft form returns 202 Accepted. Both return a Render object, though the 200 response from a direct multipart request only carries information in id, status, progress, and created_at.
| Field | Type | Always present | Description |
|---|---|---|---|
id | string | Yes | Public Render ID beginning with render_. |
status | string | Yes | queued, processing, ready, failed, or cancelled. |
stage | string or null | Yes | Coarse progress hint when available. analyzing/rendering/finishing come from the multipart pipeline; other values come from the draft-based pipeline. |
progress | integer or null | Yes | Progress from 0 to 100 when available. |
video_url | string or null | Yes | Finished video URL when ready. |
alpha_url | string or null | Yes | Alpha video URL for transparent output, when available. |
created_at / completed_at | string or null | Yes | ISO 8601 timestamps. |
error | object or null | Yes | Failure details when the Render fails. |
links | object | No | {self, events, download} — absent on renders served by the legacy migration proxy. |
{"id":"render_789ghi","status":"queued","stage":null,"progress":0,"video_url":null,"alpha_url":null,"created_at":"2026-07-21T10:00:00+00:00","completed_at":null,"error":null,"links":{"self":"/v1/renders/render_789ghi","events":"/v1/renders/render_789ghi/events","download":"/v1/renders/render_789ghi/download"}}
links.self mirrors the retired GET /v1/renders/{render_id} and no longer resolves — poll Get Video with the same id instead. links.events and links.download are unaffected.
Examples — direct multipart/form-data
body:=&bytes.Buffer{}; w:=multipart.NewWriter(body); image,_:=os.Open("character.png"); defer image.Close(); video,_:=os.Open("dance.mp4"); defer video.Close(); p,_:=w.CreateFormFile("image","character.png"); io.Copy(p,image); p,_=w.CreateFormFile("motion_video","dance.mp4"); io.Copy(p,video); w.WriteField("background_mode","original"); w.Close(); req,_:=http.NewRequest("POST","https://apis.viggle.ai/v1/renders",body); req.Header.Set("Authorization","Bearer "+os.Getenv("VIGGLE_API_KEY")); req.Header.Set("Content-Type",w.FormDataContentType()); resp,err:=http.DefaultClient.Do(req); if err!=nil { panic(err) }; defer resp.Body.Close()
const form=new FormData(); form.append("image",imageFile); form.append("motion_video",videoFile); form.append("background_mode","original"); const response=await fetch("https://apis.viggle.ai/v1/renders",{method:"POST",headers:{Authorization:`Bearer ${process.env.VIGGLE_API_KEY}`},body:form}); const render=await response.json();
import os, requests
with open("character.png","rb") as image, open("dance.mp4","rb") as video: response=requests.post("https://apis.viggle.ai/v1/renders",headers={"Authorization":f"Bearer {os.environ['VIGGLE_API_KEY']}"},files={"image":image,"motion_video":video},data={"background_mode":"original"})
response.raise_for_status(); render=response.json()
curl -X POST "https://apis.viggle.ai/v1/renders" -H "Authorization: Bearer $VIGGLE_API_KEY" -F "[email protected]" -F "[email protected]" -F "background_mode=original"
Examples — draft-based application/json
curl -X POST "https://apis.viggle.ai/v1/renders" \
-H "Authorization: Bearer $VIGGLE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"draft_id":"draft_abc123","upload_completions":[{"upload_handle":"up_1","etag":"\"9f...\""}]}'
import os, uuid, requests
response = requests.post(
"https://apis.viggle.ai/v1/renders",
headers={
"Authorization": f"Bearer {os.environ['VIGGLE_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={"draft_id": "draft_abc123", "upload_completions": [{"upload_handle": "up_1", "etag": "\"9f...\""}]},
)
response.raise_for_status()
render = response.json()
draft_id and upload the character/motion media before calling this endpoint in JSON mode.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
Caller-generated stable key, maximum 255 characters. Required by the
application/json draft form of POST /v1/renders and ignored by the
multipart/form-data form.
1 - 255Optional 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
Supply the character as one of image, image_url, or character_id,
and the motion as one of motion_video, motion_video_url, or
motion_id. If neither group is supplied at all, the server
substitutes its own preconfigured default character_id/motion_id
and renders that instead of rejecting the request — supplying either
group partially (for example a character source with no motion
source) is still an error.
Character image uploaded directly. Use exactly one character source among image, image_url, and character_id.
Public URL of a character image that the service can fetch. Use instead of image or character_id.
ID of a ready reusable Character owned by the caller. Use instead of uploading or linking a new image.
1Driving video uploaded directly. Use exactly one motion source among motion_video, motion_video_url, and motion_id.
Public URL of a driving video that the service can fetch. Use instead of motion_video or motion_id.
ID of a ready reusable Motion owned by the caller. Use instead of uploading or linking a new driving video.
1inpaint is accepted and treated as original. This set is
unrelated to RenderOutputOptions.background_mode, which belongs to
the draft-based flow.
original, solid, transparent, inpaint Solid background colour as three decimal components, "R,G,B", for
example "0,177,64". Valid only with background_mode=solid.
"0,177,64"
Response
Render accepted from a direct multipart/form-data request. Only
id, status, progress, and created_at carry information.
stage, progress, and created_at are null on renders that carry no
such information, and links is absent on renders served by the
migration proxy. Treat all four as optional when reading.
The resource response exposes the result URLs but does not expose media dimensions, duration, thumbnail URL, or URL expiry metadata. Clients must not infer those fields from this contract.
Public Render ID, normally beginning with render_; use it with get, events, and download operations.
1Overall render lifecycle. ready, failed, and cancelled are terminal states.
queued, processing, ready, failed, cancelled Coarse pipeline phase when one can be mapped to the public vocabulary; null when unavailable or unknown.
queued, preparing, generating, finalizing, ready, failed, cancelled, analyzing, rendering, finishing Best-effort completion percentage from 0 through 100; null when the active pipeline does not report progress.
0 <= x <= 100Short-lived URL of the completed rendered video; null until the render is ready or when no output was produced.
Short-lived alpha/mask video URL for transparent output; null for non-transparent renders or until ready.
ISO 8601 timestamp when the render was created; null when the backing pipeline did not provide it.
ISO 8601 timestamp when the render entered a terminal state; null while it is still active.
Structured failure details when status is failed; otherwise null.
Show child attributes
Show child attributes
Navigation paths for this render. May be absent on records served through the migration proxy.
Show child attributes
Show child attributes

