Where you are: docs → reference → api → operator Read this first: api.md See also: subsystems/control-plane.md#operators · api/comms.md
TL;DR Nine endpoints manage multi-operator production: register (optionally via invite token), reconnect with a saved token, heartbeat, list operators with connection flags, acquire/release/force-release per-subsystem locks, delete (self or by director), and list invite tokens (director only). Registered operators get their own bearer tokens and show up in ControlRoomState.Operators with live connection status.
Purpose: register a new operator and return a bearer token. When invite tokens are configured, the inviteToken field is required (unless the caller authenticates with the session API token — then the role is taken from the body).
Auth: none required (this endpoint is in authExemptPaths).
Handler: control/api_operator.go → (*API).handleOperatorRegister.
Request body:
{ "name": "Alice", "role": "director", "id": "optional-stable-id", "inviteToken": "abc123" }Roles: director, audio, graphics, captioner, viewer — see operator.Role.
Response 200: { "id": "...", "name": "...", "role": "...", "token": "..." }. The token is the bearer token the operator uses for subsequent calls.
Errors: 400 (empty name, operator.ErrInvalidRole), 403 (missing / invalid invite token when configured), 409 (operator.ErrDuplicateName).
Purpose: re-establish an operator session using a previously-issued token. Useful for reconnects after a browser refresh.
Auth: none (exempt); the bearer token in the Authorization header is checked inside the handler.
Handler: (*API).handleOperatorReconnect.
Response 200: { "id": "...", "name": "...", "role": "..." }.
Errors: 401 (missing or invalid token).
Purpose: mark the operator's session as alive; prevents the 60-second stale-session cleanup.
Auth: none; bearer token checked in handler.
Handler: (*API).handleOperatorHeartbeat.
Response 200: { "ok": true }.
Errors: 401.
Related: subsystems/control-plane.md#operators
Purpose: list all registered operators with a connected flag indicating whether a session is currently active.
Handler: (*API).handleOperatorList. Response 200: []operator.Info.
Purpose: acquire a subsystem lock. While held, other operators get 409 when commanding that subsystem.
Handler: (*API).handleOperatorLock.
Request body: { "subsystem": "audio" }.
Valid subsystems: switching, audio, graphics, replay, output, captions — see operator.Subsystem.
Response 200: { "ok": true }.
Errors: 400 (operator.ErrInvalidSubsystem, operator.ErrLockNotOwned), 401 (bad token), 403 (operator.ErrNoPermission — role can't lock this subsystem), 404 (operator.ErrSessionNotFound), 409 (operator.ErrSubsystemLocked).
Purpose: release a lock you hold.
Handler: (*API).handleOperatorUnlock.
Errors: 400 (operator.ErrNotLocked, operator.ErrLockNotOwned).
Purpose: director-only — release any operator's lock.
Handler: (*API).handleOperatorForceUnlock.
Errors: 401, 403 (non-director), 400 (operator.ErrNotLocked).
Purpose: delete an operator record. Allowed for self, or for any operator if the requester is a director. If no operators are registered yet (bootstrap case), auth is skipped.
Handler: (*API).handleOperatorDelete.
Response 200: { "ok": true }.
Errors: 400 (empty ID), 401 (invalid token with operators present), 403 (non-self non-director), 404 (operator.ErrNotFound).
Purpose: return the role → invite-token map for building invite links. Director role required.
Handler: (*API).handleOperatorInviteTokens.
Response 200: { "director": "token1", "audio": "token2", ... } or {} when invite tokens aren't configured.
Errors: 401 (missing token), 403 (non-director).
- Reference: api.md · state-broadcast.md · api/comms.md
- Subsystems: control-plane.md