---
title: "Embedded control room"
description: "Give authorized operators a live, capability-scoped SeatManager board inside your own application."
---

`SeatManager` is a same-document operator board—not an iframe. It renders the
event map, subscribes to live inventory, shows occupancy and activity, and
exposes controlled custom selection, blocking, category, table, section,
channel, cancellation, and reporting tools.

## Trust boundary

Your backend authorizes the operator and asks SeatLayer to mint an event-scoped
`mse_…` token.
The browser receives only that token. Never put an account secret key in the
page.

<FlowDiagram
  label="Embedded control-room trust boundary"
  steps={[
    {
      actor: "Operator browser",
      title: "Requests Event access",
      detail: "The operator opens your application and asks for the control room for one Event.",
      tone: "buyer",
    },
    {
      actor: "Your backend",
      title: "Grants explicit capabilities",
      detail: "It authorizes the operator, then mints an mse_ token for the Event, origin, and exact allowed actions.",
      tone: "platform",
    },
    {
      actor: "Browser",
      title: "Mounts SeatManager",
      detail: "The page receives only the temporary token and expiry, not an account secret key.",
      tone: "buyer",
    },
    {
      actor: "SeatLayer",
      title: "Provides live scoped operations",
      detail: "SeatManager reads live inventory and can perform only the actions the issued capability permits.",
      tone: "seatlayer",
    },
  ]}
/>

## Mint a manage session

```bash
curl -sX POST \
  "https://api.seatlayer.io/v1/events/ev_9f3a/manage-sessions" \
  -H "authorization: Bearer $SEATLAYER_SECRET_KEY" \
  -H "content-type: application/json" \
  -d '{
    "allowedOrigin": "https://admin.example.com",
    "capabilities": [
      "event:view",
      "event:block",
      "event:categories:manage",
      "event:tables:manage",
      "event:reports"
    ],
    "expiresInSeconds": 3600
  }'
```

Add `event:cancel` only for operators allowed to return booked inventory to
sale. A browser manage token cannot confirm a booking: the platform backend
does that through `/book`. Managed Box Office is a separate commerce surface
and capability.

If your backend omits `capabilities`, the API issues a view-only token with
exactly `event:view`. Blocking, cancellation, reports, channels, and Managed
Ticketing operations always require an explicit capability.

## Mount the board

```js title="browser/control-room.js"
import { SeatManager } from "@seatlayer/js/manager";

const session = await fetch("/api/events/ev_9f3a/manage-session", {
  method: "POST",
}).then((response) => response.json());

const room = new SeatManager({
  container: "#control-room",
  eventKey: "ev_9f3a",
  token: session.token,
  tokenExpiresAt: session.expiresAt,
  mode: "view",
  keepLiveWhileHidden: true,
  followLive: false,
  onTokenRefresh: async () => {
    const next = await fetch("/api/events/ev_9f3a/manage-session", {
      method: "POST",
    }).then((response) => response.json());
    return { token: next.token, expiresAt: next.expiresAt };
  },
  onError: (error) => showOperatorError(error),
});

await room.render();
```

Give the container a real height. SeatManager fills that box and watches it with
`ResizeObserver`. Fullscreen uses the browser Fullscreen API.

## Operator modes

Tools are grouped in the toolbar as **Watch** (`view`, `inspect` — read-only),
**Manage** (`block`, `sections`, `categories`, `tables`, `channels` — changes
this event only) and **Host tools** (`select`, `filterSections` — hands a
selection or filter to your app). Pass `tools` to offer a subset; it filters
what is shown and never grants a capability.

| Mode | Purpose | Capability |
|---|---|---|
| `view` | Live occupancy, activity, momentum, presence, and aggregate inventory performance | `event:view` |
| `inspect` | Read the current state and context of one seat | `event:view` |
| `select` | Select live event objects for a host-owned custom operation; no inventory mutation | `event:view` |
| `filterSections` | Filter and frame matching section labels for host-owned reporting | `event:view` |
| `block` | Select and block/unblock individual inventory | `event:block` |
| `sections` | Open, close, hide, or schedule sections/zones | `event:block` |
| `categories` | Change event-only category assignments | `event:categories:manage` |
| `tables` | Change free tables between per-chair and whole-table booking | `event:tables:manage` |
| `channels` | Inspect or manage private sales allocations | `event:channels:view` / `event:channels:manage` |

`event:reports` is separate from the live board. Add it only when the embedded
staff workflow must open booking history, category/section/channel breakdowns,
downloadable inventory reports, or the full audit history. SeatLayer Orders,
tickets, refunds, and attendance are not part of the Platform/SDK Control room.

Following live activity is off by default so a busy event does not repeatedly
move the operator's camera. Heat overlays express sales velocity without
replacing chart category colors.

## Common actions

```ts title="operator/actions.ts"
room.setMode("block");
room.selectSection("sec-stalls");

await room.block(undefined, {
  releaseAt: Date.parse("2026-08-01T19:00:00Z"),
  reason: "Soundcheck hold",
});

await room.unblock(["A-3"]);
await room.unblockAll();

await room.cancelBooking(["C-14", "C-15"], "order_5567");
await room.setHoldTtl(10 * 60 * 1000);

room.setMode("select");
room.selectObjects(["A-1", "A-2"]);
const selectionState = room.getSelectionValidity();

room.setMode("filterSections");
const matchedSections = room.setFilteredSection("Balcony");
room.clearFilteredSection();

await room.setCategory("vip", ["A-1", "A-2"]);
await room.setTableBooking(["table-1"], "whole");

const report = await room.getReport();
const log = await room.getLog({ limit: 50 });
```

Cancellation changes inventory only; your application owns its commercial
order, ticket cancellation, refund policy, payment execution, and buyer
communication. The booking reference prevents releasing a newer booking.

Category assignments are event-only: the reusable venue chart is unchanged,
and existing hold/booking price snapshots are not rewritten. Table-mode changes
alter inventory identity and are therefore refused while any affected table is
held, booked, or blocked, or when a private-channel allocation would be lost.

## Long-running boards

Pass both `tokenExpiresAt` and `onTokenRefresh`. SeatManager proactively
requests a replacement and also retries once after an authorization failure.
Your refresh endpoint must repeat the full host-user authorization check rather
than accepting the expired token as proof.

`keepLiveWhileHidden: true` forces a fresh paint after live deltas when browsers
throttle background animation frames. Use it for a wallboard; consider battery
and resource costs on laptops.

## Custom operator UI

Use `ManageApi` when the packaged board does not match your workflow. It exposes
the same token-authenticated reads and mutations: availability, category/table
management, block/unblock, cancellation, hold TTL, report, control-room
snapshot, audit log, and CSV Blob.
Keep capability rules identical to SeatManager.

## Verification

- [ ] The backend authorizes event and tenant before every SeatLayer session request.
- [ ] The token carries only necessary capabilities.
- [ ] The board has a bounded, responsive container.
- [ ] Token rotation is tested across laptop sleep and network loss.
- [ ] Detailed reports and full audit history stay unavailable unless `event:reports` is minted.
- [ ] Cancellation is paired with an explicit refund workflow.
- [ ] Conflict errors ask the operator to reload or reselect.
- [ ] `destroy()` runs during unmount and logout.
- [ ] Revocation occurs when access ends.

Continue to the [SeatManager reference](/platform/seat-manager),
[embed sessions](/platform/embed-sessions), and
[reports](/server-api/reports).