curl --request GET \
--url https://apis.viggle.ai/v1/motions/{motion_id}/export \
--header 'Authorization: Bearer <token>'import requests
url = "https://apis.viggle.ai/v1/motions/{motion_id}/export"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://apis.viggle.ai/v1/motions/{motion_id}/export', 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/{motion_id}/export",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://apis.viggle.ai/v1/motions/{motion_id}/export"
req, _ := http.NewRequest("GET", url, nil)
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.get("https://apis.viggle.ai/v1/motions/{motion_id}/export")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://apis.viggle.ai/v1/motions/{motion_id}/export")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"id": "<string>",
"status": "queued",
"download_type": "glb",
"mixamo_url": "<string>",
"metahuman_url": "<string>",
"metahuman_body_url": "<string>",
"thumbnail_url": "<string>",
"created_at": "<string>",
"updated_at": "<string>",
"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>"
}
}Export 3D Motion (for 3D/game engines)
Get signed download URLs for a Motion’s 3D animation extraction or generation.
curl --request GET \
--url https://apis.viggle.ai/v1/motions/{motion_id}/export \
--header 'Authorization: Bearer <token>'import requests
url = "https://apis.viggle.ai/v1/motions/{motion_id}/export"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://apis.viggle.ai/v1/motions/{motion_id}/export', 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/{motion_id}/export",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://apis.viggle.ai/v1/motions/{motion_id}/export"
req, _ := http.NewRequest("GET", url, nil)
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.get("https://apis.viggle.ai/v1/motions/{motion_id}/export")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://apis.viggle.ai/v1/motions/{motion_id}/export")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"id": "<string>",
"status": "queued",
"download_type": "glb",
"mixamo_url": "<string>",
"metahuman_url": "<string>",
"metahuman_body_url": "<string>",
"thumbnail_url": "<string>",
"created_at": "<string>",
"updated_at": "<string>",
"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>"
}
}type glb or all, or generated from text via POST /v1/motions with application/json; a render-only Motion has nothing to export. This replaces the old, now-removed GET /v1/animations/{animation_id}.
download_type selects the file format — glb or fbx — not a skeleton. Every request resolves all three rig variants at once for that format: mixamo_url, metahuman_url, and metahuman_body_url. All three rigs, in both formats, are always generated up front from the same pass, so switching download_type on the same motion_id never triggers new work or waits on a regeneration. GLB works with Mixamo, MetaHuman, and other Unity-compatible animation workflows; FBX is the same rigs/animation baked as FBX files, for pipelines (Unreal, Blender, Maya, etc.) that expect that format instead.
mixamo_url is a general-purpose, 50-joint rig used across Mixamo, Unity, Unreal, and most DCC tools; it normally needs retargeting onto your own character mesh. For a Viggle vsplat Character, metahuman_url is the 441-joint full rig corresponding to joint_set=full, while metahuman_body_url is the 86-joint body-only rig corresponding to joint_set=body. Motion export currently has no dedicated 119-joint output for joint_set=expression.Request parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
motion_id | string | Yes | Complete Motion ID returned by Create, Generate, Import, or List Motions. The Motion must contain a requested 3D animation output to expose an export. |
download_type | string | No | Selects the pre-generated file format to download: glb or fbx. All three rig variants for that format come back together. Omit to retrieve status only. |
Response parameters
Returns200 OK.
| Field | Type | Always present | Description |
|---|---|---|---|
id | string | Yes | Requested public Motion ID. |
status | string | Yes | queued, processing, ready, or failed. |
download_type | string or null | Yes | Echoes the requested download_type — the file format shared by the three rig URLs below. null if omitted. |
mixamo_url | string or null | Yes | Signed URL for the Mixamo rig in the requested download_type’s format, once ready; otherwise null. Always present, regardless of which format (or none) was requested. |
metahuman_url | string or null | Yes | Signed URL for the 441-joint MetaHuman rig (full facial) in the requested format, once ready; otherwise null. Always present. |
metahuman_body_url | string or null | Yes | Signed URL for the 86-joint body-only MetaHuman rig in the requested format, once ready; otherwise null. Always present. |
thumbnail_url | null | Yes | Always null — animations have no thumbnail. |
created_at | string or null | Yes | ISO 8601 timestamp. May be an empty string on a task that has not recorded one. |
updated_at | string or null | Yes | ISO 8601 timestamp of the most recent update. |
error | object or null | Yes | Structured failure details when status is failed. |
download_type value, there’s nothing here that’s conditionally omitted. A rig that isn’t ready yet (or doesn’t apply) simply stays null rather than causing an error; it’s never dropped from the response.
{
"id": "mot_456def",
"status": "ready",
"download_type": "glb",
"mixamo_url": "https://assets.viggle.ai/results/mixamo.glb",
"metahuman_url": "https://assets.viggle.ai/results/metahuman.glb",
"metahuman_body_url": "https://assets.viggle.ai/results/metahuman_body.glb",
"thumbnail_url": null,
"created_at": "2026-07-21T10:00:00+00:00",
"updated_at": "2026-07-21T10:01:00+00:00",
"error": null
}
fbx for the same Motion instead — no regeneration needed, and the same three fields carry FBX URLs this time:
{
"id": "mot_456def",
"status": "ready",
"download_type": "fbx",
"mixamo_url": "https://assets.viggle.ai/results/mixamo.fbx",
"metahuman_url": "https://assets.viggle.ai/results/metahuman.fbx",
"metahuman_body_url": "https://assets.viggle.ai/results/metahuman_body.fbx",
"thumbnail_url": null,
"created_at": "2026-07-21T10:00:00+00:00",
"updated_at": "2026-07-21T10:01:00+00:00",
"error": null
}
download_type (status check only) returns all three URL fields as null:
{
"id": "mot_456def",
"status": "ready",
"download_type": null,
"mixamo_url": null,
"metahuman_url": null,
"metahuman_body_url": null,
"thumbnail_url": null,
"created_at": "2026-07-21T10:00:00+00:00",
"updated_at": "2026-07-21T10:01:00+00:00",
"error": null
}
Examples
req,_:=http.NewRequest("GET","https://apis.viggle.ai/v1/motions/mot_456def/export?download_type=glb",nil); req.Header.Set("Authorization","Bearer "+os.Getenv("VIGGLE_API_KEY")); 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/mot_456def/export?download_type=glb",{headers:{Authorization:`Bearer ${process.env.VIGGLE_API_KEY}`}}); const exported=await response.json();
import os, requests
response=requests.get("https://apis.viggle.ai/v1/motions/mot_456def/export",params={"download_type":"glb"},headers={"Authorization":f"Bearer {os.environ['VIGGLE_API_KEY']}"}); response.raise_for_status(); exported=response.json()
curl "https://apis.viggle.ai/v1/motions/mot_456def/export?download_type=glb" -H "Authorization: Bearer $VIGGLE_API_KEY"
download_type again rather than caching the URL.download_type values other than glb or fbx answer 400 INVALID_REQUEST — “invalid download_type”.Next step
A text-generated Motion has no 2D render counterpart — only this export applies. A video-sourcedglb/all Motion also keeps its video_render capability; use Render Video (from Character and/or Motion) with motion_id for that side once it’s ready.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 - 128Path Parameters
Public Motion ID returned by motion creation or listing. It normally begins with mot_ and must identify a Motion owned by the caller.
1Query Parameters
Selects the pre-generated file format to return as signed URLs: glb or fbx. All three rig variants (mixamo_url, metahuman_url, metahuman_body_url) for that format come back together.
glb, fbx Response
Current animation export state.
A motion's 3D animation extraction (video source) or generation
(text source), for download. download_type selects the file
format — glb or fbx — not a skeleton; each request resolves all
three rig variants for that format at once (mixamo_url,
metahuman_url, metahuman_body_url). All three rigs, in both
formats, are generated up front from the same underlying pass, so
switching download_type never triggers new work or a wait. The
three URL fields are always present in the response — a rig that
isn't ready yet simply stays null rather than being omitted or
erroring. Download URLs are short-lived and re-signed on every
read. Formerly AnimationResource, returned by the now-removed
GET /v1/animations/{animation_id}.
ID of the Motion whose 3D animation export is represented.
1Current lifecycle of the 3D animation extraction or generation. Download only when this is ready and the relevant rig URL is populated.
queued, processing, ready, failed, cancelled Echoes the requested download_type — the file format shared by mixamo_url/metahuman_url/metahuman_body_url below.
glb, fbx, null Short-lived signed URL for the Mixamo rig in the requested download_type format, once ready; otherwise null. Always present, regardless of which format (or none) was requested.
Short-lived signed URL for the 441-joint MetaHuman rig (full facial) in the requested download_type format, once ready; otherwise null. Always present.
Short-lived signed URL for the 86-joint body-only MetaHuman rig in the requested download_type format, once ready; otherwise null. Always present.
Reserved for response-shape compatibility and always null because Motion animation exports do not generate thumbnails.
ISO 8601 timestamp with a UTC offset. May be an empty string on a task that has not recorded one.
ISO 8601 timestamp with a UTC offset for the latest 3D animation state update; null if unavailable.
Structured extraction or generation failure when status is failed; otherwise null.
Show child attributes
Show child attributes

