> ## Documentation Index
> Fetch the complete documentation index at: https://docs.reactor.inc/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> To build and serve your own model, start at /deploy/development/quickstart and /deploy/development/overview. Deploying is the default path: reactor init scaffolds a workspace, reactor auth login authenticates, and reactor model deploy registers the model, publishes the release with the weights/ folder, and activates it on Reactor's GPUs, in one command from that workspace. Docker must be running, because the publish step builds the image locally. Bump model.version in reactor.yaml before redeploying a change, because a release that already has an image is reactivated as it is. Deployment access is granted per account, so contact team@reactor.inc if a deploy is refused. Every key in reactor.yaml is documented at /deploy/platform/reactor-yaml. Model code imports reactor_runtime; Python client code imports reactor_sdk. The runtime overview explains the model interface. Running the model on your own machine with reactor run is optional and needs a GPU you attach with --gpus; /deploy/development/local-testing covers that loop and pairs a complete brightness model with a Python client test in a separate brightness-test workspace.
> Reactor hosts multiple models, each with its own connect slug (modelName) and command/event schema. The video model catalog — slug, typed SDK package, and links to its schema — is at /model-api-reference/overview. Robotics policy documentation starts at /robotics/overview; X-WAM observations, actions, and client integration are under /robotics/xwam/; Cosmos3 Nano Policy DROID is under /robotics/cosmos/nano-policy-droid/. Some models expose one slug per experience (e.g. HappyOyster); always take the slug from the model's own pages, never guess it.
> Fastest path to a working app: `npx create-reactor-app my-app --model=<slug>` scaffolds a complete app with secure auth wired up. Typed TypeScript SDKs are published as @reactor-models/<model>; Python uses the base reactor-sdk package.
> Auth: exchange an API key (rk_...) for a JWT via POST https://api.reactor.inc/tokens from your server. Never put the API key in client-side code.
> Append .md to any docs URL for clean Markdown. Search these docs via the MCP server at https://docs.reactor.inc/mcp.

# FastH3-Omni tutorial

> Connect, upload references, queue clips, and play video with synchronized audio.

This walkthrough creates a clip from a text prompt, then continues it with an image reference. It uses the base JavaScript SDK so you can see each model command. See [Authentication](/authentication) to issue a session token from your server and [File Uploads](/concepts/file-uploads) for the upload flow.

## Installation and setup

Add the base JavaScript SDK to an app with a `<video>` and an `<audio>` element. This walkthrough assumes your app provides the token helper, file picker, and UI.

```shell theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
npm install @reactor-team/js-sdk
```

## How FastH3-Omni works

The session starts idle. `enqueue` queues a clip for generation. When `clip_generated` arrives, `play` sends it to the output tracks. A command reply confirms that Reactor accepted the request. Watch later messages and the tracks for the result.

## Authentication

Issue a session token on your server. The browser's `getToken()` helper calls that server route. Do not put an `rk_` API key in browser code. See [Authentication](/authentication) for the token exchange and limits.

## 1. Connect and show the tracks

Have the browser request a short-lived token from your server, then connect to `reactor/fast-h3-omni`. Render `main_video` in the video element and attach `main_audio` to the audio element. Connecting does not generate a clip.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
import { Reactor } from "@reactor-team/js-sdk";

const reactor = new Reactor({ modelName: "reactor/fast-h3-omni" });
const video = document.querySelector("video")!;
const audio = document.querySelector("audio")!;

reactor.on("trackReceived", (name, _track, stream) => {
  if (name === "main_video") {
    video.srcObject = stream;
    void video.play().catch(console.error);
  }
  if (name === "main_audio") {
    audio.srcObject = stream;
    void audio.play().catch(console.error);
  }
});

