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?api_key=<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 ID
- name:- StringYour organization's name
- motion_download_secs_per_month:- FloatThe number of seconds of motion downloads per month allocated to the organization
- motion_download_secs_per_month_remaining:- FloatThe number of seconds of motion downloads per month remaining for the organization
- users:- [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 ID
- name:- StringYour name
- email:- StringYour email address
- email_verified:- BooleanWhether your email address is verified
- tz:- StringYour timezone at last login
- roles:- [String]Your roles
- org:- OrgYour organization
- created:- StringYour account creation timestamp
- updated:- 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 allowed
- reason:- StringThe reason the motion download is allowed or not
- character_id:- StringThe character ID
- motion_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:- String
- name:- StringCharacter name
- org_id:- StringCharacter organization ID
- assets:- [Asset]Character assets
- created:- StringCharacter creation timestamp
- updated:- StringCharacter last update timestamp
- deleted:- String|- nullCharacter deletion timestamp, or- nullif 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 of- Characterobjects in your organization
query Characters {
  characters {
    id
    name
    # ...etc. (see Character object)
  }
}
Motion
- id:- StringMotion ID
- name:- StringMotion name
- org_id:- StringMotion organization ID
- tags:- ObjectMotion tags
- assets:- [Asset]Motion assets
- labels:- [Label]Motion labels
- rating:- MotionRating|- nullMotion rating, or- nullif the motion has not been rated
- favorite:- MotionFavorite|- nullMotion favorite, or- nullif the motion has not been favorited
- created:- StringMotion creation timestamp
- updated:- StringMotion last update timestamp
- deleted:- String|- nullMotion deletion timestamp, or- nullif the motion has not been deleted
{ MotionRating }
- user_id:- StringUser ID who rated the motion
- score:- IntThe rating score, must be 0 (downvote) or 1 (upvote)
- created:- StringRating creation timestamp
- updated:- StringRating last update timestamp
{ MotionFavorite }
- user_id:- StringUser ID who favorited the motion
- motion_id:- StringMotion ID
- created:- StringFavorite creation timestamp
- updated:- 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:- String
- uid:- StringAsset internal UUID
- filename:- StringAsset original filename
- mimetype:- StringAsset mimetype
- type:- StringAsset type, typically:- bundle
- sha256:- StringAsset SHA-256 hash
- metadata:- ObjectAsset metadata
- size:- IntAsset size in bytes
- created:- StringAsset creation timestamp
- updated:- StringAsset last update timestamp
- deleted:- String|- nullAsset deletion timestamp, or- nullif 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:- String
- status:- StringJob status, one of:- RESERVED,- FINISHED,- FAILED,- READY
- created_at:- StringJob creation timestamp
- started_at:- StringJob start timestamp
- ended_at:- StringJob end timestamp
- method:- StringJob motion method
- args:- ObjectJob arguments
- result:- ObjectJob result
query Job($id: String!) {
  job(id: $id) {
    id
    status
    started_at
    ended_at
    result
  }
}
Jobs
- [Job]List of- Jobobjects
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" to- loopinstead). Control loop style:- true(progressive) or- false(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: Downvote
- 1: 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
All motion files are available via the following endpoint:
https://uthana.com/motion/file/motion_viewer/<character-id>/<motion-id>/<type>/<character-id>-<motion-id>.<type>
- app_id: The application context (always use motion_viewer)
- character_id: The character's ID
- motion_id: The motion's ID
- type: The file type/format (fbx,glb)
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"
      }
    }
  ]
}