Asset management | Uthana API Docs

Manage your characters, motions, and other assets through the Uthana API. Upload, list, download, and organize your 3D assets programmatically.

Overview

Asset management covers the full lifecycle of your 3D assets:

Character management

List your characters

Retrieve all characters available to your organization.

API_KEY="your-api-key"

curl -X POST https://uthana.com/graphql \
  -u $API_KEY: \
  -H "Content-Type: application/json" \
  -d '{
    "query": "{ characters { id name created updated assets { id filename } } }"
  }'
import asyncio
from uthana import Uthana

client = Uthana("your-api-key")

async def main():
    characters = await client.characters.list()
    for c in characters[:10]: # Limit to 10 characters
        print(c.get("id"), c.get("name"))

asyncio.run(main())
import { UthanaClient } from "@uthana/client";

const client = new UthanaClient(process.env.UTHANA_API_KEY!);

const characters = await client.characters.list();
const limitedCharacters = characters.slice(0, 10);
limitedCharacters.forEach((c) => console.log(c.id, c.name));
import { useUthanaCharacters } from "@uthana/react";

function CharacterList() {
  const { characters } = useUthanaCharacters();
  const limitedCharacters = characters?.slice(0, 10);
  return <ul>{limitedCharacters?.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;
}
private const string ApiUrl = "https://uthana.com/graphql";
private readonly string _apiKey = "your-api-key";

public async Task<List<Character>> GetCharactersAsync()
{
    var query = @"
        query {
            characters {
                id
                name
                created
                updated
                assets {
                    id
                    filename
                }
            }
        }";

var request = new { query = query };
    var json = JsonSerializer.Serialize(request);
    var content = new StringContent(json, Encoding.UTF8, "application/json");

var authValue = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{_apiKey}:"));  
    _httpClient.DefaultRequestHeaders.Authorization =
        new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", authValue);

var response = await _httpClient.PostAsync(ApiUrl, content);
    response.EnsureSuccessStatusCode();

var responseJson = await response.Content.ReadAsStringAsync();
    var result = JsonSerializer.Deserialize<GraphQLResponse<CharactersData>>(responseJson);

return result.Data.Characters;
}

// Usage
var characters = await GetCharactersAsync();
foreach (var character in characters.Take(10)) {
    Console.WriteLine($"{character.Name} ({character.Id})");
}

See the Auto-rig / add a character guide for uploading characters.

Rename a character

CHARACTER_ID="your-character-id"

curl -X POST https://uthana.com/graphql \
  -u $API_KEY: \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation UpdateCharacter($character_id: String!, $name: String) { update_character(character_id: $character_id, name: $name) { character { id name } } }",
    "variables": { "character_id": "'"$CHARACTER_ID"'", "name": "New name" }
  }'
async def main():
    await client.characters.rename("your-character-id", "New name")

asyncio.run(main())
await client.characters.rename("your-character-id", "New name");
import { useUthanaRenameCharacter } from "@uthana/react";

function RenameButton({ characterId }: { characterId: string }) {
  const rename = useUthanaRenameCharacter();
  return <button onClick={() => rename.mutate({ character_id: characterId, name: "New name" })}>Rename</button>;
}
var query = @"
    mutation UpdateCharacter($character_id: String!, $name: String) {
        update_character(character_id: $character_id, name: $name) {
            character { id name }
        }
    }";
var request = new { query = query, variables = new { character_id = "your-character-id", name = "New name" } };
var json = JsonSerializer.Serialize(request);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync(ApiUrl, content);
response.EnsureSuccessStatusCode();

Delete a character

Delete a character by setting its deleted flag to true. Built-in characters (like the default character "Tar") cannot be deleted.

CHARACTER_ID="your-character-id"

curl -X POST https://uthana.com/graphql \
  -u $API_KEY: \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation UpdateCharacter($character_id: String!, $deleted: Boolean) { update_character(character_id: $character_id, deleted: $deleted) { character { id name deleted } } }",
    "variables": { "character_id": "'"$CHARACTER_ID"'", "deleted": true }
  }'
async def main():
    await client.characters.delete("your-character-id")

asyncio.run(main())
await client.characters.delete("your-character-id");
import { useUthanaDeleteCharacter } from "@uthana/react";

