---
title: "SeatManager reference"
description: "SeatManager options, modes, callbacks, methods, data authority, and the lower-level ManageApi."
---

`SeatManager` is the complete operator SDK surface. It mounts a native canvas
and control rails in your document, subscribes to live state, and sends private
actions with an event-scoped manage token.

```ts
import { SeatManager } from "@seatlayer/js/manager";

const room = new SeatManager({
  container: "#control-room",
  eventKey: "ev_9f3a",
  token,
});

await room.render();
```

## Constructor options

| Option | Type | Required | Notes |
|---|---|---:|---|
| `container` | `string \| HTMLElement` | Yes | Must resolve to a sized element |
| `eventKey` | `string` | Yes | One event |
| `token` | `string` | Yes | Browser-safe `mse_…`; never expose `sk_…` |
| `apiBase` | `string` | No | Defaults to `https://api.seatlayer.io` |
| `tokenExpiresAt` | `number` | No | Epoch ms; enables proactive rotation |
| `mode` | `SeatManagerMode` | No | Default `view` |
| `currency` | `string` | No | Fallback; event/chart currency wins |
| `theme` | `ChartTheme` | No | Operator chrome override; canvas uses chart |
| `keepLiveWhileHidden` | `boolean` | No | Default `true` |
| `followLive` | `boolean` | No | Default `false` |
| `selectedObjects` | `string[]` | No | Public labels preselected after live inventory loads in `select` mode |
| `selectableObjects` | `string[]` | No | Labels allowed as exceptions when unavailable objects are otherwise excluded |
| `unavailableObjectsSelectable` | `boolean` | No | Include held, booked, and blocked objects in `select`; default `true` |
| `maxSelectedObjects` | `number` | No | Maximum custom-operation selection |
| `numberOfPlacesToSelect` | `number` | No | Require exactly this many objects and use it as the selection cap |
| `isObjectSelectable` | `(object, defaultValue) => boolean` | No | Final host policy for each object in `select` mode |
| `capabilities` | `SeatManagerCapability[]` | No | Declared token capabilities used to hide/fail-close privileged tools |
| `tools` | `SeatManagerMode[]` | No | Which tools the toolbar offers. Default: every tool the capabilities permit. Listing a tool never grants it, and `view` is always kept |

```ts
type SeatManagerMode =
  | "view"
  | "inspect"
  | "select"
  | "filterSections"
  | "block"
  | "sections"
  | "categories"
  | "tables"
  | "channels";
```

`view` is the read-only live/static event view. `select` is a read-only
custom-operation tool, while `filterSections` frames one public section label
for host-owned reporting. None of those tools mutates inventory. `categories`
and `tables` make event-only changes and are hidden unless their dedicated
capabilities are declared.

The toolbar presents these in three labelled groups, so an operator can tell at
a glance what a tab will do:

| Group | Tools | What it means |
|---|---|---|
| Watch | `view`, `inspect` | Read-only. Nothing changes. |
| Manage | `block`, `sections`, `categories`, `tables`, `channels` | Changes this event only. The reusable chart is never edited. |
| Host tools | `select`, `filterSections` | Hands a selection or a filter to *your* app; the control room itself changes nothing. |

Each rail repeats that scope as a badge next to its title (`Read-only`,
`This event only`, `Your app`).

Use `tools` to hide what a surface does not need — a dashboard that never
consumes `onSelectionChange` should not show a Select tab that leads nowhere:

```ts
new SeatManager({
  container: "#room",
  eventKey: "ev_123",
  token: manageToken,
  capabilities: ["event:view", "event:block", "event:categories:manage"],
  // Watch + Manage only. The whole "Host tools" group disappears.
  tools: ["view", "inspect", "block", "sections", "categories"],
});
```

`tools` is a filter, never a grant: a mode whose capability is missing stays
hidden however it is listed, and any refused mode falls back to `view`.

## Callbacks

| Callback | Signature |
|---|---|
| `onReady` | `() => void` |
| `onTallies` | `(tallies: SeatManagerTallies) => void` |
| `onActivity` | `(activity: SeatManagerActivity) => void` |
| `onControlRoom` | `(snapshot: ControlRoomSnapshot) => void` |
| `onTokenRefresh` | `() => Promise<{token: string; expiresAt: number}>` |
| `onModeChange` | `(mode: SeatManagerMode) => void` |
| `onFollowLiveChange` | `(enabled: boolean) => void` |
| `onSelectionChange` | `(seats: ExpandedSeat[]) => void` |
| `onObjectSelected` / `onObjectDeselected` | `(object: ExpandedSeat) => void` |
| `onSelectionValidityChange` | `(state: SeatManagerSelectionValidity) => void` |
| `onSelectionValid` / `onSelectionInvalid` | `(state: SeatManagerSelectionValidity) => void` |
| `onSelectionLimit` | `(max: number) => void` |
| `onFilteredSectionChange` | `(sections: SeatManagerFilteredSection[]) => void` |
| `onActionComplete` | `(result: SeatManagerActionResult) => void` |
| `onError` | `(error: unknown) => void` |

## Methods

