# Preview API

This API is a preview and is **unstable**—it is expected to change in future releases. Reach out via [support@uthana.com](mailto:support@uthana.com?subject=Stitch%20%26%20loop%20motions%20API%20feedback), Slack, or Discord to share your feedback.

## Not yet in the official clients

The stitch & loop API is not yet covered by the official Python (`uthana`) or JavaScript (`@uthana/client`) clients. Use the raw GraphQL examples below.

Learn more about [Stitching](/content/product/stitching/index.html) and [Looping](/content/product/looping/index.html).

Stitch allows you to join two motion clips into a single animation. Using a leading **prefix**, a trailing **suffix**, and spatial data at the join points, the stitch model generates a smooth transition between the two motions.

## Overview

You provide pose samples for each clip (pelvis/root position + rotation, plus forward-facing yaw) at the required time points. The stitch uses the prefix pose at `at_upper_trim_time` and the suffix pose at `at_lower_trim_time` as the connection points, aligns the motions in world space, and returns a new motion id synchronously.

You can use the [Uthana web app Stitch UI](/content/app/stitch/index.html) to play around with stitch and better understand how to position characters for prefix/suffix alignment.

## Key concepts and terminology

Before diving into the API, it helps to understand the core concepts and how they map to the stitch parameters.

### Coordinate system and axes

