Skip to main content

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: String Your organization's ID
  • name: String Your organization's name
  • motion_download_secs_per_month: Float The number of seconds of motion downloads per month allocated to the organization
  • motion_download_secs_per_month_remaining: Float The 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: String Your user ID
  • name: String Your name
  • email: String Your email address
  • email_verified: Boolean Whether your email address is verified
  • tz: String Your timezone at last login
  • roles: [String] Your roles
  • org: Org Your organization
  • created: String Your account creation timestamp
  • updated: String Your account last update timestamp
query User {
  user {
    id
    name
    email
    email_verified
    tz
    roles
    created
    updated
    org {
      id
      name
    }
  }
}

MotionDownloadAllowed

  • allowed: Boolean Whether the motion download is allowed
  • reason: String The reason the motion download is allowed or not
  • character_id: String The character ID
  • motion_id: String The 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: String Character name
  • org_id: String Character organization ID
  • assets: [Asset] Character assets
  • created: String Character creation timestamp
  • updated: String Character last update timestamp
  • deleted: String | null Character deletion timestamp, or null if 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 Character objects in your organization
query Characters {
  characters {
    id
    name
    # ...etc. (see Character object)
  }
}

Motion

  • id: String Motion ID
  • name: String Motion name
  • org_id: String Motion organization ID
  • tags: Object Motion tags
  • assets: [Asset] Motion assets
  • labels: [Label] Motion labels
  • rating: MotionRating | null Motion rating, or null if the motion has not been rated
  • favorite: MotionFavorite | null Motion favorite, or null if the motion has not been favorited
  • created: String Motion creation timestamp
  • updated: String Motion last update timestamp
  • deleted: String | null Motion deletion timestamp, or null if the motion has not been deleted
{ MotionRating }
  • user_id: String User ID who rated the motion
  • score: Int The rating score, must be 0 (downvote) or 1 (upvote)
  • created: String Rating creation timestamp
  • updated: String Rating last update timestamp
{ MotionFavorite }
  • user_id: String User ID who favorited the motion
  • motion_id: String Motion ID
  • created: String Favorite creation timestamp
  • updated: String Favorite 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: String Asset internal UUID
  • filename: String Asset original filename
  • mimetype: String Asset mimetype
  • type: String Asset type, typically: bundle
  • sha256: String Asset SHA-256 hash
  • metadata: Object Asset metadata
  • size: Int Asset size in bytes
  • created: String Asset creation timestamp
  • updated: String Asset last update timestamp
  • deleted: String | null Asset deletion timestamp, or null if the asset has not been deleted

Note: Asset objects are children of Character and Motion.

Job

Note: The created_at, started_at, and ended_at fields 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: String Job status, one of: RESERVED, FINISHED, FAILED, READY
  • created_at: String Job creation timestamp
  • started_at: String Job start timestamp
  • ended_at: String Job end timestamp
  • method: String Job motion method
  • args: Object Job arguments
  • result: Object Job result
query Job($id: String!) {
  job(id: $id) {
    id
    status
    started_at
    ended_at
    result
  }
}

Jobs

  • [Job] List of Job objects
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 loop instead). 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) and text-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 to false if not specified.
  • steps: Number of diffusion steps. Range 1–150, default 50.
  • cfg_scale: Classifier-free guidance scale. Range 0–10, default 2.
  • length: Desired motion length in seconds. Range 0.25–10, default 10.
  • motion_length: DEPRECATED. Use length instead. Desired motion length in frames (20 FPS).
  • seed: Random seed for reproducibility. Range 1–99999, default None (random).
  • retargeting_ik: Enable inverse kinematics (IK) for retargeting from the internal skeleton. Defaults to true if 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.

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"
      }
    }
  ]
}