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

# Live view

> Watch or take over a sandbox's terminal or desktop from your own page.

Live view streams a sandbox to a browser: its terminal, or its desktop. Your backend mints a short-lived **viewer token** with your API key, and your page shows the returned embed URL in an iframe. Many people can watch the same sandbox; the sandbox serves one stream per view, whatever the number of viewers.

| Mode (`channels`) | Shows | Status |
| - | - | - |
| `pty` (embed mode `terminal`) | A shell in the sandbox (xterm.js). Needs `control` scope: it starts a shell | Available |
| `desktop` | The `desktop` template's screen (noVNC) | Available |
| `dom` | The page open in the sandbox's Chrome, recorded with rrweb and replayed in your browser: light, sharp at any size, passwords masked. Templates with Chrome (`desktop`, `coding-agent`); Chrome is started with DevTools on first use if it is not running | Available |
| `thumb` | A 320 px WebP of the screen (or of the active Chrome page) every 3 seconds | Available |

Scopes: `view` watches; `control` also sends keystrokes, mouse and resize. Tokens last 30 to 600 seconds (default 600).

## Mint a token

```python live_view.py theme={null}
import os
from anona import Sandbox

sbx = Sandbox.connect(os.environ["SANDBOX_ID"])

url = sbx.live.url(mode="pty", scope="control", ttl=300)  # terminal you can type into
print(url)

tok = sbx.live.token(scope="view", channels=["desktop"], ttl=120)
print(tok.expires_at, tok.claims()["ch"])

sbx.live.revoke_all()  # closes every open viewer, invalidates issued tokens
```

```typescript live_view.ts theme={null}
import { Sandbox } from "@anona/sdk";

const sbx = await Sandbox.connect(process.env.SANDBOX_ID!);

const url = await sbx.live.url({ mode: "pty", scope: "control", ttl: 300 });
console.log(url);

const tok = await sbx.live.token({ scope: "view", channels: ["desktop"], ttl: 120 });
console.log(tok.expiresAt, tok.claims().ch);

await sbx.live.revokeAll();
```

```bash live_view.sh theme={null}
anona-compute view "$SANDBOX_ID" --mode terminal --control --ttl 5m
anona-compute view "$SANDBOX_ID" --mode desktop --json
```

The embed URL looks like `https://stream.compute.anonalabs.com/embed/v1/<id>?mode=terminal#t=<token>`. The token is in the URL fragment, which browsers never send to a server, so it does not end up in logs.

## Embed it

Mint the token on your server (never ship your API key to a browser) and put the URL in an iframe:

```html theme={null}
<iframe
  src="https://stream.compute.anonalabs.com/embed/v1/SANDBOX_ID?mode=terminal#t=TOKEN"
  style="width: 100%; height: 480px; border: 0"
></iframe>
```

### Keep a long session alive

A token expires after at most 10 minutes. The embed asks its host page for a new one shortly before that, with `postMessage`. Answer it with a fresh token from your backend:

```js theme={null}
const frame = document.querySelector("iframe");
const streamOrigin = "https://stream.compute.anonalabs.com";

window.addEventListener("message", async (event) => {
  if (event.origin !== streamOrigin || event.data?.source !== "anona-viewer") return;
  if (event.data.type === "token-request") {
    const { token } = await fetch("/api/viewer-token").then((r) => r.json()); // your backend mints it
    frame.contentWindow.postMessage(
      { source: "anona-viewer-host", type: "token", id: event.data.id, token },
      streamOrigin,
    );
  }
  if (event.data.type === "state") console.log("viewer", event.data.state);
});
```

The viewer reconnects on its own after network drops. It stops for good when the token is revoked, the sandbox is gone, or the token does not allow the channel.

## Thumbnails

For lists and dashboards, fetch the latest thumbnail directly. It is a WebP while the sandbox has a screen or a Chrome page to show, and `204 No Content` otherwise:

```bash thumbnail.sh theme={null}
code=$(curl -sS -o thumb.webp -w '%{http_code}' \
  "https://api.compute.anonalabs.com/v1/sandboxes/$SANDBOX_ID/thumbnail" -H "X-API-Key: $E2B_API_KEY")
echo "thumbnail: HTTP $code"   # 200 with image/webp, or 204 when there is nothing to show
```

The sandbox grabs frames only while someone asks for them, and every viewer shares the same frames. Send `If-None-Match` with the last `ETag` to get `304` when nothing changed.

## Who may mint

| Caller | `view` | `control` |
| - | - | - |
| API key with `sandboxes:use` | Yes | Yes |
| Dashboard owner, admin, member | Yes | Yes |
| Dashboard viewer | Yes | No |

A key limited to certain sandboxes can mint only for those. A sandbox of another organization answers `404`. The dashboard's sandbox page has the same live view built in.


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