await reactor.connect(await getToken()); // Your server issues the token.
```

Start playback from a user action if the browser blocks audio autoplay. Wait for the SDK's `ready` status before sending commands. On connect, the model sends `state_update` with the current defaults, capacities, and `valid_commands`. It also sends `queue_update` with both queues.

## 2. Queue a clip from text

Choose a supported duration. Register the message listener before `enqueue` so it catches the first clip's result. The `clip_queued` reply gives you the clip ID, but the clip cannot be continued or played until `clip_generated` arrives.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
const firstClipReady = new Promise<string>((resolve, reject) => {
  reactor.on("message", (message) => {
    if (message.type !== "clip_generated" && message.type !== "clip_failed") return;
    const result = message.data as { clip: { clip_id: string }; reason?: string };
    if (message.type === "clip_failed") {
      reject(new Error(result.reason ?? "Clip generation failed"));
    } else {
      resolve(result.clip.clip_id);
    }
  });
});

const queued = await reactor.sendCommand("enqueue", {
  prompt: "A ceramic artist turns a blue bowl under warm studio light. The camera slowly pushes in. A pottery wheel hums and rain taps the window.",
  seconds: 5.875,
  metadata: "opening-shot",
});
if (queued?.type !== "clip_queued") throw new Error("The clip was not queued");
const firstClipId = (queued.data as { clip: { clip_id: string } }).clip.clip_id;
```

The reply means the clip is queued, not ready to play. If generation fails, `clip_failed` supplies a reason.

## 3. Play when ready

Wait for `clip_generated`, then call the model's `play` command. The model sends `clip_started` when playback begins and `clip_finished` when both tracks finish. The video and audio elements from step 1 render those tracks.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
const generatedClipId = await firstClipReady;
await reactor.sendCommand("play", { clip_id: generatedClipId });
```

If you enable autoplay with `set_autoplay({ enabled: true })`, remove the `play` call. Autoplay plays ready clips when the output is idle; it does not generate new ones.

## 4. Add a reference and continue

Only continue after `firstClipReady` resolves. Upload a JPEG, PNG, or WebP image through the SDK and pass its `FileRef` to `reference_image`. The default continuation carries the first clip's motion, camera, and audio. Both clips must use the same canvas, and the model must still retain the first clip.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
const imageRef = await reactor.uploadFile(imageFile);

await reactor.sendCommand("enqueue", {
  prompt: "Continue the same studio scene. The artist from <Picture 1> lifts the finished blue bowl toward the window. The wheel slows, and rain continues outside.",
  reference_image: imageRef,
  continue_from_clip_id: firstClipId,
  seconds: 8,
});
```

Here `imageFile` comes from your file picker. In the prompt, `<Picture 1>` names the first reference image; the brackets are prompt text, not API syntax. With the base JavaScript SDK 3.1.0 or later, pass `FileRef` values directly, including in `reference_images` and `reference_audios` arrays.

To start the next clip on the previous clip's final frame instead, set `continue_mode: "frame"` and keep a reference image in that request. You can also add `last_frame_image` to pin its ending. A frame continuation supplies its own first frame, so do not send `first_frame_image` with `continue_from_clip_id`. See the [schema](/model-api-reference/fast-h3-omni/schema) for a keyframe example.

## 5. Manage state and errors

Read `state_update` for the generation and playout capacities. A full generation queue refuses `enqueue`; a full playout queue pauses builds. Use `move` to reorder a queued clip, `pop` to remove one, or `stop` to cut playback. Use `reset` to clear the session.

Show the `reason` from `command_error` when the model refuses a command. If generation fails, `clip_failed` reports the reason and removes the clip from the queue. You can then submit another request. The model refuses stale continuation IDs, unsupported durations, invalid media, and over-budget prompts at enqueue time.

### Surfacing command\_error

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
reactor.on("message", (message) => {
  if (message.type === "command_error" || message.type === "clip_failed") {
    console.error(message.data.reason);
  }
});
```

The [schema](/model-api-reference/fast-h3-omni/schema) lists every field and message. The [prompt guide](/model-api-reference/fast-h3-omni/prompt-guide) has scene and soundtrack examples.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.