function DeleteButton({ characterId }: { characterId: string }) {
  const remove = useUthanaDeleteCharacter();
  return <button onClick={() => remove.mutate({ character_id: characterId })}>Delete</button>;
}
public async Task<Character> DeleteCharacterAsync(string characterId)
{
    var query = @"
        mutation UpdateCharacter($character_id: String!, $deleted: Boolean) {
            update_character(character_id: $character_id, deleted: $deleted) {
                character {
                    id
                    name
                    deleted
                }
            }
        }";

var request = new
    {
        query = query,
        variables = new
        {
            character_id = characterId,
            deleted = true
        }
    };

var json = JsonSerializer.Serialize(request);
    var content = new StringContent(json, Encoding.UTF8, "application/json");

var response = await _httpClient.PostAsync(ApiUrl, content);
    response.EnsureSuccessStatusCode();

var responseJson = await response.Content.ReadAsStringAsync();
    var result = JsonSerializer.Deserialize<GraphQLResponse<UpdateCharacterData>>(responseJson);

return result.Data.UpdateCharacter.Character;
}

See the Download a character guide for downloading characters.

Motion management

List your motions

Retrieve all motions available to your organization.

curl -X POST https://uthana.com/graphql \
  -u $API_KEY: \
  -H "Content-Type: application/json" \
  -d '{
    "query": "{ motions { id name created updated assets { id filename } } }"
  }'
async def main():
    motions = await client.motions.list()
    for m in motions[:10]: # Limit to 10 motions
        print(m.get("id"), m.get("name"))

asyncio.run(main())
const motions = await client.motions.list();
const limitedMotions = motions.slice(0, 10);
limitedMotions.forEach((m) => console.log(m.id, m.name));
import { useUthanaMotions } from "@uthana/react";

function MotionList() {
  const { motions } = useUthanaMotions();
  const limitedMotions = motions?.slice(0, 10);
  return <ul>{limitedMotions?.map((m) => <li key={m.id}>{m.name}</li>)}</ul>;
}
public async Task<List<Motion>> GetMotionsAsync()
{
    var query = @"
        query {
            motions {
                id
                name
                created
                updated
                assets {
                    id
                    filename
                }
            }
        }";

var request = new { query = query };
    var json = JsonSerializer.Serialize(request);
    var content = new StringContent(json, Encoding.UTF8, "application/json");

var response = await _httpClient.PostAsync(ApiUrl, content);
    response.EnsureSuccessStatusCode();

var responseJson = await response.Content.ReadAsStringAsync();
    var result = JsonSerializer.Deserialize<GraphQLResponse<MotionsData>>(responseJson);

return result.Data.Motions;
}

Get a specific motion

Retrieve detailed information about a specific motion by ID.

MOTION_ID="your-motion-id"

curl -X POST https://uthana.com/graphql \
  -u $API_KEY: \
  -H "Content-Type: application/json" \
  -d '{
    "query": "query GetMotion($id: String!) { motion(id: $id) { id name created updated tags assets { id filename } } }",
    "variables": { "id": "'"$MOTION_ID"'" }
  }'
async def main():
    motion = await client.motions.get("your-motion-id")
    print(motion.get("name"), motion.get("created"))

asyncio.run(main())
const motion = await client.motions.get("your-motion-id");
console.log(motion.name, motion.created);
import { useUthanaMotion } from "@uthana/react";

function MotionDetail({ motionId }: { motionId: string }) {
  const { motion } = useUthanaMotion(motionId);
  return <div>{motion?.name}</div>;
}
public async Task<Motion> GetMotionAsync(string motionId)
{
    var query = @"
        query GetMotion($id: String!) {
            motion(id: $id) {
                id
                name
                created
                updated
                tags
                assets {
                    id
                    filename
                }
            }
        }";

var request = new
    {
        query = query,
        variables = new { id = motionId }
    };

var json = JsonSerializer.Serialize(request);
    var content = new StringContent(json, Encoding.UTF8, "application/json");

var response = await _httpClient.PostAsync(ApiUrl, content);
    response.EnsureSuccessStatusCode();

var responseJson = await response.Content.ReadAsStringAsync();
    var result = JsonSerializer.Deserialize<GraphQLResponse<MotionData>>(responseJson);

return result.Data.Motion;
}

Rename a motion

MOTION_ID="your-motion-id"
NEW_NAME="Updated motion name"

curl -X POST https://uthana.com/graphql \
  -u $API_KEY: \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation UpdateMotion($motion_id: String!, $name: String) { update_motion(motion_id: $motion_id, name: $name) { motion { id name } } }",
    "variables": { "motion_id": "'"$MOTION_ID"'", "name": "'"$NEW_NAME"'" }
  }'
async def main():
    await client.motions.rename("your-motion-id", "Updated motion name")

