Download a motion
Download generated motions in FBX or GLB format using the motion download endpoints.
Step 1: Identify your character and motion IDs
You need both the characterId and motionId to download files.
Note: The filename in the URL is customizable for /motion/file/ and /motion/animation/ endpoints. You can use any filename you want (e.g., motion.fbx, walking-animation.glb). The /motion/bundle/ endpoint requires the fixed filename character.glb or character.fbx.
Retargeting: When you download a motion with a specific character ID, retargeting happens automatically—the motion is adapted to work with that character's skeleton and proportions.
Step 2: Download the motion file
- Shell
- Python
- TypeScript
- C#
API_KEY="{{apiKey}}"
CHARACTER_ID="cXi2eAP19XwQ"
MOTION_ID="your-motion-id"
# FBX (includes character mesh)
curl -L "https://uthana.com/motion/file/motion_viewer/$CHARACTER_ID/$MOTION_ID/fbx/motion.fbx" \
-u $API_KEY: \
-o motion.fbx
# GLB (includes character mesh)
curl -L "https://uthana.com/motion/file/motion_viewer/$CHARACTER_ID/$MOTION_ID/glb/motion.glb" \
-u $API_KEY: \
-o motion.glb
import requests
API_KEY = "{{apiKey}}"
CHARACTER_ID = "cXi2eAP19XwQ"
MOTION_ID = "your-motion-id"
fbx_url = f"https://uthana.com/motion/file/motion_viewer/{CHARACTER_ID}/{MOTION_ID}/fbx/motion.fbx"
response = requests.get(fbx_url, auth=(API_KEY, ""))
with open("motion.fbx", "wb") as f:
f.write(response.content)
const API_KEY = "{{apiKey}}";
const CHARACTER_ID = "cXi2eAP19XwQ";
const MOTION_ID = "your-motion-id";
const authString = btoa(`${API_KEY}:`);
const fbxUrl = `https://uthana.com/motion/file/motion_viewer/${CHARACTER_ID}/${MOTION_ID}/fbx/motion.fbx`;
const response = await fetch(fbxUrl, {
headers: {
Authorization: `Basic ${authString}`,
},
});
const data = await response.blob();
var apiKey = "{{apiKey}}";
var characterId = "cXi2eAP19XwQ";
var motionId = "your-motion-id";
var downloadUrl = $"https://uthana.com/motion/file/motion_viewer/{characterId}/{motionId}/fbx/motion.fbx";
var authValue = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{apiKey}:"));
_httpClient.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", authValue);
var bytes = await _httpClient.GetByteArrayAsync(downloadUrl);
Download options
Frame rate (FPS)
You can specify the frame rate for downloaded motions using the fps query parameter. Supported values are 24, 30, and 60.
- Shell
- Python
- TypeScript
- C#
# Download at 30 FPS
curl -L "https://uthana.com/motion/file/motion_viewer/$CHARACTER_ID/$MOTION_ID/fbx/motion.fbx?fps=30" \
-u $API_KEY: \
-o motion-30fps.fbx
# Download at 60 FPS
curl -L "https://uthana.com/motion/file/motion_viewer/$CHARACTER_ID/$MOTION_ID/fbx/motion.fbx?fps=60" \
-u $API_KEY: \
-o motion-60fps.fbx
# Download at 30 FPS
fbx_url = f"https://uthana.com/motion/file/motion_viewer/{CHARACTER_ID}/{MOTION_ID}/fbx/motion.fbx?fps=30"
response = requests.get(fbx_url, auth=(API_KEY, ""))
with open("motion-30fps.fbx", "wb") as f:
f.write(response.content)
async function downloadMotionAtFps(
characterId: string,
motionId: string,
format: "fbx" | "glb",
fps: 24 | 30 | 60,
filename: string = "motion",
) {
const url = `https://uthana.com/motion/file/motion_viewer/${characterId}/${motionId}/${format}/${filename}.${format}?fps=${fps}`;
const response = await fetch(url, {
headers: {
Authorization: `Basic ${authString}`,
},
});
return await response.blob();
}
// Usage
const motion30fps = await downloadMotionAtFps(CHARACTER_ID, MOTION_ID, "fbx", 30);
public async Task<byte[]> DownloadMotionAtFpsAsync(string motionId, string format, int fps, string filename = "motion")
{
var downloadUrl = $"https://uthana.com/motion/file/motion_viewer/{CHARACTER_ID}/{motionId}/{format}/{filename}.{format}?fps={fps}";
var response = await _httpClient.GetAsync(downloadUrl);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsByteArrayAsync();
}
// Usage
var motion30fps = await DownloadMotionAtFpsAsync(motionId, "fbx", 30);
Exclude character mesh
Use the no_mesh query parameter to download animation data without the character mesh. Set no_mesh=true to exclude the mesh, or no_mesh=false to include it (default).
- Shell
- Python
- TypeScript
- C#
# Download FBX without character mesh
curl -L "https://uthana.com/motion/file/motion_viewer/$CHARACTER_ID/$MOTION_ID/fbx/motion.fbx?no_mesh=true" \
-u $API_KEY: \
-o motion-no-mesh.fbx
# Download FBX without character mesh
fbx_url = f"https://uthana.com/motion/file/motion_viewer/{CHARACTER_ID}/{MOTION_ID}/fbx/motion.fbx?no_mesh=true"
response = requests.get(fbx_url, auth=(API_KEY, ""))
with open("motion-no-mesh.fbx", "wb") as f:
f.write(response.content)
async function downloadMotionNoMesh(
characterId: string,
motionId: string,
format: "fbx" | "glb",
filename: string = "motion",
) {
const url = `https://uthana.com/motion/file/motion_viewer/${characterId}/${motionId}/${format}/${filename}.${format}?no_mesh=true`;
const response = await fetch(url, {
headers: {
Authorization: `Basic ${authString}`,
},
});
return await response.blob();
}
public async Task<byte[]> DownloadMotionNoMeshAsync(string motionId, string format, string filename = "motion")
{
var downloadUrl = $"https://uthana.com/motion/file/motion_viewer/{CHARACTER_ID}/{motionId}/{format}/{filename}.{format}?no_mesh=true";
var response = await _httpClient.GetAsync(downloadUrl);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsByteArrayAsync();
}
In-place motion
Use the in_place query parameter to remove horizontal root motion from the animation. Set in_place=true to keep the character in place with no horizontal translation (default: false).
- Shell
- Python
- TypeScript
- C#
# Download motion with character in place
curl -L "https://uthana.com/motion/file/motion_viewer/$CHARACTER_ID/$MOTION_ID/fbx/motion.fbx?in_place=true" \
-u $API_KEY: \
-o motion-in-place.fbx
# Download motion with character in place
fbx_url = f"https://uthana.com/motion/file/motion_viewer/{CHARACTER_ID}/{MOTION_ID}/fbx/motion.fbx?in_place=true"
response = requests.get(fbx_url, auth=(API_KEY, ""))
with open("motion-in-place.fbx", "wb") as f:
f.write(response.content)
async function downloadMotionInPlace(
characterId: string,
motionId: string,
format: "fbx" | "glb",
filename: string = "motion",
) {
const url = `https://uthana.com/motion/file/motion_viewer/${characterId}/${motionId}/${format}/${filename}.${format}?in_place=true`;
const response = await fetch(url, {
headers: {
Authorization: `Basic ${authString}`,
},
});
return await response.blob();
}
public async Task<byte[]> DownloadMotionInPlaceAsync(string motionId, string format, string filename = "motion")
{
var downloadUrl = $"https://uthana.com/motion/file/motion_viewer/{CHARACTER_ID}/{motionId}/{format}/{filename}.{format}?in_place=true";
var response = await _httpClient.GetAsync(downloadUrl);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsByteArrayAsync();
}
Roblox-compatible FBX
ExperimentalUse the roblox_compatible query parameter to export FBX optimized for Roblox Studio. Set roblox_compatible=true to enable Roblox-compatible export.
Caveats:
- Only applies to FBX format (not GLB); using with GLB returns an error
- Only supported for character IDs:
cFB7NoFCUCvf,cg1RuTM77HXu,c8EaC2nVbPS8; using with any other character ID returns an error
- Shell
- Python
- TypeScript
- C#
# Download Roblox-compatible FBX (use a supported character ID)
CHARACTER_ID="cFB7NoFCUCvf"
curl -L "https://uthana.com/motion/file/motion_viewer/$CHARACTER_ID/$MOTION_ID/fbx/motion.fbx?roblox_compatible=true" \
-u $API_KEY: \
-o motion-roblox.fbx
# Download Roblox-compatible FBX (use a supported character ID)
CHARACTER_ID = "cFB7NoFCUCvf"
fbx_url = f"https://uthana.com/motion/file/motion_viewer/{CHARACTER_ID}/{MOTION_ID}/fbx/motion.fbx?roblox_compatible=true"
response = requests.get(fbx_url, auth=(API_KEY, ""))
with open("motion-roblox.fbx", "wb") as f:
f.write(response.content)
async function downloadMotionRoblox(characterId: string, motionId: string, filename: string = "motion") {
const url = `https://uthana.com/motion/file/motion_viewer/${characterId}/${motionId}/fbx/${filename}.fbx?roblox_compatible=true`;
const response = await fetch(url, {
headers: {
Authorization: `Basic ${authString}`,
},
});
return await response.blob();
}
// Usage (use a supported character ID)
const robloxBlob = await downloadMotionRoblox("cFB7NoFCUCvf", MOTION_ID);
// Download Roblox-compatible FBX (use a supported character ID)
var characterId = "cFB7NoFCUCvf";
var downloadUrl = $"https://uthana.com/motion/file/motion_viewer/{characterId}/{motionId}/fbx/motion.fbx?roblox_compatible=true";
var response = await _httpClient.GetAsync(downloadUrl);
response.EnsureSuccessStatusCode();
var bytes = await response.Content.ReadAsByteArrayAsync();
Combining options
You can combine fps, no_mesh, in_place, and roblox_compatible parameters:
# Download at 30 FPS without character mesh
curl -L "https://uthana.com/motion/file/motion_viewer/$CHARACTER_ID/$MOTION_ID/fbx/motion.fbx?fps=30&no_mesh=true" \
-u $API_KEY: \
-o motion-30fps-no-mesh.fbx
# Download at 30 FPS, in-place, with Roblox-compatible FBX (use a supported character ID)
CHARACTER_ID="cFB7NoFCUCvf"
curl -L "https://uthana.com/motion/file/motion_viewer/$CHARACTER_ID/$MOTION_ID/fbx/motion.fbx?fps=30&no_mesh=false&in_place=true&roblox_compatible=true" \
-u $API_KEY: \
-o motion-30fps-roblox.fbx
Motion-only GLB
To download only animation data without the character mesh, you can use the motion-only endpoint or the no_mesh=true parameter:
- Shell
- Python
- TypeScript
- C#
# Using the motion-only endpoint
curl -L "https://uthana.com/motion/animation/motion_viewer/$CHARACTER_ID/$MOTION_ID/glb/motion.glb" \
-u $API_KEY: \
-o motion-only.glb
# Or using no_mesh parameter
curl -L "https://uthana.com/motion/file/motion_viewer/$CHARACTER_ID/$MOTION_ID/glb/motion.glb?no_mesh=true" \
-u $API_KEY: \
-o motion-only.glb
# Using the motion-only endpoint
motion_only_url = f"https://uthana.com/motion/animation/motion_viewer/{CHARACTER_ID}/{MOTION_ID}/glb/motion.glb"
response = requests.get(motion_only_url, auth=(API_KEY, ""))
with open("motion-only.glb", "wb") as f:
f.write(response.content)
const motionOnlyUrl = `https://uthana.com/motion/animation/motion_viewer/${CHARACTER_ID}/${MOTION_ID}/glb/motion.glb`;
const response = await fetch(motionOnlyUrl, {
headers: {
Authorization: `Basic ${authString}`,
},
});
const blob = await response.blob();
var motionOnlyUrl = $"https://uthana.com/motion/animation/motion_viewer/{CHARACTER_ID}/{motionId}/glb/motion.glb";
var response = await _httpClient.GetAsync(motionOnlyUrl);
response.EnsureSuccessStatusCode();
var bytes = await response.Content.ReadAsByteArrayAsync();
Error handling
These endpoints return binary file data on success. On error, they return an HTTP error status. Check response.status (or response.status_code) in your code—the response body may contain error details, but for file downloads the status code is the primary signal.
| Condition | Status code |
|---|---|
Invalid fps (not 24, 30, or 60) | 400 |
Invalid no_mesh (not true or false) | 400 |
Invalid in_place (not true or false) | 400 |
Invalid roblox_compatible (not true or false) | 400 |
roblox_compatible=true with GLB format | 400 |
roblox_compatible=true with unsupported character (not cFB7NoFCUCvf, cg1RuTM77HXu, or c8EaC2nVbPS8) | 400 |
| Invalid motion or character ID, or no access | 404 |
| Permission denied (quota, etc.) | 403 |
| Not logged in | 401 |
Related docs
- Asset management for listing motions and metadata