> For the complete documentation index, see [llms.txt](https://animotive.gitbook.io/animotive-kimodo/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://animotive.gitbook.io/animotive-kimodo/advanced/remote-api.md).

# Remote API Reference

This page documents the HTTP contract the plugin speaks when **Active Backend** is set to **Remote Server**. It is the reference an infrastructure or pipeline engineer works from when standing up a FastAPI service that fronts Kimodo for the Animotive Kimodo plugin.

NVIDIA's open-source Kimodo distribution does **not** ship these endpoints. The public release exposes `kimodo_gen` / `kimodo_demo` / `kimodo_textencoder` CLIs and a local Gradio demo on `localhost:7860`; it does not expose an HTTP API. To use the Self-Hosted (HTTP) backend, your team implements a FastAPI service that:

1. accepts the request schema described below,
2. invokes `kimodo_gen` (or an equivalent process) to solve the in-betweens,
3. streams progress over WebSocket while the solve runs, and
4. returns the resulting BVH bytes to the plugin.

The future managed [Animotive Kimodo (hosted)](/animotive-kimodo/setup/animotive-backend.md) service is exactly such a wrapper, operated by Animotive against the same contract.

## Endpoints

The plugin supports two endpoint shapes. The shape is selected by the **Endpoint Path** setting.

### Async (default)

```
POST {Server URL}{Endpoint Path}
```

Default **Endpoint Path** is `/api/projects/generate/async`. The server returns a 202 envelope containing a `job_id` and a `websocket_url`. The plugin then opens a WebSocket and consumes lifecycle events from the server while the solve runs. The final terminal event identifies the result URL, which the plugin fetches over HTTP to obtain the BVH bytes.

This is the recommended shape for new deployments. Live progress reaches the editor over the WebSocket — stage, segment, diffusion step, and percentage all update in real time on both the post-submit toast and the monitor row.

### Sync (legacy)

```
POST {Server URL}/api/generate
```

The server holds the HTTP connection open for the duration of the solve and returns the BVH bytes as the response body. There is no WebSocket and no job ID. The editor surfaces stage-only progress in five discrete steps rather than live updates.

The sync route is preserved for back-compat with deployments that have not shipped the async route. New wrappers should implement the async route.

## Request body schema

Both endpoints accept the same JSON document. Fields:

| Field                  | Type             | Default | Notes                                                                                                                                                             |
| ---------------------- | ---------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `model`                | string           | —       | Model identifier in lowercase-hyphen form, for example `kimodo-soma-rp`. Note this differs from the Docker CLI form `Kimodo-SOMA-RP-v1`.                          |
| `meta.texts`           | array of strings | —       | One prompt per Animotive Kimodo section, in timeline order. Single-prompt exports MAY collapse to a singular `meta.text` string; the server should accept either. |
| `meta.durations`       | array of numbers | —       | Per-section durations in seconds, aligned 1:1 with `meta.texts`. Single-prompt exports MAY collapse to a singular `meta.duration` number.                         |
| `meta.num_samples`     | int              | `1`     | Number of solver samples. The plugin always sends `1`.                                                                                                            |
| `meta.diffusion_steps` | int              | `100`   | Diffusion step count. Surfaces to the editor through the WebSocket `current_step` / `total_steps` fields when the stage is `sampling`.                            |
| `meta.seed`            | int              | `42`    | Random seed.                                                                                                                                                      |
| `constraints`          | array of objects | `[]`    | Per-frame constraint samples. Always present; emits as an empty array when the user has authored no constraint keys. See below for entry shape.                   |
| `bvh_standard_tpose`   | bool             | `true`  | Required by the plugin's IK retargeter. Stays at request root, not under `meta`.                                                                                  |

`constraints[]` entries take the shape:

```json
{ "type": "fullbody", "frame": 42, "pose": { /* 77-joint pose */ } }
```

`type` is one of `fullbody`, `left-hand`, `right-hand`, `left-foot`, `right-foot`, `2d-root`. `frame` is an integer frame index at the export frame rate (30 fps). `pose` is the SOMA77 pose document — see [File Formats](/animotive-kimodo/advanced/file-formats.md) for the full structure.

### Example body

```json
{
  "model": "kimodo-soma-rp",
  "meta": {
    "texts": ["walk forward", "wave hand"],
    "durations": [2.5, 1.0],
    "num_samples": 1,
    "diffusion_steps": 100,
    "seed": 42
  },
  "constraints": [
    { "type": "left-foot", "frame": 0,  "pose": { /* … */ } },
    { "type": "left-foot", "frame": 30, "pose": { /* … */ } }
  ],
  "bvh_standard_tpose": true
}
```

The plugin omits `meta.cfg`, `num_transition_frames`, and `include_motion_data` from the async body — the server is expected to apply its own defaults. The legacy sync body emits a flatter shape with `prompts`, `durations`, `num_transition_frames`, and `include_motion_data` at the document root.

## Async submission envelope

On the async route, the server's 202 response carries the job handle the plugin uses to track the run.

```json
{
  "job_id": "<hex>",
  "status": "running",
  "status_url": "http://host:port/api/projects/generate/jobs/<id>",
  "result_url": "http://host:port/api/projects/generate/jobs/<id>/result",
  "websocket_url": "ws://host:port/api/ws/generation/jobs/<id>",
  "poll_after_seconds": 2.0
}
```

`job_id` is required; the URL fields default to server-relative paths derived from `job_id` when omitted. `websocket_url` is the URL the plugin opens immediately after parsing the envelope. The scheme MUST match the server's transport — use `wss://` when the server is fronted by TLS.

A 2xx response with no `job_id` is treated as a malformed envelope and surfaces in the monitor as `RemoteServerError`.

## WebSocket progress envelope

For the duration of the job, the server pushes lifecycle messages to the WebSocket. The plugin consumes messages of the following shape (fields are anyOf nullable per the server's OpenAPI; the plugin tolerates missing fields):

| Field                              | Type         | Notes                                                                                                                    |
| ---------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------ |
| `event`                            | string       | `snapshot` / `progress` / `heartbeat` / `completed` / `failed`. Parser falls back to `status` when this field is absent. |
| `status`                           | string       | `queued` / `running` / `completed` / `failed`.                                                                           |
| `stage`                            | string       | `queued` / `running` / `encoding` / `sampling` / `completed` / `failed`. Mapped to a monitor lifecycle state.            |
| `progress`                         | number\|null | Overall 0..1. Clamped on parse.                                                                                          |
| `current_step` / `total_steps`     | int\|null    | Diffusion step counter — populated during `sampling`, null elsewhere.                                                    |
| `segment_index` / `total_segments` | int\|null    | Multi-segment progress. Overall is `(segment_index + within_segment) / total_segments`.                                  |
| `queue_position`                   | int\|null    | Informational.                                                                                                           |
| `result_url`                       | string       | Populated on `event=completed`.                                                                                          |
| `error_message`                    | string       | Populated on `event=failed`.                                                                                             |

The plugin's lifecycle states are `Queued`, `Uploading`, `Processing`, `Downloading`, `Retargeting`, plus terminal `Completed` / `Failed` / `Cancelled`. The server's `stage` values map onto the in-flight subset as follows: `queued` → `Queued`, `running` / `encoding` / `sampling` → `Processing`, `completed` → triggers the `Downloading` → `Retargeting` import chain on the client, `failed` → `Failed`.

Progress source priority on the editor side:

1. WebSocket `progress` float.
2. WebSocket `current_step / total_steps` ratio.
3. Internal stage fraction.

The plugin treats progress as monotonic during in-flight states and freezes the bar on terminal events.

## Terminal response

### Success

On `event=completed`, the plugin GETs `result_url` (or the derived `<Server URL>/api/projects/generate/jobs/<id>/result`) and expects the BVH content as the response body. Two return shapes are supported:

* **Binary body.** Response body is the raw BVH bytes. This is the simplest shape and the recommended default.
* **JSON envelope.** Response body is `{ "ok": true, "bvh": "<base64>|<url>", "warnings": ["…"] }`. When `bvh` is a URL string, the plugin GETs that URL to obtain the BVH bytes. Any `warnings` are forwarded to the monitor row as non-terminal warning chips.

The legacy sync route returns the BVH binary directly as the response body to the single POST.

### Failure

On `event=failed`, or when the result fetch fails, the plugin treats the run as terminal-failed and expects an envelope of the form:

```json
{
  "ok": false,
  "error": {
    "category": "ServerSolveFailed",
    "message": "Prompt under-constrained at segment 2"
  }
}
```

`category` is one of the `EKimodoErrorCategory` tokens listed below. `message` is the operator-facing text that the plugin surfaces on the monitor row tooltip and via **Copy error**. If `category` is omitted or is a value the plugin does not recognise, the run is classified as `Unknown`.

## Error categories accepted from the server

The plugin's monitor accepts any of the terminal `EKimodoErrorCategory` tokens listed on the [Troubleshooting](/animotive-kimodo/going-further/troubleshooting.md) page. Server authors most commonly emit the following:

| Category            | Use when                                                                                                                                                                                                                                                              |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ServerSolveFailed` | The server received the request and ran the model, but the model could not produce a result for this prompt and constraint set. The monitor maps this to the friendly text "Kimodo couldn't solve this animation. Try simplifying the prompt or adjusting key poses." |
| `RemoteServerError` | The server hit an internal error unrelated to the model — failed dependency, bad config, or an unhandled exception. Friendly text: "Kimodo server returned an error or no data. Try again or contact pipeline."                                                       |
| `InvalidSkeleton`   | The submitted pose is not SOMA77-compatible. Rare from a server context, but available.                                                                                                                                                                               |
| `FileWriteFailed`   | The server could not persist the result.                                                                                                                                                                                                                              |

If `category` is omitted entirely, the plugin classifies the failure as `Unknown` and surfaces a generic friendly message. Prefer always emitting an explicit category.

## Authentication

The plugin sends the **Auth Header** setting verbatim as the HTTP `Authorization` header on every request, including the result fetch and (when enabled) the cancel POST. If **Auth Header** is empty, no `Authorization` header is sent.

The open-source server contract treats authentication as optional — typical self-hosted deployments inside a private network omit the header entirely. The future managed [Animotive Kimodo (hosted)](/animotive-kimodo/setup/animotive-backend.md) service requires a bearer token; the plugin field is the input point for that token.

The WebSocket connection inherits no headers from the HTTP submission. Servers that require authentication on the WebSocket must encode the credential in the `websocket_url` itself (for example as a query parameter) when emitting the submission envelope.

## Cancel endpoint (optional)

```
POST {Server URL}/api/projects/{jobId}/cancel
```

The plugin only invokes this endpoint when **Enable Server-Side Cancel** is ticked under plugin settings. Until that flag is on, the monitor row's **Cancel** button stays disabled with an explanatory tooltip and the plugin cancels only its local handle to the request.

When the cancel endpoint is invoked, the plugin expects a 2xx response. The monitor row moves to **Cancelled** once the server confirms; the server SHOULD also close the WebSocket cleanly after acknowledging the cancel.

Server authors who have not shipped the cancel route should document that fact to their users, who should leave **Enable Server-Side Cancel** off. Turning the flag on against a server that does not implement cancel will surface a `RemoteServerError` on the cancel attempt.

## Reconnect policy

WebSocket disconnects during an in-flight job are not terminal. The plugin attempts reconnection three times with backoff at 1 s / 2 s / 4 s before giving up.

If all reconnect attempts fail, or if the total time from submission exceeds **Request Timeout (Seconds)**, the row moves to **Failed** with category `NetworkUnreachable`. Deliberate disconnects — terminal events or operator cancellation — skip the reconnect path.

Server implementations SHOULD therefore tolerate brief client absences. When a client reconnects on the same `job_id`, the server SHOULD emit a `snapshot` event so the client can resync state without missing the lifecycle.

## Reference implementation

NVIDIA publishes Kimodo at <https://github.com/nv-tlabs/kimodo>. That repository is the place to start when wrapping the model in a FastAPI service. A minimal wrapper has three responsibilities:

1. **Submission handler.** Accept the JSON body documented above, validate it, allocate a `job_id`, persist the job state, and return the 202 envelope. Spawn (or enqueue) a `kimodo_gen` process with arguments derived from the body.
2. **Progress streamer.** Open a WebSocket per `job_id`, parse the `kimodo_gen` process output, and emit `snapshot` / `progress` / `completed` / `failed` events with the fields documented in the WebSocket envelope section.
3. **Result handler.** When `kimodo_gen` exits successfully, expose the produced BVH at `result_url` (either as a binary response or as the JSON envelope with an embedded URL). On failure, emit a `failed` event with an `error_message` populated from the process's stderr.

The wrapper is the deploying team's responsibility. The plugin consumes only the contract on this page — it makes no assumptions about how the wrapper schedules work, persists jobs, or scales horizontally.

## See also

* [Self-Hosted (HTTP) Backend](/animotive-kimodo/setup/remote-backend.md) — user-facing setup of the editor against a server that implements this contract.
* [Animotive Kimodo (hosted)](/animotive-kimodo/setup/animotive-backend.md) — managed cloud variant of the same contract.
* [File Formats](/animotive-kimodo/advanced/file-formats.md) — request and BVH body internals.
* [Troubleshooting](/animotive-kimodo/going-further/troubleshooting.md) — full `EKimodoErrorCategory` table and symptom-to-cause mapping.
