> ## 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.

# Using the SDK

> Connect to any Reactor model and send commands, receive video, and handle events from JavaScript, React, or Python

The Reactor SDKs are how your app opens a session, sends commands, and receives video and events from any model. There are two:

* **JavaScript** ships both a **React** API (provider, components, hooks) and an **imperative** API (the `Reactor` class). Use React for browser apps, imperative for vanilla JS or fine-grained control. The browser obtains a short-lived **JWT token** from your server.
* **Python** is an async library for scripts, servers, and computer-vision pipelines. It receives frames as NumPy arrays and authenticates with your **API key** directly, server-side.

Every code example below offers all three. Pick a tab and it stays selected down the page.

<Note>
  Never ship your API key to the browser. Browser apps exchange it for a JWT on your server; Python
  runs server-side and uses the key directly. See [Authentication](/authentication).
</Note>

## Two layers: base SDK and typed SDKs

Reactor SDKs are built in two layers:

* **The base `Reactor` class** is the generic, model-agnostic entry point. It speaks raw JSON over the wire: you call `sendCommand(name, payload)` and listen for generic `message` events. It works against any model. This page teaches the base layer, so the examples work no matter which model you connect to.
* **Typed SDKs** (for example, `HeliosModel`) extend the base class and expose strongly-named methods (`setPrompt()`, `sendImage()`, …) that mirror a model's schema. They add ergonomics, not capabilities, and every method is still a `sendCommand()` under the hood.

<Frame>
  <img src="https://mintcdn.com/reactortechnologiesinc/oCeFlbp0H3XO2l0l/diagrams/sdk-layering.svg?fit=max&auto=format&n=oCeFlbp0H3XO2l0l&q=85&s=0778c30e757fe08635dec0603f96452d" alt="The base Reactor class sits at the top; typed model SDKs like HeliosModel and LingbotModel extend it" width="570" height="202" data-path="diagrams/sdk-layering.svg" />
</Frame>

Reach for the base `Reactor` class when you're experimenting with a model that has no typed SDK yet, developing locally against an in-progress model, writing a one-off script, or want a single SDK that can talk to several models. For everything else the typed SDK is the recommended path. See [Typed model SDKs](/sdk-reference/typed-model-sdk) for the full story and the name-to-name mapping.

For the commands and events a specific model accepts, see the [Model API Reference](/model-api-reference/overview).

***

## Connecting

