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.
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 |
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:
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.
Tallies
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
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
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:
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 and manage-session security model.