Skip to main content

cURL examples

Uthana provides a powerful GraphQL API for all programmatic access to animation, character, and motion data. There is no REST API—all features are available via GraphQL.

This guide shows how to use curl to interact with the GraphQL API, including authentication, queries, mutations, and file uploads.

Endpoint

https://uthana.com/graphql

Authentication

Use the -u flag to authenticate with your API key:

curl -s "https://uthana.com/graphql" \
-u YOUR_API_KEY: \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
--data-raw '{<your-query-or-mutation>}'

or 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 as a query parameter:

curl 'https://uthana.com/graphql?api_key=<your-api-key>'

Making GraphQL queries with cURL

Send a POST request with your query or mutation in the JSON body:

Get motion details

curl -X POST https://uthana.com/graphql \
-u <your-api-key>: \
-H "Content-Type: application/json" \
-d '{
"query": "query GetMotion($id: String!) { motion(id: $id) { id name created } }",
"variables": { "id": "m3G3XSJrjEJH" }
}'

Get org info

curl -X POST https://uthana.com/graphql \
-u <your-api-key>: \
-H "Content-Type: application/json" \
-d '{"query":"{ org { id name } }"}'

Download a motion

To download a motion file (fbx or glb), use 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)

Download a motion as FBX

curl -L "https://uthana.com/motion/file/motion_viewer/<character-id>/<motion-id>/fbx/<character-id>-<motion-id>.fbx" \
-u <your-api-key>: \
-o motion.fbx
  • Replace <character-id> and <motion-id> with your actual IDs.
  • The -L flag follows redirects (if any).
  • The -o flag saves the file as motion.fbx.

Generate motion from text (text-to-motion)

curl -X POST https://uthana.com/graphql \
-u <your-api-key>: \
-H "Content-Type: application/json" \
-d '{
"query": "mutation CreateTextToMotion($character_id: String!, $prompt: String!) { create_text_to_motion(character_id: $character_id, prompt: $prompt) { ok job_id motion { id name } } }",
"variables": { "character_id": "<your-character-id>", "prompt": "walk casually" }
}'

Upload files (multipart/form-data)

To upload files (e.g., for create_character or create_video_to_motion), use the GraphQL multipart request spec:

Upload a character

curl -X POST https://uthana.com/graphql \
-u <your-api-key>: \
-F 'operations={
"query": "mutation ($file: Upload!, $name: String!) { create_character(file: $file, name: $name) { ok character_id } }",
"variables": { "file": null, "name": "My Character" }
}' \
-F 'map={ "0": ["variables.file"] }' \
-F '0=@/path/to/character.fbx'

Auto-rig a character

If you have a character that doesn't have a rig, you can auto-rig it using the create_rig mutation. You will need to upload your un-rigged character file first, and then call create_rig with the character ID.

The create_character upload will fail, but will return a character ID in the response:

{
"data": {
"create_character": {
"ok": false,
"character_id": "<your-character-id>"
}
}
}

You can then use the character ID to call create_rig:

curl -X POST https://$HOST/graphql \
-u "$UTHANA_APIKEY:" \
-H "Content-Type: application/json" \
-d '{
"query":"mutation AutoRigCharacter { create_rig(character_id: \"<your-character-id>\", filename: \"<your-filename>\") { ok character_id error } }"
}'

Complete examples

Create a text-to-motion job and download the motion as FBX and GLB

#!/bin/bash

set -eo pipefail

# Set your API key and character ID for easy reuse
UTHANA_APIKEY=<your-api-key>
HOST="uthana.com"
CHARACTER_ID=<your-character-id>

# Create a text-to-motion job
MUTATION='{
"query": "mutation CreateTextToMotion($character_id: String!, $model: String!, $prompt: String!) {create_text_to_motion(character_id: $character_id, model: $model, prompt: $prompt) {motion{id name}}}",
"variables": {"character_id": "'$CHARACTER_ID'", "model": "text-to-motion", "prompt": "a person runs in a circle"},
"operationName": "CreateTextToMotion"
}'

RES="$(curl -s "https://$HOST/graphql" \
-u $UTHANA_APIKEY: \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
--data-raw "$MUTATION"
)"
echo $RES

MOTION_ID="$(echo $RES | jq -r .data.create_text_to_motion.motion.id)"
echo "motion_id: $MOTION_ID"

# Download the motion as FBX
curl -s --remote-name "https://$HOST/motion/file/motion_viewer/$CHARACTER_ID/$MOTION_ID/fbx/$CHARACTER_ID-$MOTION_ID.fbx" \
-u $UTHANA_APIKEY:

# Download the motion as GLB
curl -s --remote-name "https://$HOST/motion/file/motion_viewer/$CHARACTER_ID/$MOTION_ID/glb/$CHARACTER_ID-$MOTION_ID.glb" \
-u $UTHANA_APIKEY:

# View the motion in the Uthana Web UI
echo "https://$HOST/app/play/$CHARACTER_ID/$MOTION_ID"

Upload a character

#!/bin/bash

set -eo pipefail

# Set your API key and character ID for easy reuse
UTHANA_APIKEY=<your-api-key>
HOST="uthana.com"
CHARACTER_NAME="My Character"

# Your existing fbx or glb file.
FILE="$1"
if [ ! -e "$FILE" ]; then
echo "$FILE is missing?"
fi

# Upload & create character

# In this request we auto-retarget to our internal skeleton, so the first time
# we see a skeleton this might be slow, but if you use the same skeleton again,
# it should be faster. Please contact us if you have issues during this process.

# You can also include a motion prompt to generate a motion from text in the same
# request, or omit `prompt` to just create the character.
RES="$(curl -s -X POST "https://$HOST/graphql" \
-u $UTHANA_APIKEY: \
-H 'Accept: application/json' \
--form $'operations={"query":"mutation CreateCharacter($file: Upload!, $name: String!) {\\n create_character(file: $file, name: $name) {character_id motion_id}\\n}","variables":{"file":null,"name":"'"$CHARACTER_NAME"'","prompt":"a person runs in a circle"},"operationName":"CreateCharacter"}' \
--form 'map={ "nFile": ["variables.file"] }' \
--form nFile=@$FILE
)"
echo $RES

CHARACTER_ID="$(echo $RES | jq -r .data.create_character.character_id)"
MOTION_ID="$(echo $RES | jq -r .data.create_character.motion_id)"

# List existing characters
curl -s "https://$HOST/graphql" \
-u $UTHANA_APIKEY: \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
--data-raw $'{"query":"{ characters { id name created } }"}'

# View the character in the Uthana Web UI
echo "https://$HOST/app/play/$CHARACTER_ID/$MOTION_ID"

Auto-rig a character

#!/bin/bash

set -eo pipefail

# Set your API key and character ID for easy reuse
UTHANA_APIKEY=<your-api-key>
HOST="uthana.com"
CHARACTER_NAME="My Character"

# Your existing fbx or glb file.
FILE="$1"
if [ ! -e "$FILE" ]; then
echo "$FILE is missing?"
fi

# Upload & create character
RES="$(curl -s -X POST "https://$HOST/graphql" \
-u $UTHANA_APIKEY: \
-H 'Accept: application/json' \
--form $'operations={"query":"mutation CreateCharacter($file: Upload!, $name: String!) {\\n create_character(file: $file, name: $name) {character_id motion_id}\\n}","variables":{"file":null,"name":"'"$CHARACTER_NAME"'"},"operationName":"CreateCharacter"}' \
--form 'map={ "nFile": ["variables.file"] }' \
--form nFile=@$FILE
)"
echo $RES

CHARACTER_ID="$(echo $RES | jq -r .data.create_character.character_id)"
OK="$(echo $RES | jq -r .data.create_character.ok)"
ERROR="$(echo $RES | jq -r .errors[0].message)"
FRIENDLY_ERROR="$(echo $RES | jq -r .data.create_character.error)"

# If the error contains the "No skeleton found" message, we can try to auto-rig it:
if [[ "$ERROR" == *"no rig found in scene"* ]] || [[ "$FRIENDLY_ERROR" == *"No rig found in scene"* ]]; then
echo "No rig found, trying to auto-rig..."

# Fail (exit 1) if there is no character ID
if [ "$CHARACTER_ID" = "null" ]; then
echo "No character ID found, cannot auto-rig"
exit 1
fi

MUTATION='{
"query": "mutation AutoRigCharacter { create_rig(character_id: \"'$CHARACTER_ID'\", filename: \"'$FILE'\") { ok character_id error } }"
}'

RES="$(curl -s -X POST "https://$HOST/graphql" \
-u $UTHANA_APIKEY: \
-H "Content-Type: application/json" \
-d "$MUTATION")"
echo $RES

OK="$(echo $RES | jq -r .data.create_rig.ok)"
ERROR="$(echo $RES | jq -r .errors[0].message)"

if [ "$OK" = "false" ]; then
echo "Failed to auto-rig character, error is: $ERROR"
exit 1
fi
CHARACTER_ID="$(echo $RES | jq -r .data.create_rig.character_id)"
echo "Auto-rigged character with ID: $CHARACTER_ID"
else
echo "Failed to create character, error is: $ERROR"
exit 1
fi

Error handling

Errors are returned in the errors array of the GraphQL response:

{
"errors": [
{
"message": "Rate limit exceeded",
"extensions": {
"code": "RATE_LIMIT_EXCEEDED",
"resetAt": "2025-01-01T00:00:00Z"
}
}
]
}

Rate limits

Rate limits are enforced on a per-organization basis. Contact the Uthana team to discuss your needs.