| Method | Result |
|---|---|
| `render()` | Load, subscribe, and return `Promise<this>` |
| `setMode(mode)` | Switch operator tool |
| `setHeatOverlay(enabled)` | Toggle velocity outline |
| `setFollowLive(enabled)` | Toggle camera following |
| `setTrendWindow(minutes)` | Refresh and return control-room snapshot |
| `enterFullscreen()` / `exitFullscreen()` | Native fullscreen |
| `isFullscreen()` | Current fullscreen state |
| `setToken(token, expiresAt?)` | Rotate credential without remount |
| `block(labels?, {releaseAt?, reason?})` | Block labels or current selection |
| `unblock(labels?)` | Unblock labels or selection |
| `unblockAll()` | Unblock all event inventory |
| `cancelBooking(labels, bookingRef)` | Booking-reference-safe cancellation |
| `setCategory(categoryKey, labels?)` | Assign an event-only category to labels or the selection |
| `setTableBooking(tableIds, mode, bounds?)` | Change free event tables between per-chair, whole-table, or variable occupancy |
| `selectAll()` | Select all blockable seats |
| `selectSection(sectionId)` | Select section |
| `selectByLabels(labels)` | Select exact labels |
| `selectObjects(labels)` / `deselectObjects(labels)` | Custom selection by public label |
| `selectCategories(keys)` / `deselectCategories(keys)` | Custom selection by chart category |
| `setSelectableObjects(labels)` | Replace unavailable-object exceptions |
| `setUnavailableObjectsSelectable(enabled)` | Include or exclude unavailable inventory |
| `setObjectSelectable(predicate)` | Replace the final host selection policy |
| `setMaxSelectedObjects(max?)` | Change or clear the custom selection cap |
| `setNumberOfPlacesToSelect(required?)` | Change or clear exact-count selection |
| `getSelectionValidity()` | Exact-count state, or `null` when no count is required |
| `setFilteredSection(label)` | Filter and frame every section with this public label |
| `clearFilteredSection()` | Clear filtering and restore the full event frame |
| `getFilteredSections()` | Read all currently matched sections |
| `clearSelection()` / `getSelection()` | Manage block selection |
| `getReport()` | Private event report |
| `getControlRoomSnapshot(minutes?)` | Booked value, velocity, presence, activity |
| `getLog({limit?, before?})` | Paginated audit log |
| `setHoldTtl(msOrNull)` | Set or clear event checkout window |
| `zoomToFit()` | Fit chart |
| `destroy()` | Close socket, timers, and mounted UI |

## Event-only category and table controls

`setCategory()` changes the event snapshot and current free inventory pricing;
it does not edit the reusable chart. Existing hold and booking line-item price
snapshots remain immutable.

`setTableBooking()` changes inventory identity, so SeatManager reloads the
authoritative event chart after success. The server refuses the operation when
any affected table unit is held, booked, or blocked, or when changing labels
would drop a private-channel assignment. Variable occupancy bounds must fit the
physical table capacity. These controls require inventory model 2.

`boxBook()` exists as a compatibility stub in current package declarations. Do
not call it. Use the server-side
[`box-book` route](/server-api/cancellations-and-box-office).

## Tallies

```ts
interface SeatManagerTallies {
  free: number;
  held: number;
  booked: number;
  blocked: number;
  total: number;
  capacityPct: number;
  sellThroughPct: number;
  grossRevenue: number;
  revenueStatus: "loading" | "current" | "stale";
  currency: string;
}
```

`capacityPct` is booked divided by total capacity.
`sellThroughPct` is booked divided by capacity excluding blocked inventory.
`grossRevenue` and `revenue` are legacy SDK field names. They come from
authenticated configured-price snapshots captured with booked inventory and
should be labelled **booked value** in a Platform product. They are never
reconstructed from the current list price, but they also do not prove the
amount charged, settled, or refunded in your commerce system.

## Control-room snapshot

```ts
interface ControlRoomSnapshot {
  version: number;
  currency: string;
  totals: {
    free: number;
    held: number;
    booked: number;
    blocked: number;
  };
  revenue: {
    gross: number;
    bySection: ControlRoomSectionMetric[];
  };
  velocity: {
    windowMinutes: number;
    bySection: Array<{
      sectionId: string;
      netBooked: number;
      grossRevenue: number;
      previousNetBooked: number;
      trend: "rising" | "steady" | "cooling";
    }>;
  };
  presence: {
    shoppingSessions: number;
    activeHolds: number;
  };
  activity?: ControlRoomActivityEntry[];
  event: {
    key: string;
    name: string;
    seatTotal: number;
    currency?: string;
  };
}
```

Section metrics include section and zone identity plus total, free, held,
booked, not-for-sale, and configured booked-value snapshots.

## Action results

```ts
interface SeatManagerActionResult {
  action:
    | "block"
    | "unblock"
    | "unblockAll"
    | "cancelBooking"
    | "setHoldTtl"
    | "setCategory"
    | "setTableBooking";
  labels: string[];
  count: number;
}
```

Errors from permission, conflict, expiry, and transport failures reach
`onError` and reject the relevant promise. Do not infer success from an
optimistic repaint.

## ManageApi

`ManageApi` is the lower-level transport for custom operator interfaces:

```ts
import { ManageApi } from "@seatlayer/js/manager";

const api = new ManageApi("https://api.seatlayer.io", token);
const report = await api.report(eventKey);
const rules = await api.availability(eventKey);
```

It provides `chart`, `objects`, `socketUrl`, `block`, `unblock`, `unblockAll`,
`unbook`, `setCategory`, `setTableBooking`, `setHoldTtl`, `availability`, `setAvailability`, `report`,
`controlRoom`, `log`, and `reportCsv`. `reportCsv()` returns a `Blob` because a
Bearer token cannot be attached to a plain download link.

## Lifecycle

Call `render()` before imperative methods. Call `destroy()` on route changes,
component unmount, logout, and event switching. Reuse `setToken()` for rotation
rather than reconstructing the board.

See the [control-room guide](/platform/embedded-control-room) and
[manage-session security model](/platform/embed-sessions).