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 "<your-api-key>:" | base64)
curl -H "Authorization: Basic $AUTH_STRING" \
https://uthana.com/graphql
or in the query string (plaintext):
https://uthana.com/graphql?apikey=<your-api-key>
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(app_ids: [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 organizationusers:[User]The users in your organization
query Org {
org {
id
name
motion_download_secs_per_month
motion_download_secs_per_month_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.
mutation {
create_video_to_motion(
file: Upload!
motion_name: 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_stitched_motion
Stitch two motions together.
Note: The motion stitching model has been upgraded, and this endpoint will be improved soon with new behavior. Legacy behavior may be preserved in a separate endpoint.
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:
retargeting_ik: Enables retargeting IK for better alignment to the target skeleton.steps: Number of diffusion steps used during generation.cfg_scale: Classifier-free guidance scale that balances prompt fidelity versus diversity.motion_length: Output length in frames at the internal model frame rate of 20 FPS.seed: Random seed to reproduce 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
motion_length: 200
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.
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"
}
}
]
}