asyncio.run(main())
await client.motions.rename("your-motion-id", "Updated motion name");
const client = useUthanaClient();
await client.motions.rename(motionId, "Updated motion name");
public async Task<Motion> UpdateMotionAsync(string motionId, string newName)
{
    var query = @"
        mutation UpdateMotion($motion_id: String!, $name: String) {
            update_motion(motion_id: $motion_id, name: $name) {
                motion {
                    id
                    name
                }
            }
        }";

var request = new
    {
        query = query,
        variables = new
        {
            motion_id = motionId,
            name = newName
        }
    };

var json = JsonSerializer.Serialize(request);
    var content = new StringContent(json, Encoding.UTF8, "application/json");

var response = await _httpClient.PostAsync(ApiUrl, content);
    response.EnsureSuccessStatusCode();

var responseJson = await response.Content.ReadAsStringAsync();
    var result = JsonSerializer.Deserialize<GraphQLResponse<UpdateMotionData>>(responseJson);

return result.Data.UpdateMotion.Motion;
}

Delete a motion

Delete a motion by setting its deleted flag to true. The motion will be marked as deleted and will not appear in API responses or the UI.

MOTION_ID="your-motion-id"

curl -X POST https://uthana.com/graphql \
  -u $API_KEY: \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation UpdateMotion($motion_id: String!, $deleted: Boolean) { update_motion(motion_id: $motion_id, deleted: $deleted) { motion { id name deleted } } }",
    "variables": { "motion_id": "'"$MOTION_ID"'", "deleted": true }
  }'
async def main():
    await client.motions.delete("your-motion-id")

asyncio.run(main())
await client.motions.delete("your-motion-id");
const client = useUthanaClient();
await client.motions.delete(motionId);
public async Task<Motion> DeleteMotionAsync(string motionId)
{
    var query = @"
        mutation UpdateMotion($motion_id: String!, $deleted: Boolean) {
            update_motion(motion_id: $motion_id, deleted: $deleted) {
                motion {
                    id
                    name
                    deleted
                }
            }
        }";

var request = new
    {
        query = query,
        variables = new
        {
            motion_id = motionId,
            deleted = true
        }
    };

var json = JsonSerializer.Serialize(request);
    var content = new StringContent(json, Encoding.UTF8, "application/json");

var response = await _httpClient.PostAsync(ApiUrl, content);
    response.EnsureSuccessStatusCode();

var responseJson = await response.Content.ReadAsStringAsync();
    var result = JsonSerializer.Deserialize<GraphQLResponse<UpdateMotionData>>(responseJson);

return result.Data.UpdateMotion.Motion;
}

Favorite a motion

MOTION_ID="your-motion-id"

curl -X POST https://uthana.com/graphql \
  -u $API_KEY: \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation UpdateMotion($motion_id: String!, $favorite: Boolean) { update_motion(motion_id: $motion_id, favorite: $favorite) { motion { id name } } }",
    "variables": { "motion_id": "'"$MOTION_ID"'", "favorite": true }
  }'
async def main():
    await client.motions.favorite("your-motion-id", True)
    # Unfavorite:
    await client.motions.favorite("your-motion-id", False)

asyncio.run(main())
await client.motions.favorite("your-motion-id", true);
// Unfavorite:
await client.motions.favorite("your-motion-id", false);
const client = useUthanaClient();
await client.motions.favorite(motionId, true);
var query = @"
    mutation UpdateMotion($motion_id: String!, $favorite: Boolean) {
        update_motion(motion_id: $motion_id, favorite: $favorite) {
            motion { id name }
        }
    }";
var request = new { query = query, variables = new { motion_id = "your-motion-id", favorite = true } };
var json = JsonSerializer.Serialize(request);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync(ApiUrl, content);
response.EnsureSuccessStatusCode();

Rate a motion

Rate a motion with a thumbs up or thumbs down.

MOTION_ID="your-motion-id"

curl -X POST https://uthana.com/graphql \
  -u $API_KEY: \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation RateMotion($motion_id: String!, $score: Int!) { rate_motion(motion_id: $motion_id, score: $score) { motion { id } } }",
    "variables": { "motion_id": "'"$MOTION_ID"'", "score": 1 }
  }'
async def main():
    # 1 = thumbs up, 0 = thumbs down
    await client.motions.rate("your-motion-id", 1)

asyncio.run(main())
await client.motions.rate("your-motion-id", 1);
import { useUthanaRateMotion } from "@uthana/react";

function RateButtons({ motionId }: { motionId: string }) {
  const rate = useUthanaRateMotion();
  return (
    <div>
      <button onClick={() => rate.mutate({ motion_id: motionId, score: 1 })}>👍</button>
      <button onClick={() => rate.mutate({ motion_id: motionId, score: 0 })}>👎</button>
    </div>
  );
}
var query = @"
    mutation RateMotion($motion_id: String!, $score: Int!) {
        rate_motion(motion_id: $motion_id, score: $score) {
            motion { id }
        }
    }";
var request = new { query = query, variables = new { motion_id = "your-motion-id", score = 1 } };
var json = JsonSerializer.Serialize(request);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync(ApiUrl, content);
response.EnsureSuccessStatusCode();

Next steps