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

# App bridge

> The window.runchat object available to HTML apps: run tools, store state, drive commands and the scene, and reach Rhino.

Every HTML app runs in a sandboxed frame with a `window.runchat` object already on the page. It is how the app talks to Runchat: running published tools, storing state, running the app's commands, editing the scene and, in Rhino app tools, reaching the user's Rhino.

The same object is available in three places, so one HTML file works in all of them:

| Where | What it is |
| - | - |
| **Canvas preview** | An HTML [Code node](https://docs.runchat.com/concepts/nodes/code.md) on the canvas. See [Apps on the canvas](https://docs.runchat.com/concepts/editor/apps.md) |
| **Published app** | An app [artifact](https://docs.runchat.com/concepts/publishing/app-view.md) at `runchat.com/artifacts/<handle>/<slug>` |
| **Rhino app tool** | A published app installed as a [CAD tool](https://docs.runchat.com/concepts/integrations/rhino-tools.md#app-tools) in Rhino |

Every method returns a Promise. Calls made before the host page is ready are queued and sent once it is, so you can call them as soon as your script runs.

```js theme={null}
const { user } = await runchat.ready;
if (!user.signed_in) showSignInHint();

const result = await runchat.run("YOUR_TOOL_ID", { prompt: "a timber pavilion" });
await runchat.state.set("last", result.outputs);
```

## Methods

| Method | Returns | Canvas | Published |
| - | - | :-: | :-: |
| `runchat.ready` | `{ user, app }` once the host answers | ✓ | ✓ |
| `runchat.user()` | `{ signed_in, id, handle, name }` | ✓ | ✓ |
| `runchat.run(toolId, inputs, opts)` | `{ status, outputs, error, credits }` | ✓ | ✓ |
| `runchat.upload(file)` | The uploaded file's URL | ✓ | ✓ |
| [`runchat.state.get(key, opts)`](#state) | The stored value, or `null` | ✓ | ✓ |
| [`runchat.state.set(key, value, opts)`](#state) | | ✓ | ✓ |
| [`runchat.state.delete(key, opts)`](#state) | | ✓ | ✓ |
| [`runchat.state.list(opts)`](#state) | `{ items: [{ key, value }], truncated }` | ✓ | ✓ |
| `runchat.state.apply(ops)` | `{ results }` | ✓ | |
| `runchat.state.subscribe(fn)` | An unsubscribe function | ✓ | |
| `runchat.command(name, args, opts)` | `{ result, applied }` | ✓ | |
| [`runchat.scene.subscribe(fn)`](#scene) | An unsubscribe function | ✓ | |
| [`runchat.scene.edit(ops)`](#scene) | The result of each edit | ✓ | |
| [`runchat.rhino.*`](#rhino) | See [Rhino](#rhino) | | Rhino app tools |

Methods without a tick reject with `UNSUPPORTED` in that place.

### `runchat.ready`

A Promise (not a function) that resolves when the host page answers:

```js theme={null}
{
  user: { signed_in: true, id: "…", handle: "ada", name: "Ada" },
  app: {
    artifactId: "…",   // null in the canvas preview
    test: true,        // true in the canvas preview
    instanceId: "…"    // the app node whose shared state the app reads
  }
}
```

### `runchat.user()`

Who is using the app: `{ signed_in, id, handle, name }`. Signed-out visitors get `{ signed_in: false }`.

### `runchat.run()`

```js theme={null}
runchat.run(toolId, inputs, { onProgress, timeout })
```

Runs a [published tool](https://docs.runchat.com/concepts/publishing/custom-nodes.md) on Runchat's servers and resolves when it finishes.

| Parameter | Type | Description |
| - | - | - |
| `toolId` | `string` | The tool's runchat id |
| `inputs` | `object` | Input values, keyed by the tool's input names |
| `opts.onProgress` | `function` | Called with progress updates, such as `{ phase: "running" }` |
| `opts.timeout` | `number` | Milliseconds before the call rejects. Default 600000 (10 minutes) |

Resolves with `{ status, outputs, error, credits }`. `status` is `"success"` or `"error"`, and `outputs` is keyed by the tool's output names.

The run is billed to the person using the app, so they must be signed in. On a published app, signed-out visitors are prompted to sign in and the call rejects with `SIGNED_OUT`.

### `runchat.upload()`

```js theme={null}
const url = await runchat.upload(fileInput.files[0]);
```

Uploads a `File` or `Blob` and resolves with its hosted URL, ready to pass to `runchat.run` as an image or file input. Needs a signed-in user. The default timeout is 2 minutes; pass `{ timeout }` as the second argument to change it.

## State

State is a small key-value store for the app. Each call takes an optional `{ scope }`:

| Scope | Who can read | Who can write |
| - | - | - |
| `"user"` (default) | Only the current person | Only the current person |
| `"shared"` | Everyone using the app | On the canvas: the workflow's owner, teammates and agents. On a published app: only the app's owner |

```js theme={null}
await runchat.state.set("favourites", ids);
const favourites = (await runchat.state.get("favourites")) ?? [];
await runchat.state.delete("draft");

const { items, truncated } = await runchat.state.list({ prefix: "render/", scope: "shared" });
```

In the canvas preview, `"user"` state is kept in your browser as test state and does not carry over when the app is published. `"shared"` state is the app node's live state, saved with the workflow. Generated media URLs saved into state on a published app are copied to permanent storage, so they keep working after the provider's temporary links expire.

### `runchat.state.apply()`

Applies a list of changes to shared state in one step. Canvas only for now.

```js theme={null}
await runchat.state.apply([
  { op: "append", key: "strokes", items: [stroke], max: 500 },
  { op: "merge", key: "settings", value: { brush: 4 } },
]);
```

| Op | Fields | Effect |
| - | - | - |
| `set` | `key`, `value` | Replaces a key |
| `merge` | `key`, `value` | Shallow-merges an object into a key |
| `append` | `key`, `items`, optional `max` | Adds items to a list, keeping at most `max` |
| `delete` | `key` | Removes a key |
| `clear` | | Removes every key |

### `runchat.state.subscribe()`

```js theme={null}
const unsubscribe = runchat.state.subscribe((state) => render(state));
```

Calls `fn` with the whole shared state straight away and again on every change, whether it came from this page, another person or an agent. Returns a function that stops the updates. Canvas only for now.

### Limits

* Each key holds up to 256 KB. Split large documents across several keys.
* Up to 1,000 keys per app, and up to 200 changes in one `apply`.

## `runchat.command()`

```js theme={null}
const { result } = await runchat.command("add_voxels", { cells: [[0, 0, 0]] });
```

Runs one of the app's commands, defined in its `commands.js`, on Runchat's servers. Resolves with the command's `result` and `applied`, the state changes it made. The default timeout is 2 minutes. Canvas only for now. See [Writing commands](https://docs.runchat.com/concepts/editor/apps.md#writing-commands).

## Scene

```js theme={null}
const unsubscribe = runchat.scene.subscribe((objects) => drawOverlay(objects));
await runchat.scene.edit([{ op: "add", ... }]);
```

Canvas only for now. `scene.subscribe(fn)` calls `fn` with the objects in the workflow's [Scene](https://docs.runchat.com/concepts/editor/scene.md), now and whenever they change. `scene.edit(ops)` edits the scene as the person using the app, with the same operations as the `edit_scene` tool, and resolves with one result per operation. Run `runchat use_skill --name html-code` for the full list of operations.

## Rhino

`runchat.rhino` reaches the Rhino of the person using the app. It only works in a [Rhino app tool](https://docs.runchat.com/concepts/integrations/rhino-tools.md#app-tools) that they have installed, while Rhino is open with the Runchat panel connected. Anywhere else, the calls reject with `UNSUPPORTED`.

| Method | Returns |
| - | - |
| `runchat.rhino.context()` | Units, object counts, the selection and the active view |
| `runchat.rhino.capture(options)` | `{ url, metadata }`: an uploaded viewport image |
| `runchat.rhino.exportSelection({ scope, layer })` | `{ url, objects, bounds, units, layers }`: a GLB of the `"selection"`, a `"layer"` or `"all"` |
| `runchat.rhino.runScript(code, { language })` | `{ success, message, output, console }` |
| `runchat.rhino.importModel(url)` | `{ started }`. Imports a `.glb` (as meshes) or `.svg` (as curves) on the current layer |

`capture` accepts `view`, `display_mode`, `camera_location`, `camera_target`, `frame` (`"all"`, `"selection"` or object ids), `width` and `height`. The person's own view is restored afterwards.

`runScript` takes `language: "python"` (default) or `"csharp"`. Set a `runchat_result` variable in the script to return data in `output`. Each run is one undo step in Rhino.

```js theme={null}
const shot = await runchat.rhino.capture({ display_mode: "Shaded", width: 1024 });
const { outputs } = await runchat.run("YOUR_TOOL_ID", { image: shot.url });
await runchat.rhino.importModel(outputs.model);
```

## Canvas preview only

In the canvas preview, `runchat.setNodeOutputs(outputs)` writes values to the HTML Code node's outputs, so an app can feed the rest of the workflow:

```js theme={null}
runchat.setNodeOutputs({ result: selectedImageUrl });
```

## Errors

Failed calls reject with an `Error` whose `code` says why:

| Code | Meaning |
| - | - |
| `SIGNED_OUT` | The method needs a signed-in user |
| `PAYWALL` | The person doesn't have enough credits |
| `FORBIDDEN` | The person can't run this tool or write this state |
| `RATE_LIMIT` | Too many requests |
| `UNSUPPORTED` | The method isn't available here, such as `runchat.rhino` outside an installed Rhino app tool |
| `ERROR` | Anything else; see `message` |

```js theme={null}
try {
  await runchat.run(toolId, inputs);
} catch (e) {
  if (e.code === "PAYWALL") showUpgrade();
  else showError(e.message);
}
```

Calls that take longer than their timeout reject with a `timed out` message. The default is 15 seconds, except where noted above.


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