curl --request POST \
--url https://apis.viggle.ai/v1/motions \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form motion_video='@example-file' \
--form 'motion_video_url=<string>' \
--form name= \
--form type=render \
--form enable_smoothing=false \
--form target_fps=30 \
--form 'task_id=<string>'import requests
url = "https://apis.viggle.ai/v1/motions"
files = { "motion_video": ("example-file", open("example-file", "rb")) }
payload = {
"motion_video_url": "<string>",
"name": "",
"type": "render",
"enable_smoothing": "false",
"target_fps": "30",
"task_id": "<string>"
}
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('motion_video', '<string>');
form.append('motion_video_url', '<string>');
form.append('name', '');
form.append('type', 'render');
form.append('enable_smoothing', 'false');
form.append('target_fps', '30');
form.append('task_id', '<string>');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://apis.viggle.ai/v1/motions', 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/motions",
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=\"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=\"name\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"type\"\r\n\r\nrender\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"enable_smoothing\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"target_fps\"\r\n\r\n30\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"task_id\"\r\n\r\n<string>\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/motions"
payload := strings.NewReader("-----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=\"name\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"type\"\r\n\r\nrender\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"enable_smoothing\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"target_fps\"\r\n\r\n30\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"task_id\"\r\n\r\n<string>\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/motions")
.header("Authorization", "Bearer <token>")
.body("-----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=\"name\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"type\"\r\n\r\nrender\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"enable_smoothing\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"target_fps\"\r\n\r\n30\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"task_id\"\r\n\r\n<string>\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://apis.viggle.ai/v1/motions")
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=\"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=\"name\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"type\"\r\n\r\nrender\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"enable_smoothing\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"target_fps\"\r\n\r\n30\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"task_id\"\r\n\r\n<string>\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"id": "<string>",
"status": "queued",
"name": "<string>",
"progress": 50,
"capabilities": [
"<string>"
],
"created_at": "<string>",
"completed_at": "<string>",
"error": {
"code": "<string>",
"message": "<string>",
"request_id": "<string>"
},
"type": "render",
"vsplat": {
"status": "queued",
"error": {
"code": "<string>",
"message": "<string>",
"request_id": "<string>"
}
},
"glb": {
"status": "queued",
"skeletons": [
"mixamo"
],
"error": {
"code": "<string>",
"message": "<string>",
"request_id": "<string>"
}
}
}{
"error": {
"code": "authentication_required",
"message": "<string>",
"request_id": "<string>"
}
}{
"error": {
"code": "authentication_required",
"message": "<string>",
"request_id": "<string>"
}
}{
"error": {
"code": "authentication_required",
"message": "<string>",
"request_id": "<string>"
}
}{
"error": {
"code": "authentication_required",
"message": "<string>",
"request_id": "<string>"
}
}{
"error": {
"code": "authentication_required",
"message": "<string>",
"request_id": "<string>"
}
}{
"error": {
"code": "authentication_required",
"message": "<string>",
"request_id": "<string>"
}
}{
"error": {
"code": "authentication_required",
"message": "<string>",
"request_id": "<string>"
}
}从文本创建动作
通过文本提示词生成可复用的 3D 动作。
curl --request POST \
--url https://apis.viggle.ai/v1/motions \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form motion_video='@example-file' \
--form 'motion_video_url=<string>' \
--form name= \
--form type=render \
--form enable_smoothing=false \
--form target_fps=30 \
--form 'task_id=<string>'import requests
url = "https://apis.viggle.ai/v1/motions"
files = { "motion_video": ("example-file", open("example-file", "rb")) }
payload = {
"motion_video_url": "<string>",
"name": "",
"type": "render",
"enable_smoothing": "false",
"target_fps": "30",
"task_id": "<string>"
}
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('motion_video', '<string>');
form.append('motion_video_url', '<string>');
form.append('name', '');
form.append('type', 'render');
form.append('enable_smoothing', 'false');
form.append('target_fps', '30');
form.append('task_id', '<string>');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://apis.viggle.ai/v1/motions', 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/motions",
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=\"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=\"name\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"type\"\r\n\r\nrender\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"enable_smoothing\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"target_fps\"\r\n\r\n30\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"task_id\"\r\n\r\n<string>\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/motions"
payload := strings.NewReader("-----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=\"name\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"type\"\r\n\r\nrender\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"enable_smoothing\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"target_fps\"\r\n\r\n30\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"task_id\"\r\n\r\n<string>\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/motions")
.header("Authorization", "Bearer <token>")
.body("-----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=\"name\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"type\"\r\n\r\nrender\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"enable_smoothing\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"target_fps\"\r\n\r\n30\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"task_id\"\r\n\r\n<string>\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://apis.viggle.ai/v1/motions")
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=\"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=\"name\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"type\"\r\n\r\nrender\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"enable_smoothing\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"target_fps\"\r\n\r\n30\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"task_id\"\r\n\r\n<string>\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"id": "<string>",
"status": "queued",
"name": "<string>",
"progress": 50,
"capabilities": [
"<string>"
],
"created_at": "<string>",
"completed_at": "<string>",
"error": {
"code": "<string>",
"message": "<string>",
"request_id": "<string>"
},
"type": "render",
"vsplat": {
"status": "queued",
"error": {
"code": "<string>",
"message": "<string>",
"request_id": "<string>"
}
},
"glb": {
"status": "queued",
"skeletons": [
"mixamo"
],
"error": {
"code": "<string>",
"message": "<string>",
"request_id": "<string>"
}
}
}{
"error": {
"code": "authentication_required",
"message": "<string>",
"request_id": "<string>"
}
}{
"error": {
"code": "authentication_required",
"message": "<string>",
"request_id": "<string>"
}
}{
"error": {
"code": "authentication_required",
"message": "<string>",
"request_id": "<string>"
}
}{
"error": {
"code": "authentication_required",
"message": "<string>",
"request_id": "<string>"
}
}{
"error": {
"code": "authentication_required",
"message": "<string>",
"request_id": "<string>"
}
}{
"error": {
"code": "authentication_required",
"message": "<string>",
"request_id": "<string>"
}
}{
"error": {
"code": "authentication_required",
"message": "<string>",
"request_id": "<string>"
}
}application/json,即可通过提示词生成 3D 骨骼动画。文本生成的动作始终为 type: glb,因为它没有可供 2D 渲染使用的视频。
本模式与从视频创建动作共用 POST /v1/motions,但请求内容类型和字段不同。它替代已移除的 POST /v1/animations/generate。
请求参数
使用application/json。
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
text | string(1–400 字符) | 是 | — | 动作的自然语言描述。可写明动作、方向、节奏及相关身体部位,使结果更明确。 |
duration_seconds | number | 否 | 5 | 动画时长,单位为秒,按 30 FPS 四舍五入到最近帧。超出 [3, 60] 返回 400,不会自动截断。不影响固定 10 积分价格。 |
guidance_scale | number | 否 | 5 | 提示词对生成的引导强度,范围 0–30,越界返回 400。 |
smooth | boolean | 否 | true | 是否平滑动画,减少相邻帧关节突变。 |
name | string | 否 | "" | 详情和列表显示的名称,不影响生成。 |
task_id | string | 否 | — | 调用者自定义幂等 ID,用于安全重试。同一生成任务使用相同值,不同任务使用新值。 |
响应参数
返回200 OK 和排队中的 Motion 对象。
| 字段 | 类型 | 始终返回 | 说明 |
|---|---|---|---|
id | string | 是 | 以 mot_ 开头的公开 ID,请原样保存。 |
status | string | 是 | 通常为 queued;终态为 ready/failed。 |
name | string | 是 | 传入的名称,或空字符串。 |
progress | integer 或 null | 是 | 可用时为 0–100 的进度。 |
capabilities | string[] | 是 | 文本生成动作没有 2D 渲染能力,因此为空。 |
created_at / completed_at | string 或 null | 是 | ISO 8601 时间。 |
error | object 或 null | 是 | 失败时的结构化错误详情。 |
type | string | 是 | 固定为 glb。 |
glb | object 或 null | 是 | 生成中及完成后包含 {status, skeletons, error}。就绪时同时提供 mixamo 和 metahuman。 |
vsplat | null | 是 | 动作始终为 null。 |
{
"id": "mot_550e8400-e29b-41d4-a716-446655440000",
"status": "queued",
"name": "Cartwheel",
"progress": 0,
"capabilities": [],
"created_at": "2026-07-21T10:00:00+00:00",
"completed_at": null,
"error": null,
"type": "glb",
"vsplat": null,
"glb": {
"status": "queued",
"skeletons": [],
"error": null
}
}
示例
payload := strings.NewReader(`{"text":"a person doing a cartwheel","duration_seconds":5,"guidance_scale":7.5,"smooth":true,"name":"Cartwheel","task_id":"motion-cartwheel-001"}`)
req, _ := http.NewRequest("POST", "https://apis.viggle.ai/v1/motions", payload)
req.Header.Set("Authorization", "Bearer "+os.Getenv("VIGGLE_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
const response = await fetch("https://apis.viggle.ai/v1/motions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VIGGLE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
text: "a person doing a cartwheel",
duration_seconds: 5,
guidance_scale: 7.5,
smooth: true,
name: "Cartwheel",
task_id: "motion-cartwheel-001",
}),
});
const motion = await response.json();
import os
import requests
response = requests.post(
"https://apis.viggle.ai/v1/motions",
headers={"Authorization": f"Bearer {os.environ['VIGGLE_API_KEY']}"},
json={
"text": "a person doing a cartwheel",
"duration_seconds": 5,
"guidance_scale": 7.5,
"smooth": True,
"name": "Cartwheel",
"task_id": "motion-cartwheel-001",
},
)
response.raise_for_status()
motion = response.json()
curl -X POST "https://apis.viggle.ai/v1/motions" \
-H "Authorization: Bearer $VIGGLE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"a person doing a cartwheel","duration_seconds":5,"guidance_scale":7.5,"smooth":true,"name":"Cartwheel","task_id":"motion-cartwheel-001"}'
下一步
使用获取动作轮询,直到status=ready,再调用导出 3D 动作获取 mixamo 或 metahuman GLB。
快速入门:文本生成动作
授权
服务端 SDK 使用项目 API 密钥,Remote MCP 使用 OAuth 访问令牌。不要在浏览器代码中暴露项目密钥。
请求头
可选的调用者自定义关联 ID,最长 128 字符。服务会在响应头返回实际使用的值,便于追踪和支持排查。
1 - 128可选来源标签,最长 128 字符,用于识别 SDK、集成、产品入口或内部工作流。
1 - 128请求体
motion_video 与 motion_video_url 必须二选一。type 决定输出,提取参数仅适用于 glb/all。暂存路径、输出位置、提取模板、角色 PKL、关节配置和追踪蒙版由服务管理。
直接上传的驱动视频,提取其中可见的身体动作用于复用。与 motion_video_url 二选一。
可公开访问的驱动视频 HTTP(S) URL,替代文件上传,并在开始接收素材时保持可访问。
render/all 的可选名称。仅 glb 的动作当前返回空名称。
render 创建 2D 动作(不收费);glb 提取 3D 动画(源视频时长向上取整后按 5 积分/秒);all 同时创建并合并费用。
render, glb, all 是否平滑提取的关节动作,减少帧间抖动,也可能略微弱化突然的动作。
提取和 GLB 时间轴的目标帧率,必须大于 0。省略时使用 30 FPS。
x > 0type=glb 的调用者自定义幂等 ID。同一任务重试使用稳定且唯一的值;type=all 使用新建动作 ID 作为配套提取任务的键。
1响应
请求成功。
角色和动作共用的资源结构。列表中的 progress 为 null,顶层 error 当前始终为 null,失败请检查 status。角色 type 为 render、vsplat、all,动作为 render、glb、all,旧资源默认报告 render。vsplat/glb 仅包含提取状态,下载链接通过角色或动作的 /export 获取。未请求提取时对应字段为 null。all 需等 2D 与 3D 均就绪才为 ready;任一失败则顶层 failed,子对象状态可用于定位。
素材公开 ID。角色通常以 char_ 开头,动作通常以 mot_ 开头。
1素材整体生命周期。所需能力就绪后才能用于渲染或导出。
queued, processing, ready, failed, cancelled 创建时提供或导入时生成的显示名称;不支持名称的流程可能返回空字符串。
详情中尽可能提供 0–100 的处理百分比,列表中或不可用时为 null。
0 <= x <= 100素材具备的能力。就绪且可渲染时包含 video_render。
带 UTC 偏移的 ISO 8601 时间,例如 2026-07-31T09:15:22+00:00。
所有请求处理进入终态时的 ISO 8601 时间,带 UTC 偏移;处理中为 null。
保留的顶层错误详情,目前始终为 null。通过 status 判断失败,并在适用时检查提取子对象。
Show child attributes
Show child attributes
创建时请求的输出。render 为可渲染的 2D 素材,vsplat/glb 为 3D 输出,all 同时请求两种支持的输出。
render, vsplat, glb, all 角色 vsplat 提取状态。动作及 type=render 的角色为 null。这里只返回状态,不回显 model_precision 等创建参数。
Show child attributes
Show child attributes
动作 3D 动画提取或生成状态。角色及 type=render 的动作为 null。
Show child attributes
Show child attributes

