> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://platform.bctrl.ai/llms.txt.
> For full documentation content, see https://platform.bctrl.ai/llms-full.txt.

# Live View

> Embed a real-time, watch-only or interactive view of an active run.

The live view gives you an embeddable iframe for the active run. Use it to watch automation as it happens, or hand control to a human for an in-the-loop step.

Get the run from the runtime, then create the viewer from the run.

```ts
const run = await runtime.currentRun();

const { iframeUrl } = await run.live({
  control: "none",
});
```

Drop `iframeUrl` into an iframe. The URL is opaque and short-lived.

```html
<iframe src={iframeUrl} style="width: 100%; height: 600px; border: none;" />
```

<Callout intent="info" title="Viewer URLs are opaque">
  The URL is backed by a short-lived server-side lease. Do not parse it, build sub-URLs from it, or store it long-term. When `expiresAt` passes, create a new viewer URL.
</Callout>

## Options

```ts
const { iframeUrl, activityStreamUrl, expiresAt } = await run.live({
  control: "input",
  components: ["activity"],
});
```

| Parameter          | Type                | Required | Description                                                                         |
| ------------------ | ------------------- | -------- | ----------------------------------------------------------------------------------- |
| `control`          | `"none" \| "input"` | No       | `"none"` is watch-only. `"input"` forwards mouse and keyboard input to the browser. |
| `components`       | `Array<"activity">` | No       | Request viewer add-ons. Pass `["activity"]` to receive `activityStreamUrl`.         |
| `expiresInSeconds` | `number`            | No       | Lease lifetime override. Default is one hour; max is 24 hours.                      |

## Watch-only vs interactive

```ts
const monitor = await run.live();

const handoff = await run.live({
  control: "input",
});
```

`control: "none"` is enforced server-side. Use it for dashboards and shared viewers. Use `control: "input"` only when a user should be able to control the browser.

## Refresh an expired viewer

```ts
async function refreshViewer(runtime, iframe) {
  const run = await runtime.currentRun();
  const viewer = await run.live({ control: "input" });

  iframe.src = viewer.iframeUrl;

  const expiresIn = new Date(viewer.expiresAt).getTime() - Date.now();
  setTimeout(() => refreshViewer(runtime, iframe), Math.max(60_000, expiresIn - 60_000));
}
```

## HTTP equivalent

```http
POST /v1/runs/{runId}/live
```

## Related

* [Runs](/sdk/core-concepts/runs)
* [Run Events](/sdk/observability/run-events)
* [Recording](/sdk/observability/recording)