<Tabs>
  <Tab title="JavaScript">
    ```typescript Connect theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    import { Reactor } from "@reactor-team/js-sdk";

    const reactor = new Reactor({ modelName: "your-model-name" });
    await reactor.connect(token);
    ```

    Disconnect when done:

    ```typescript Disconnect theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    await reactor.disconnect();
    ```
  </Tab>

  <Tab title="React">
    Wrap your app in `ReactorProvider`. Pass a token and the connection is managed automatically.

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

    <ReactorProvider modelName="your-model-name" jwtToken={token}>
      <ReactorView className="w-full aspect-video" />
    </ReactorProvider>
    ```

    <Note>
      The provider does not connect on mount by default. Pass `connectOptions={{ autoConnect: true }}` to connect immediately, or trigger it manually.
    </Note>

    ```tsx Manual connect theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    import { useReactor } from "@reactor-team/js-sdk";

    function Controls() {
      const { connect, disconnect, status } = useReactor((s) => s);

      return status === "disconnected"
        ? <button onClick={connect}>Connect</button>
        : <button onClick={disconnect}>Disconnect</button>;
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python Connect theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    import asyncio
    import os
    from reactor_sdk import Reactor

    async def main():
        reactor = Reactor(
            model_name="your-model-name",
            api_key=os.environ["REACTOR_API_KEY"],
        )

        await reactor.connect()
        await reactor.disconnect()

    asyncio.run(main())
    ```

    For automatic cleanup, use `async with`:

    ```python Context manager theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    from reactor_sdk import Reactor

    async with Reactor(model_name="your-model-name", api_key=os.environ["REACTOR_API_KEY"]) as reactor:
        # disconnects automatically on exit
        pass
    ```
  </Tab>
</Tabs>

See [Authentication](/authentication) for how to obtain a token or API key.

***

## Receiving model output

The browser SDK delivers the model's video as a media track you render in a `<video>` element. Python delivers decoded frames as NumPy arrays for processing.

<Tabs>
  <Tab title="JavaScript">
    Listen for the `trackReceived` event and attach the stream to a video element.

    ```typescript Track listener theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    const video = document.getElementById("video") as HTMLVideoElement;

    reactor.on("trackReceived", (name, track, stream) => {
      video.srcObject = stream;
    });
    ```
  </Tab>

  <Tab title="React">
    `ReactorView` displays the model's video output automatically.

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

    <ReactorView
      className="w-full aspect-video"
      videoObjectFit="cover"
    />
    ```
  </Tab>

  <Tab title="Python">
    Frames are delivered as NumPy arrays with shape `(H, W, 3)`, dtype `uint8`, in RGB color order. They start arriving once the connection reaches `READY` and continue until you disconnect.

    ```python Frame handler theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    @reactor.on_frame
    def on_frame(frame):
        print(f"Frame: {frame.shape}")  # e.g. (768, 1280, 3)
    ```

    Common uses:

    ```python Common uses theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    # Save with PIL
    from PIL import Image
    Image.fromarray(frame).save("frame.png")

    # Process with OpenCV (convert RGB to BGR first)
    import cv2
    cv2.imwrite("frame.png", cv2.cvtColor(frame, cv2.COLOR_RGB2BGR))

    # Run inference
    result = my_model(frame)
    ```
  </Tab>
</Tabs>

***

## Sending commands

Commands control what the model does. The available commands depend on the model. See the [Model API Reference](/model-api-reference/overview). Wait for the connection to reach `ready` before sending commands; the model is not available until the connection is fully established.

<Tabs>
  <Tab title="JavaScript">
    ```typescript Send command theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    reactor.on("statusChanged", async (status) => {
      if (status === "ready") {
        await reactor.sendCommand("set_prompt", { prompt: "a forest at dawn" });
      }
    });
    ```
  </Tab>

  <Tab title="React">
    ```tsx Send command theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    import { useReactor } from "@reactor-team/js-sdk";

    function Controls() {
      const { sendCommand, status } = useReactor((s) => s);

      return (
        <button
          disabled={status !== "ready"}
          onClick={() => sendCommand("set_prompt", { prompt: "a forest at dawn" })}
        >
          Set prompt
        </button>
      );
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python Send command theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    from reactor_sdk import ReactorStatus

    @reactor.on_status(ReactorStatus.READY)
    async def on_ready(status):
        await reactor.send_command("set_prompt", {"prompt": "a forest at dawn"})
    ```
  </Tab>
</Tabs>

***

## File uploads

Upload files and pass them to commands. See [File Uploads](/concepts/file-uploads) for the full guide.

<Tabs>
  <Tab title="JavaScript">
    ```typescript File upload theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    const file = input.files[0];

    const ref = await reactor.uploadFile(file);
    await reactor.sendCommand("set_image", { image: ref });
    ```
  </Tab>

  <Tab title="React">
    ```tsx File upload theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    import { useReactor } from "@reactor-team/js-sdk";

    function ImageUpload() {
      const { uploadFile, sendCommand, status } = useReactor((s) => s);

      const handleFile = async (e: React.ChangeEvent<HTMLInputElement>) => {
        const file = e.target.files?.[0];
        if (!file) return;

        const ref = await uploadFile(file);
        await sendCommand("set_image", { image: ref });
      };

      return (
        <input
          type="file"
          accept="image/*"
          disabled={status !== "ready"}
          onChange={handleFile}
        />
      );
    }
    ```
  </Tab>

  <Tab title="Python">
    `upload_file()` accepts a file path, raw bytes, or a file-like object.

    ```python File upload theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    from reactor_sdk import Reactor, ReactorStatus

    @reactor.on_status(ReactorStatus.READY)
    async def on_ready(status):
        ref = await reactor.upload_file("photo.jpg")
        await reactor.send_command("set_image", {"image": ref})
    ```

    ```python Upload variants theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    # From a path (name and MIME type are inferred)
    ref = await reactor.upload_file("photo.jpg")

    # From bytes
    ref = await reactor.upload_file(image_bytes, name="photo.jpg")

    # From a file object
    with open("photo.jpg", "rb") as f:
        ref = await reactor.upload_file(f)
    ```
  </Tab>
</Tabs>

`uploadFile()` returns a `FileRef` ([JavaScript](/sdk-reference/types#fileref) ·
[Python](/sdk-reference/python/types#fileref)) containing the upload ID, filename, MIME type, and
size. Pass multiple `FileRef` values in a single command and mix them with scalar arguments like
`transition`.

***

## Receiving messages

Models can send structured messages back to your app, such as the current frame number, generation state, or custom events.

<Tabs>
  <Tab title="JavaScript">
    ```typescript Message handler theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    reactor.on("message", (msg) => {
      if (msg.type === "state") {
        console.log("Frame:", msg.data.current_frame);
      }
    });
    ```
  </Tab>

  <Tab title="React">
    ```tsx Message handler theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    import { useReactorMessage } from "@reactor-team/js-sdk";

    function StateDisplay() {
      const [frame, setFrame] = useState(0);

      useReactorMessage((msg) => {
        if (msg.type === "state") {
          setFrame(msg.data.current_frame);
        }
      });

      return <div>Frame: {frame}</div>;
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python Message handler theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    @reactor.on_message
    def on_message(msg):
        if msg.get("type") == "state":
            print(f"Frame: {msg['data']['current_frame']}")
    ```

    Using the event API directly:

    ```python Event API theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    def on_message(msg):
        print(msg)

    reactor.on("message", on_message)
    reactor.off("message", on_message)
    ```
  </Tab>
</Tabs>

***

## Sending video input

For models that accept a video input (e.g. video-to-video models), publish a track to the input declared by the model. Publishing is **explicit**: the model receives nothing until you publish, and connecting on its own never starts sending. Publish once the connection is `ready`, and call `unpublishTrack()` to stop.

<Tabs>
  <Tab title="JavaScript">
    Get a media stream and publish it once connected:

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

    const reactor = new Reactor({ modelName: "your-model-name" });

    const stream = await navigator.mediaDevices.getUserMedia({ video: true });
    const track = stream.getVideoTracks()[0];

    reactor.on("statusChanged", async (status) => {
      if (status === "ready") {
        await reactor.publishTrack("webcam", track);
      }
    });

    await reactor.connect(token);
    ```

    To stop publishing:

    ```typescript Unpublish theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    await reactor.unpublishTrack("webcam");
    stream.getTracks().forEach((t) => t.stop());
    ```
  </Tab>

  <Tab title="React">
    Use `WebcamStream` and pass the name of the model's input track:

    ```tsx WebcamStream theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    import { ReactorProvider, ReactorView, WebcamStream } from "@reactor-team/js-sdk";

    <ReactorProvider modelName="your-model-name" jwtToken={token}>
      <ReactorView className="w-full aspect-video" />
      <WebcamStream track="webcam" className="w-48 aspect-video" />
    </ReactorProvider>
    ```

    `WebcamStream` handles camera permissions, publishing when ready, and cleanup on unmount.
  </Tab>

  <Tab title="Python">
    Publish a `MediaStreamTrack` to the track declared by the model:

    ```python Publish track theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    from reactor_sdk import Reactor, ReactorStatus

    reactor = Reactor(
        model_name="your-model-name",
        api_key=os.environ["REACTOR_API_KEY"],
    )

    @reactor.on_status(ReactorStatus.READY)
    async def on_ready(status):
        await reactor.publish_track("webcam", my_track)
    ```

    To stop publishing:

    ```python Unpublish theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    await reactor.unpublish_track("webcam")
    ```
  </Tab>
</Tabs>

<Note>
  The track name (`"webcam"`) must match the attribute name declared on the model. The model declares which tracks it accepts in its capabilities, which are fetched automatically during connection.
</Note>

***

## Error handling

Handle connection and runtime errors from the SDK.

<Tabs>
  <Tab title="JavaScript">
    ```typescript Error handler theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    reactor.on("error", (error) => {
      console.error(`[${error.component}] ${error.code}: ${error.message}`);

      if (error.recoverable) {
        reactor.reconnect();
      }
    });
    ```
  </Tab>

  <Tab title="React">
    ```tsx Error display theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    import { useReactor } from "@reactor-team/js-sdk";

    function ErrorDisplay() {
      const lastError = useReactor((s) => s.lastError);

      if (!lastError) return null;

      return (
        <div>
          <strong>{lastError.code}</strong>: {lastError.message}
        </div>
      );
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python Error handler theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    from reactor_sdk import ReactorError

    @reactor.on_error
    def on_error(error: ReactorError):
        print(f"[{error.component}:{error.code}] {error.message}")

        if error.recoverable:
            print(f"Retrying in {error.retry_after or 3}s")
    ```
  </Tab>
</Tabs>

See `ReactorError` ([JavaScript](/sdk-reference/types#reactorerror) ·
[Python](/sdk-reference/python/types#reactorerror)) for all error codes and fields.

***

## Reconnection

If a connection drops unexpectedly, the session stays alive on the GPU for **30 seconds**. Reconnect
within that window to resume without losing server-side state. For more information, see
[Sessions](/concepts/sessions#disconnecting).

<Tabs>
  <Tab title="JavaScript">
    ```typescript Reconnect theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    // Terminate the session
    await reactor.disconnect();

    // Disconnect but keep the session alive on the server
    await reactor.disconnect(true);
    await reactor.reconnect();
    ```
  </Tab>

  <Tab title="React">
    ```tsx Reconnect theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    import { useReactor } from "@reactor-team/js-sdk";

    const { disconnect, reconnect } = useReactor((s) => s);

    // Terminate the session
    await disconnect();

    // Disconnect but keep the session alive on the server
    await disconnect(true);
    await reconnect();
    ```
  </Tab>

  <Tab title="Python">
    ```python Reconnect theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    # Terminate the session
    await reactor.disconnect()

    # Disconnect but keep the session alive on the server
    await reactor.disconnect(recoverable=True)
    await reactor.reconnect()
    ```
  </Tab>
</Tabs>

To reconnect automatically when the error is recoverable:

<Tabs>
  <Tab title="JavaScript">
    ```typescript Auto-reconnect theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    reactor.on("error", (error) => {
      if (error.recoverable) {
        setTimeout(() => reactor.reconnect(), (error.retryAfter ?? 3) * 1000);
      }
    });
    ```
  </Tab>

  <Tab title="React">
    ```tsx Auto-reconnect theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    import { useReactor } from "@reactor-team/js-sdk";

    const { reconnect, lastError } = useReactor((s) => ({
      reconnect: s.reconnect,
      lastError: s.lastError,
    }));

    // Watch the store's lastError and reconnect when a recoverable error lands.
    useEffect(() => {
      if (lastError?.recoverable) {
        const timer = setTimeout(() => reconnect(), (lastError.retryAfter ?? 3) * 1000);
        return () => clearTimeout(timer);
      }
    }, [lastError, reconnect]);
    ```
  </Tab>

  <Tab title="Python">
    ```python Auto-reconnect theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    from reactor_sdk import ReactorError

    @reactor.on_error
    async def on_error(error: ReactorError):
        if error.recoverable:
            await asyncio.sleep(error.retry_after or 3)
            await reactor.reconnect()
    ```
  </Tab>
</Tabs>

***

## Sharing a session

Multiple clients can connect to one session at the same time. Create a session on one client (or your
backend), read its id with `getSessionId()`, then adopt it from another client by passing `sessionId`
to `connect()`. The creator owns the session lifecycle; clients that adopt it leave it running when
they disconnect.

<Tabs>
  <Tab title="JavaScript">
    ```typescript Adopt a session theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    // sessionId from your backend or another client
    await reactor.connect(token, { sessionId });
    ```
  </Tab>

  <Tab title="React">
    ```tsx Adopt a session theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
    <ReactorProvider
      modelName="your-model-name"
      jwtToken={token}
      connectOptions={{ sessionId, autoConnect: true }}
    >
      <ReactorView className="w-full aspect-video" />
    </ReactorProvider>
    ```
  </Tab>
</Tabs>

See [Sessions › Multiple connections per session](/concepts/sessions#multiple-connections-per-session)
for the full lifecycle and connection-id details.

***

## Full API reference

<CardGroup cols={2}>
  <Card title="Typed model SDKs" icon="boxes" href="/sdk-reference/typed-model-sdk">
    `HeliosModel`, `LingbotModel`, and their React layers over the base SDK.
  </Card>

  <Card title="JavaScript: Reactor class" icon="box" href="/sdk-reference/reactor-class">
    The base class for connecting to any model and sending raw commands.
  </Card>

  <Card title="React components & hooks" icon="component" href="/sdk-reference/react-components">
    `ReactorProvider`, `ReactorView`, `WebcamStream`, `useReactor`, and more.
  </Card>

  <Card title="Python: Reactor class" icon="python" href="/sdk-reference/python/reactor">
    The base Python class, decorators, and types for server-side use.
  </Card>
</CardGroup>
