准备渲染
curl --request POST \
--url https://apis.viggle.ai/v1/renders/prepare \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"character": {
"kind": "upload",
"filename": "<string>",
"content_type": "<string>",
"bytes": 2
},
"motion": {
"kind": "upload",
"filename": "<string>",
"content_type": "<string>",
"bytes": 2
},
"output": {}
}
'import requests
url = "https://apis.viggle.ai/v1/renders/prepare"
payload = {
"character": {
"kind": "upload",
"filename": "<string>",
"content_type": "<string>",
"bytes": 2
},
"motion": {
"kind": "upload",
"filename": "<string>",
"content_type": "<string>",
"bytes": 2
},
"output": {}
}
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({
character: {kind: 'upload', filename: '<string>', content_type: '<string>', bytes: 2},
motion: {kind: 'upload', filename: '<string>', content_type: '<string>', bytes: 2},
output: {}
})
};
fetch('https://apis.viggle.ai/v1/renders/prepare', 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/prepare",
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([
'character' => [
'kind' => 'upload',
'filename' => '<string>',
'content_type' => '<string>',
'bytes' => 2
],
'motion' => [
'kind' => 'upload',
'filename' => '<string>',
'content_type' => '<string>',
'bytes' => 2
],
'output' => [
]
]),
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://apis.viggle.ai/v1/renders/prepare"
payload := strings.NewReader("{\n \"character\": {\n \"kind\": \"upload\",\n \"filename\": \"<string>\",\n \"content_type\": \"<string>\",\n \"bytes\": 2\n },\n \"motion\": {\n \"kind\": \"upload\",\n \"filename\": \"<string>\",\n \"content_type\": \"<string>\",\n \"bytes\": 2\n },\n \"output\": {}\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://apis.viggle.ai/v1/renders/prepare")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"character\": {\n \"kind\": \"upload\",\n \"filename\": \"<string>\",\n \"content_type\": \"<string>\",\n \"bytes\": 2\n },\n \"motion\": {\n \"kind\": \"upload\",\n \"filename\": \"<string>\",\n \"content_type\": \"<string>\",\n \"bytes\": 2\n },\n \"output\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://apis.viggle.ai/v1/renders/prepare")
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 \"character\": {\n \"kind\": \"upload\",\n \"filename\": \"<string>\",\n \"content_type\": \"<string>\",\n \"bytes\": 2\n },\n \"motion\": {\n \"kind\": \"upload\",\n \"filename\": \"<string>\",\n \"content_type\": \"<string>\",\n \"bytes\": 2\n },\n \"output\": {}\n}"
response = http.request(request)
puts response.read_body{
"draft_id": "<string>",
"state": "awaiting_uploads",
"uploads": [
{
"slot": "character",
"upload_handle": "<string>",
"method": "PUT",
"url": "<string>",
"required_headers": {},
"expires_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
}
}
}准备渲染
准备渲染草稿,并获取待上传素材的直传计划。
POST
/
v1
/
renders
/
prepare
准备渲染
curl --request POST \
--url https://apis.viggle.ai/v1/renders/prepare \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"character": {
"kind": "upload",
"filename": "<string>",
"content_type": "<string>",
"bytes": 2
},
"motion": {
"kind": "upload",
"filename": "<string>",
"content_type": "<string>",
"bytes": 2
},
"output": {}
}
'import requests
url = "https://apis.viggle.ai/v1/renders/prepare"
payload = {
"character": {
"kind": "upload",
"filename": "<string>",
"content_type": "<string>",
"bytes": 2
},
"motion": {
"kind": "upload",
"filename": "<string>",
"content_type": "<string>",
"bytes": 2
},
"output": {}
}
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({
character: {kind: 'upload', filename: '<string>', content_type: '<string>', bytes: 2},
motion: {kind: 'upload', filename: '<string>', content_type: '<string>', bytes: 2},
output: {}
})
};
fetch('https://apis.viggle.ai/v1/renders/prepare', 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/prepare",
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([
'character' => [
'kind' => 'upload',
'filename' => '<string>',
'content_type' => '<string>',
'bytes' => 2
],
'motion' => [
'kind' => 'upload',
'filename' => '<string>',
'content_type' => '<string>',
'bytes' => 2
],
'output' => [
]
]),
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://apis.viggle.ai/v1/renders/prepare"
payload := strings.NewReader("{\n \"character\": {\n \"kind\": \"upload\",\n \"filename\": \"<string>\",\n \"content_type\": \"<string>\",\n \"bytes\": 2\n },\n \"motion\": {\n \"kind\": \"upload\",\n \"filename\": \"<string>\",\n \"content_type\": \"<string>\",\n \"bytes\": 2\n },\n \"output\": {}\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://apis.viggle.ai/v1/renders/prepare")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"character\": {\n \"kind\": \"upload\",\n \"filename\": \"<string>\",\n \"content_type\": \"<string>\",\n \"bytes\": 2\n },\n \"motion\": {\n \"kind\": \"upload\",\n \"filename\": \"<string>\",\n \"content_type\": \"<string>\",\n \"bytes\": 2\n },\n \"output\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://apis.viggle.ai/v1/renders/prepare")
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 \"character\": {\n \"kind\": \"upload\",\n \"filename\": \"<string>\",\n \"content_type\": \"<string>\",\n \"bytes\": 2\n },\n \"motion\": {\n \"kind\": \"upload\",\n \"filename\": \"<string>\",\n \"content_type\": \"<string>\",\n \"bytes\": 2\n },\n \"output\": {}\n}"
response = http.request(request)
puts response.read_body{
"draft_id": "<string>",
"state": "awaiting_uploads",
"uploads": [
{
"slot": "character",
"upload_handle": "<string>",
"method": "PUT",
"url": "<string>",
"required_headers": {},
"expires_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
}
}
}开始草稿渲染流程。
prepare 返回短期有效的直传 URL,无需在 JSON 中嵌入媒体字节。将角色和动作文件直接上传到这些 URL,再携带 draft_id 调用渲染视频的 JSON 模式。
请求参数
使用application/json。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
character | object | 是 | 角色来源描述,三选一:直传元数据、公开 HTTPS URL、已就绪的可复用角色 ID。 |
motion | object | 是 | 动作来源描述:直传元数据、公开 HTTPS URL、已就绪动作 ID 或官方动作模板 ID。 |
character.kind / motion.kind | string | 是 | 来源类型:upload、url、asset;动作还支持 official_motion。该值决定其他必填字段。 |
character.filename / motion.filename | string | kind=upload 时 | 含扩展名的原始文件名,用于后续上传识别及校验。 |
character.content_type / motion.content_type | string | kind=upload 时 | MIME 类型,如 image/png 或 video/mp4。直传时 Content-Type 需保持相同。 |
character.bytes / motion.bytes | integer | kind=upload 时 | 大于 0 的精确文件字节数,上传对象大小将与此值核对。 |
character.url / motion.url | string | kind=url 时 | 可公开访问的 HTTPS 素材 URL,草稿准备及提交期间应保持可访问。 |
character.asset_id / motion.asset_id | string | kind=asset 时 | 当前调用者拥有的就绪素材 ID,角色槽使用角色 ID,动作槽使用动作 ID。 |
motion.motion_id | string | kind=official_motion 时 | 官方 Viggle 动作模板 ID,仅适用于动作槽。 |
output | object | 否 | 输出设置,省略时使用默认背景处理和源宽高比。 |
output.background_mode | string | 否 | original、green、white 或 transparent。green/white 会转换为对应颜色的纯色背景。 |
output.aspect_ratio | string | 否 | 当前仅支持 source。虽然枚举包含 16:9、9:16、1:1,但由于流程尚不支持重新构图,传入这些值会被拒绝,不会静默忽略。 |
kind: "upload" 仅声明上传槽,此时不发送文件内容。先提供 filename、content_type 和 bytes,随后向返回的 URL PUT 原始字节。省略 output 时使用默认背景和宽高比,与直接 multipart 渲染一致。
响应参数
返回200 OK。
| 字段 | 类型 | 始终返回 | 说明 |
|---|---|---|---|
draft_id | string | 是 | 提交到渲染视频的 JSON 模式。 |
state | string | 是 | 有上传槽时为 awaiting_uploads,否则为 ready_to_create。 |
uploads | array | 是 | 每个上传槽对应一项。全部使用 url/asset/official_motion 时为空。 |
uploads[].slot | string | 是 | character 或 motion。 |
uploads[].upload_handle | string | 是 | 创建渲染时在 upload_completions 中原样传回。 |
uploads[].method | string | 是 | 固定为 PUT。 |
uploads[].url | string | 是 | 短期有效的直传 URL。 |
uploads[].required_headers | object | 是 | PUT 请求所需的请求头,格式为 {name: value}。 |
uploads[].expires_at | string | 是 | 上传 URL 的 ISO 8601 过期时间。 |
{
"draft_id": "draft_abc123",
"state": "awaiting_uploads",
"uploads": [
{
"slot": "character",
"upload_handle": "up_1",
"method": "PUT",
"url": "https://uploads.viggle.ai/draft_abc123/character?sig=...",
"required_headers": { "Content-Type": "image/png" },
"expires_at": "2026-07-31T09:30:00+00:00"
}
]
}
示例
curl -X POST "https://apis.viggle.ai/v1/renders/prepare" \
-H "Authorization: Bearer $VIGGLE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"character": {"kind":"upload","filename":"character.png","content_type":"image/png","bytes":204800},
"motion": {"kind":"asset","asset_id":"mot_456def"}
}'
import os, requests
response = requests.post(
"https://apis.viggle.ai/v1/renders/prepare",
headers={"Authorization": f"Bearer {os.environ['VIGGLE_API_KEY']}"},
json={
"character": {"kind": "upload", "filename": "character.png", "content_type": "image/png", "bytes": 204800},
"motion": {"kind": "asset", "asset_id": "mot_456def"},
},
)
response.raise_for_status()
draft = response.json()
for upload in draft["uploads"]:
with open("character.png", "rb") as f:
put = requests.put(upload["url"], data=f, headers=upload["required_headers"])
put.raise_for_status()
下一步
在expires_at 之前,携带 uploads[].required_headers,将每个文件的原始字节 PUT 到 uploads[].url。然后以 Content-Type: application/json 调用渲染视频,提交 draft_id 及每个已上传槽对应的 upload_completions。Authorizations
服务端 SDK 使用项目 API 密钥,Remote MCP 使用 OAuth 访问令牌。不要在浏览器代码中暴露项目密钥。
Headers
可选的调用者自定义关联 ID,最长 128 字符。服务会在响应头返回实际使用的值,便于追踪和支持排查。
Required string length:
1 - 128可选来源标签,最长 128 字符,用于识别 SDK、集成、产品入口或内部工作流。
Required string length:
1 - 128Body
application/json

