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
  • characters_allowed: Int The maximum number of characters your organization can create per month
  • characters_allowed_remaining: Int The number of character creation (upload, generate, or rig) slots remaining for the current month
  • users: [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: 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.
  • character_id: (optional) Character ID to retarget the motion to.
  • model: (optional) Model to use: video-to-motion-v1 or video-to-motion-v2 fast (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 containing character_id, prefix, and suffix.

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):

FieldTypeRequiredDescription
promptStringYesText hint for the transition. Use "" for deterministic baseline.
motion_idStringYesMotion ID.
stitch_loopBooleanYesUse false for standard stitching.
stitch_durationFloatYesTransition duration in seconds (0.1–3.0). Recommended: 2.0.
motion_durationFloatYesTotal motion duration in seconds.
motion_lower_trim_timeFloatYesLower trim time in seconds.
motion_upper_trim_timeFloatYesUpper trim time in seconds. For prefix, use motion_duration - 0.051 to avoid exact-end sampling.
motion_lower_trim_fractionFloatYesLower trim as fraction [0.0–1.0].
motion_upper_trim_fractionFloatYesUpper trim as fraction [0.0–1.0].
root_node_world_posVector3InputYesRoot node world position {x, y, z}.
root_node_world_rotQuaternionInputYesRoot node world rotation {x, y, z, w}.
at_zero_timePelvisStateInputYesPelvis state at time 0.
at_lower_trim_timePelvisStateInputYesPelvis state at lower trim time.
at_upper_trim_timePelvisStateInputYesPelvis 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_motion with MotionStitchInput for 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" 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.

Both motion endpoints support the following query parameters:

  • fps: 24, 30, or 60
  • no_mesh: true or false
  • in_place: true or false — removes horizontal root motion to keep the character in place (default: false)
  • roblox_compatible: Experimental true or false — 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, and c8EaC2nVbPS8; 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"
      }
    }
  ]
}