GraphQL API
The Uthana GraphQL API provides a flexible way to query and mutate animation data.
Endpoint
https://uthana.com/graphql
Authentication
Include your API key in the basic Authorization header, base64-encoded with a colon:
AUTH_STRING=$(echo -n "{{apiKey}}:" | base64)
curl -H "Authorization: Basic $AUTH_STRING" \
https://uthana.com/graphql
or in the query string (plaintext):
https://uthana.com/graphql?apikey={{apiKey}}
Schema
Queries
type Query {
# Get the current user's organization
org: Org
# Get the current user
user: User
# Get a motion download allowance
motion_download_allowed(character_id: String, motion_id: String): MotionDownloadAllowed
# List all characters available to the user
characters: [Character!]
# Get a motion by ID
motion(id: String): Motion
# List motions, with optional filters
motions(character_id: String, app_ids: [String!], methods: [String]): [Motion!]
# Search for labels by description
label_search(query: String, limit: Int = 20): [LabelSearchResult!]
# Get a job by job ID
job(job_id: String): Job
# List jobs, optionally filtered by method
jobs(method: String): [Job!]
}
Org
id:StringYour organization's IDname:StringYour organization's namemotion_download_secs_per_month:FloatThe number of seconds of motion downloads per month allocated to the organizationmotion_download_secs_per_month_remaining:FloatThe number of seconds of motion downloads per month remaining for the organizationcharacters_allowed:IntThe maximum number of characters your organization can create per monthcharacters_allowed_remaining:IntThe number of character creation (upload, generate, or rig) slots remaining for the current monthusers:[User]The users in your organization
query Org {
org {
id
name
motion_download_secs_per_month
motion_download_secs_per_month_remaining
characters_allowed
characters_allowed_remaining
users {
id
name
email
email_verified
tz
roles
}
}
}
User
id:StringYour user IDname:StringYour nameemail:StringYour email addressemail_verified:BooleanWhether your email address is verifiedtz:StringYour timezone at last loginroles:[String]Your rolesorg:OrgYour organizationcreated:StringYour account creation timestampupdated:StringYour account last update timestamp
query User {
user {
id
name
email
email_verified
tz
roles
created
updated
org {
id
name
}
}
}
MotionDownloadAllowed
allowed:BooleanWhether the motion download is allowedreason:StringThe reason the motion download is allowed or notcharacter_id:StringThe character IDmotion_id:StringThe motion ID
query MotionDownloadAllowed($character_id: String, $motion_id: String) {
motion_download_allowed(character_id: $character_id, motion_id: $motion_id) {
allowed
reason
}
}
Character
id:Stringname:StringCharacter nameorg_id:StringCharacter organization IDassets:[Asset]Character assetscreated:StringCharacter creation timestampupdated:StringCharacter last update timestampdeleted:String|nullCharacter deletion timestamp, ornullif the character has not been deleted
query Character($id: String!) {
character(id: $id) {
id
name
org_id
created
updated
deleted
assets {
id
filename
}
}
}
Characters
[Character]List ofCharacterobjects in your organization
query Characters {
characters {
id
name
# ...etc. (see Character object)
}
}
Motion
id:StringMotion IDname:StringMotion nameorg_id:StringMotion organization IDtags:ObjectMotion tagsassets:[Asset]Motion assetslabels:[Label]Motion labelsrating:MotionRating|nullMotion rating, ornullif the motion has not been ratedfavorite:MotionFavorite|nullMotion favorite, ornullif the motion has not been favoritedcreated:StringMotion creation timestampupdated:StringMotion last update timestampdeleted:String|nullMotion deletion timestamp, ornullif the motion has not been deleted
{ MotionRating }
user_id:StringUser ID who rated the motionscore:IntThe rating score, must be 0 (downvote) or 1 (upvote)created:StringRating creation timestampupdated:StringRating last update timestamp
{ MotionFavorite }
user_id:StringUser ID who favorited the motionmotion_id:StringMotion IDcreated:StringFavorite creation timestampupdated:StringFavorite last update timestamp
query Motion($id: String!) {
motion(id: $id) {
id
name
tags
assets {
id
filename
}
rating {
user_id
score
created
}
favorite {
user_id
created
}
created
updated
deleted
}
}
Motions
[Motion]The motions in your organization
query Motions {
motions {
id
name
# ...etc. (see Motion object)
}
}
Asset
id:Stringuid:StringAsset internal UUIDfilename:StringAsset original filenamemimetype:StringAsset mimetypetype:StringAsset type, typically:bundlesha256:StringAsset SHA-256 hashmetadata:ObjectAsset metadatasize:IntAsset size in bytescreated:StringAsset creation timestampupdated:StringAsset last update timestampdeleted:String|nullAsset deletion timestamp, ornullif the asset has not been deleted
Note:
Assetobjects are children ofCharacterandMotion.
Job
Note: The
created_at,started_at, andended_atfields are timestamps for when a job was created, started, and ended, respectively. These are not the timestamps of the database record, and as such have intentionally different names.
id:Stringstatus:StringJob status, one of:RESERVED,FINISHED,FAILED,READYcreated_at:StringJob creation timestampstarted_at:StringJob start timestampended_at:StringJob end timestampmethod:StringJob motion methodargs:ObjectJob argumentsresult:ObjectJob result
query Job($id: String!) {
job(id: $id) {
id
status
started_at
ended_at
result
}
}
Jobs
[Job]List ofJobobjects
query Jobs {
jobs {
id
# ...etc. (see Job object)
}
}
Mutations
The following mutations are available for creating and modifying data:
create_text_to_motion
Generate a motion from a text prompt.
prompt: The text prompt to generate a motion from.foot_ik: Whether to enable foot IK.
mutation {
create_text_to_motion(
prompt: String!
model: String # internal use only
foot_ik: Boolean = false
) {
motion {
id
name
}
}
}
create_video_to_motion
Generate a motion from a 2D video file.
file: File or URL of the video file to create a motion from.motion_name: The name of the resulting motion.character_id: (optional) Character ID to retarget the motion to.model: (optional) Model to use:video-to-motion-v1orvideo-to-motion-v2fast (preview). If omitted, the default model (v1) is used.
Duplicate uploads: If the same video file is already being processed, or was successfully processed in the last 24 hours or failed in the last hour, the API returns the existing job instead of throwing an error.
mutation {
create_video_to_motion(
file: Upload!
motion_name: String!
character_id: String
model: String
) {
job {
id
status
}
}
}
create_character
Upload a new character. Auto-rigging is handled automatically if the character doesn't have a rig.
file: File or URL of the character file to create.name: The name of the resulting character.auto_rig: Whether to auto-rig the character. (default:true)
mutation {
create_character(
file: Upload!
name: String!
auto_rig: Boolean = true # optional, defaults to true
root: String # internal use only
) {
character {
id
name
}
}
}
create_enhanced_stitched_motion
Stitch two motions together with detailed pose and spatial data for high-quality transitions. This is the primary stitching API.
stitch_input:MotionStitchInput!— Required input containingcharacter_id,prefix, andsuffix.
MotionStitchInput:
character_id(String!): Character ID both motions are retargeted to.prefix(StitchParamsInput!): Parameters for the leading motion.suffix(StitchParamsInput!): Parameters for the trailing motion.
StitchParamsInput (each of prefix and suffix):
| Field | Type | Required | Description |
|---|---|---|---|
prompt | String | Yes | Text hint for the transition. Use "" for deterministic baseline. |
motion_id | String | Yes | Motion ID. |
stitch_loop | Boolean | Yes | Use false for standard stitching. |
stitch_duration | Float | Yes | Transition duration in seconds (0.1–3.0). Recommended: 2.0. |
motion_duration | Float | Yes | Total motion duration in seconds. |
motion_lower_trim_time | Float | Yes | Lower trim time in seconds. |
motion_upper_trim_time | Float | Yes | Upper trim time in seconds. For prefix, use motion_duration - 0.051 to avoid exact-end sampling. |
motion_lower_trim_fraction | Float | Yes | Lower trim as fraction [0.0–1.0]. |
motion_upper_trim_fraction | Float | Yes | Upper trim as fraction [0.0–1.0]. |
root_node_world_pos | Vector3Input | Yes | Root node world position {x, y, z}. |
root_node_world_rot | QuaternionInput | Yes | Root node world rotation {x, y, z, w}. |
at_zero_time | PelvisStateInput | Yes | Pelvis state at time 0. |
at_lower_trim_time | PelvisStateInput | Yes | Pelvis state at lower trim time. |
at_upper_trim_time | PelvisStateInput | Yes | Pelvis state at upper trim time. |
PelvisStateInput: pelvis_world_pos (Vector3), pelvis_world_rot (Quaternion), hips_forward_facing_world_yaw (Float, radians).
Pose data must be sampled from each motion at the trim points. See Stitch & loop motions for a full tutorial and recommended defaults.
mutation CreateEnhancedStitchedMotion($stitch_input: MotionStitchInput!) {
create_enhanced_stitched_motion(stitch_input: $stitch_input) {
motion {
id
name
}
}
}
create_stitched_motion
Legacy stitch endpoint. Prefer create_enhanced_stitched_motion for better quality and control.
Migration: Use
create_enhanced_stitched_motionwithMotionStitchInputfor the upgraded stitching model. See Stitch & loop motions for the full API and tutorial.
leading_motion_id: The ID of the leading motion.trailing_motion_id: The ID of the trailing motion.duration: The duration in seconds of the transition between the two motions. (default:0.5)
mutation {
create_stitched_motion(
leading_motion_id: String!
trailing_motion_id: String!
duration: Float = 0.5
) {
motion {
id
name
}
}
}
trim_and_loop_motion
Note: This mutation's contract (its name and parameters) may change in the future. The implementation is stable, and the resulting motions are unaffected.
Trim and optionally loop a motion.
motion_id: The ID of the motion to trim and loop.motion_name: The name of the resulting motion.start/end: The start and end times of the trimmed motion, as a decimal value between 0 (start of motion) and 1 (end of motion).loop: Create a looped motion. Valid values:- (empty): No loop
"cyclical": Moves the character back to its starting position before looping again (in-place)"progressive": Loops continuously starting from wherever the previous loop ended (locomotion)
loop_root_motion: (Deprecated: Pass "progressive" or "cyclical" toloopinstead). Control loop style:true(progressive) orfalse(cyclical)"
mutation {
trim_and_loop_motion(
motion_id: String!
motion_name: String
start: Float!
end: Float!
loop: "progressive" | "cyclical"
) {
motion {
id
name
}
}
}
Backward compatibility: The deprecated loop_root_motion parameter is still supported for legacy clients. When both loop and loop_root_motion are provided, loop takes precedence.
rate_motion
Rate a motion or label.
motion_id: The ID of the motion to rate.label_id: The ID of the label to rate.score: The score to rate the motion or label with. Must be one of:0: Downvote1: Upvote
mutation {
rate_motion(
label_id: String
motion_id: String!
score: Int! # must be 0 or 1
) {
rating {
score
user_id
}
}
}
update_motion
Update a motion's name and/or deletion status.
motion_id: The ID of the motion to update.name: The new name of the motion.deleted: Marks the motion as deleted. While not permanently removed, it will not appear in API responses or the UI.
mutation {
update_motion(
deleted: Boolean
motion_id: String!
name: String
) {
motion {
id
name
deleted
}
}
}
Example queries
Get motion details
query GetMotion($id: String!) {
motion(id: $id) {
id
name
created
updated
org_id
priority
tags
motion_viewer
training
assets {
id
filename
}
labels {
id
description
start
end
}
rating {
score
user_id
}
}
}
List motions
query ListMotions {
motions(methods: ["TextToMotion"]) {
id
name
org_id
created
assets {
filename
}
}
}
List jobs
query ListJobs {
jobs(method: "VideoToMotion") {
id
status
}
}
Generate motion from text (text-to-motion)
Available models:
text-to-motion(default) andtext-to-motion-bucmd. For the BUCMD variant you can also pass the following parameters:
foot_ik: Enable foot IK for better foot placement on uneven surfaces. Defaults tofalseif not specified.steps: Number of diffusion steps. Range1–150, default50.cfg_scale: Classifier-free guidance scale. Range0–10, default2.length: Desired motion length in seconds. Range0.25–10, default10.motion_length: DEPRECATED. Uselengthinstead. Desired motion length in frames (20 FPS).seed: Random seed for reproducibility. Range1–99999, defaultNone(random).retargeting_ik: Enable inverse kinematics (IK) for retargeting from the internal skeleton. Defaults totrueif not specified (recommended to keep enabled for best results).
mutation GenerateTextMotion {
create_text_to_motion(prompt: "walk casually", model: "text-to-motion", foot_ik: true) {
motion {
id
name
}
}
}
mutation GenerateBucmdTextMotion {
create_text_to_motion(
prompt: "confident stride down the runway"
model: "text-to-motion-bucmd"
foot_ik: false
retargeting_ik: true
steps: 50
cfg_scale: 2
length: 10
seed: 12345
) {
motion {
id
name
}
}
}
Upload a character
mutation UploadCharacter($file: Upload!) {
create_character(
file: $file
name: "My Character"
auto_rig: true # optional, defaults to true
) {
character {
id
name
}
}
}
Download a character as FBX or GLB
All character files are available via the following endpoint:
https://uthana.com/motion/bundle/<character-id>/character.<fbx,glb>
- character_id: The character's ID
- type: The file type/format (
fbx,glb)
Note: You will need to pass your API key in the Authorization header (see this curl example).
Download a motion as FBX or GLB
Motion files with character mesh are available via the following endpoint:
https://uthana.com/motion/file/motion_viewer/<character-id>/<motion-id>/<type>/<character-id>-<motion-id>.<type>
- character_id: The character's ID
- motion_id: The motion's ID
- type: The file type/format (
fbx,glb)
Motion-only files (animation data without the character mesh) are available via the following endpoint, currently only GLB is supported:
https://uthana.com/motion/animation/motion_viewer/<character-id>/<motion-id>/glb/<character-id>-<motion-id>.glb
Note: The character ID is required to ensure the correct skeleton is used. If you don't have a custom character ID, use cXi2eAP19XwQ - Uthana's Tar character.
Both motion endpoints support the following query parameters:
- fps:
24,30, or60 - no_mesh:
trueorfalse - in_place:
trueorfalse— removes horizontal root motion to keep the character in place (default:false) - roblox_compatible: Experimental
trueorfalse— exports FBX optimized for Roblox Studio- Only applies to FBX (not GLB); using with GLB returns an error
- Only supported for character IDs
cFB7NoFCUCvf,cg1RuTM77HXu, andc8EaC2nVbPS8; using with unsupported IDs returns an error
Rate limits
Rate limits are enforced on a per-organization basis. Reach out to the Uthana team to discuss your needs.
Error handling
Errors are returned in the errors array with detailed information:
{
"errors": [
{
"message": "Invalid character model format. Expected .fbx or .glb file.",
"path": ["create_character"],
"extensions": {
"code": "INVALID_MODEL_FORMAT",
"timestamp": "2025-01-15T10:30:00Z"
}
}
]
}
{
"errors": [
{
"message": "Rate limit exceeded",
"extensions": {
"code": "RATE_LIMIT_EXCEEDED",
"resetAt": "2025-01-01T00:00:00Z"
}
}
]
}