Uthana uses a **right-handed, Y-up** coordinate system consistent with Three.js, GLM, and [glTF](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#coordinate-system-and-units):

| Axis | Direction | Meaning |
| --- | --- | --- |
| **X** | Right | Positive X points to the character's right when facing +Z |
| **Y** | Up | Positive Y points upward; the floor is at `y = 0` |
| **Z** | Forward | Positive Z is "forward" when the character has zero yaw |

Right-handed Y-up axes

#### Pose properties and how they map to the API

The stitch model needs to know where each character is and how it's oriented at key moments. It uses this pose data to align the prefix and suffix spatially and generate a smooth transition. Positions are `{x, y, z}` in meters—e.g. `{x: 0.1, y: 0.95, z: 0.5}` means 0.1m to the right, pelvis height 0.95m above the floor, 0.5m forward. **These properties are required for both the leading (prefix) and trailing (suffix) motion**—each motion gets its own `StitchParamsInput` with full pose data.

You should get these values from your motion loader by evaluating the motion at each required time: seek to the frame, update the animation, and read the world-space transforms from your 3D engine (e.g. `getWorldPosition`, `getWorldQuaternion` in Three.js). Pelvis position and rotation come directly from the motion; `hips_forward_facing_world_yaw` is computed from the left and right hip bone positions (see [How to obtain these values](/content/docs/api/capabilities/stitch-loop-motions#how-to-obtain-these-values/index.html)).

Suppose you want to space two motions 1 meter apart in a straight line. You would set the prefix pelvis at the last frame (`at_upper_trim_time`) to `{x: 0, y: 0.95, z: 0}` and the suffix pelvis at the first frame (`at_lower_trim_time`) to `{x: 0, y: 0.95, z: 1}`. Without this spatial data, the stitch model has no way to know where each motion sits in the world, and the transition will look wrong.

##### What each property is

| Property | Type | What it represents |
| --- | --- | --- |
| `root_node_world_pos` | `{x, y, z}` (meters) | Character root position in world space. `x` and `z` are horizontal; `y` is height above the floor. |
| `root_node_world_rot` | `{x, y, z, w}` (quaternion) | Character root orientation in world space. Identity (no rotation) is `{x: 0, y: 0, z: 0, w: 1}`. |
| `pelvis_world_pos` | `{x, y, z}` (meters) | Pelvis bone position in world space. Used in each of `at_zero_time`, `at_lower_trim_time`, `at_upper_trim_time`. |
| `pelvis_world_rot` | `{x, y, z, w}` (quaternion) | Pelvis bone orientation in world space. |
| `hips_forward_facing_world_yaw` | radians | Angle around the Y axis for hips facing. Computed as `atan2(forward.x, forward.z)`. Examples: <br>- `0` = facing +Z<br>- `π/2` = facing -X<br>- `π` = facing -Z<br>- `-π/2` = facing +X |

Quaternions use `{x, y, z, w}` with `w` as the scalar component. This matches Three.js, GLM, and common game engines. See [Three.js Quaternion](https://threejs.org/docs/#api/en/math/Quaternion) and [Quaternions and spatial rotation](https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation) for more on this format. For the coordinate system and glTF reference, see [above](/content/docs/api/capabilities/stitch-loop-motions#coordinate-system-and-axes/index.html).

##### What they're used for

The stitch model uses root and pelvis transforms to:

- **Align the two motions spatially** so the character doesn't pop or drift when transitioning.
- **Match facing direction** so the blend looks natural.
- **Preserve floor contact** so the character doesn't float or sink.

Without accurate pose data, the transition can look jarring. For production, always sample from your motion loader.

##### Example: a single pelvis state

Each of `at_zero_time`, `at_lower_trim_time`, and `at_upper_trim_time` is a `PelvisStateInput`:

```
{
  "pelvis_world_pos": { "x": 0.12, "y": 0.95, "z": 0.0 },
  "pelvis_world_rot": { "x": 0, "y": 0, "z": 0, "w": 1 },
  "hips_forward_facing_world_yaw": 0
}
```

### Root node vs. pelvis

- **Root node** (`root_node_world_pos`, `root_node_world_rot`): The character's root transform in world space—typically the top-level node that contains the whole skeleton.
- **Pelvis**: A specific bone (the hip/pelvis joint) that defines the character's center of mass and facing direction. The stitch model uses pelvis data to align the two motions spatially.

The root is the hierarchy root; the pelvis is a child bone. Their positions and rotations can differ when the character is posed.

### Trim times and the three sample points

Each motion is sampled at **three time points**:

| Sample | When | Purpose |
| --- | --- | --- |
| `at_zero_time` | `t = 0` | Start of the motion clip |
| `at_lower_trim_time` | Start of the trimmed segment | Where the "useful" part of the clip begins |
| `at_upper_trim_time` | End of the trimmed segment | Where the "useful" part ends |

- **Prefix motion**: The stitch uses the pose at `at_upper_trim_time` (the last frame of the prefix) to connect to the suffix.
- **Suffix motion**: The stitch uses the pose at `at_lower_trim_time` (the first frame of the suffix) as the connection point.

For the prefix, keep `at_upper_trim_time` slightly before the clip end (e.g. `motion_duration - 0.051`) to avoid sampling exactly at the boundary.

### How to obtain these values

Motion loader required

You will need a motion loader (e.g. GLTF + Three.js, or your engine's equivalent) that can evaluate poses at specific times.

- **Lower and upper trim times.** You first need to decide `lower_trim` and `upper_trim`—the start and end of the segment you want to use. In the web app, the trim handles on each motion's timeline define these: drag the handles to mark the segment; the times at those positions are your `lower_trim` and `upper_trim`.

- **Pose data at each time.** Once you have those times, sample poses at `0`, `lower_trim`, and `upper_trim` from your motion loader. The pose data comes from pelvis and root rotation and positioning in world space—`pelvis_world_pos`, `pelvis_world_rot`, and the root transforms—plus the hips forward yaw. The web app lets you preview and adjust how the prefix and suffix characters are positioned and oriented relative to each other; the 3D view shows how pelvis rotation and positioning affect spatial alignment.

- **Pelvis positioning and duration.** The spatial gap between the prefix and suffix connection poses, combined with `stitch_duration`, can make or break a stitch. If the two poses are far apart in world space but you use a short duration, the model has to compress a large motion into a small window—the result can look rushed or unnatural. Conversely, if the poses are very close and you use a long duration, the transition may feel sluggish. Match the duration to the distance: larger gaps generally need longer durations for a smooth, believable transition. For example:
  - **Walking** (~1.2 m/s): For a 1 m gap, use `stitch_duration` in the 0.8–1.2 s range; for 2 m, use 1.5–2.0 s.
  - **Jogging** (~2.5 m/s): For a 1 m gap, use 0.4–0.6 s; for 2 m, use 0.8–1.2 s.

**Summary.** For programmatic use, the sampling process is:

1. **Load the motion** (e.g. GLTF with animation) and the character skeleton.
2. **Seek to each time** (`0`, `lower_trim`, `upper_trim`) and update the animation.
3. **Read world-space transforms** for the pelvis and root from your 3D engine (e.g. `getWorldPosition`, `getWorldQuaternion` in Three.js).
4. **Compute `hips_forward_facing_world_yaw`**: Get world positions of left and right hip bones. The "hips forward" direction is perpendicular to the hip line in the horizontal plane: `forward = normalize(cross(left_hip - right_hip, up))`. Then `yaw = atan2(forward.x, forward.z)`.

## Prerequisites

Before calling the stitch API, you need:

- **API key**: For authentication (see [GraphQL API](/content/docs/api/graphql#authentication/index.html))
- **Character ID**: The character both motions are retargeted to (e.g. `cXi2eAP19XwQ` for Uthana's default character)
- **Prefix motion ID**: The leading motion (plays first)
- **Suffix motion ID**: The trailing motion (plays second)
- **Pose data**: Pelvis and root-node transforms sampled at time 0, lower trim, and upper trim for each motion

See [How to obtain these values](/content/docs/api/capabilities/stitch-loop-motions#how-to-obtain-these-values/index.html) for the sampling process. The Uthana web app performs this automatically when you use the Stitch UI.

## Step-by-step tutorial

### Step 1: Authenticate your request

```shell
API_KEY="your-api-key"
API_URL="https://uthana.com/graphql"
```

### Step 2: Build the stitch input

The `MotionStitchInput` requires `character_id`, `prefix`, and `suffix`. Each of `prefix` and `suffix` is a `StitchParamsInput` with motion metadata and pose samples at three time points.

If you don't yet have a motion loader, you can use these placeholder values for the pose fields to satisfy the schema and test the API flow. They are structurally valid but will not yield good stitch quality—for production, always sample from your motion data.

| Property | Placeholder value | Notes |
| --- | --- | --- |
| `root_node_world_pos` | `{x: 0, y: 0, z: 0}` | Origin. Replace with sampled data for real stitches. |
| `root_node_world_rot` | `{x: 0, y: 0, z: 0, w: 1}` | Identity (no rotation). |
| `pelvis_world_pos` (in each sample) | `{x: 0, y: 0, z: 0}` | Origin. |
| `pelvis_world_rot` (in each sample) | `{x: 0, y: 0, z: 0, w: 1}` | Identity. |
| `hips_forward_facing_world_yaw` | `0` | Facing +Z. |

Recommended defaults

For `stitch_duration`, `stitch_loop`, `prompt`, and other optional fields, see [Recommended defaults](/content/docs/api/capabilities/stitch-loop-motions#recommended-defaults/index.html).

**Required fields** (must be derived from your motion data):

- `motion_id`, `motion_duration`, `motion_lower_trim_time`, `motion_upper_trim_time`, `motion_lower_trim_fraction`, `motion_upper_trim_fraction`
- `root_node_world_pos`, `root_node_world_rot` (character root at trim points)
- `at_zero_time`, `at_lower_trim_time`, `at_upper_trim_time` (pelvis position, rotation, and `hips_forward_facing_world_yaw` in radians)

For prefix motions, keep the upper trim time slightly before the clip end (e.g. `duration - 0.051`) to avoid sampling at the exact boundary, which improves stitch quality.

### Step 3: Call the mutation

```shell
curl -X POST "$API_URL" \
  -u "$API_KEY": \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation CreateEnhancedStitchedMotion($stitch_input: MotionStitchInput!) { create_enhanced_stitched_motion(stitch_input: $stitch_input) { motion { id } } }",
    "variables": {
      "stitch_input": {
        "character_id": "'
