Realtime BeatKhana: match coordination, overlays, live co-editing, custom overlay packets and donations. Use REST (Swagger · Scalar) for durable reads and changes; use these sockets for anything that has to update live.
Productionwss://wss.beatkhana.com
Stagingwss://wss.staging.beatkhana.com
Localws://localhost:48522
ProtocolSocket.IO 4 (WebSocket transport)
PayloadsMessagePack in, MessagePack events out
Authauthenticate packet with a bearer token
Quick start
The official TypeScript client (bk-client) handles encoding, acknowledgements and reconnects:
bk-client
import { BKClient } from "bk-client";
const bk = new BKClient({ wsUrl: "https://wss.beatkhana.com" }).setAuthToken(accessToken);
bk.socket.connect({ authenticate: true });
// Follow a match (works signed out too) and react to every change.
const stop = bk.socket.matches.observe({ matchGuid }, {
// Full state, newest revision only; update carries the notice ("scoresConfirmed", "replay", ...).
onDetail: (detail, update) => { render(detail); if (update?.notice) toast(update.notice.kind); },
onRealtime: (frame) => drawLiveScores(frame), // staff and overlays only
});
Any Socket.IO 4 client works. This is the whole protocol with socket.io-client and @msgpack/msgpack:
socket.io-client
import { io } from "socket.io-client";
import { encode, decode } from "@msgpack/msgpack";
const socket = io("https://wss.beatkhana.com", { transports: ["websocket"] });
const send = (event, payload = {}) => socket.timeout(15000).emitWithAck(event, encode(payload));
const on = (event, handler) => socket.on(event, (bytes) => handler(decode(bytes)));
socket.on("connect", async () => {
// 1. Authenticate on every (re)connect. Note the "Bearer " prefix.
const auth = await send("authenticate", { token: `Bearer ${accessToken}` });
if (!auth.success) throw new Error(auth.error);
// 2. Subscribe; the acknowledgement contains the current state.
const sub = await send("match:subscribe", { matchGuid });
if (sub.type !== "success") throw new Error(sub.error);
render(sub.detail);
});
// 3. Apply live updates, dropping anything older than what you have.
on("match:update", ({ detail }) => {
if (detail.revision > current.revision) render(detail);
});
How packets work
Commands are emits with an acknowledgement. Emit the packet name with one payload and a callback (emitWithAck). The server answers through the callback exactly once.
Encode the payload with MessagePack. Send encode(payload). Plain objects are accepted too, but MessagePack keeps binary-safe parity with the official clients.
Read the acknowledgement as a plain object. Success has type: "success" plus packet-specific fields; failure is { type: "fail", error }. The only exception is authenticate, which answers { success, userData | error }.
Server events are MessagePack bytes. Decode every server event before use, except connected (plain JSON).
Without a callback, the server answers with a separate <packet>:response event (MessagePack). This exists for old clients; new code should always pass a callback.
Authentication and permissions
Authentication belongs to the connection. Send authenticate with { token: "Bearer <access token>" } after every connect and reconnect. Tokens are the same BeatKhana JWTs the REST API accepts (website sessions or OAuth applications).
Badge
Meaning
handshake
The authenticate packet itself.
public
Anyone, signed in or not (match:subscribe, match:unsubscribe). Signed-in staff get the private view.
ws:read
Authenticated socket whose token has ws:read (subscriptions and reads).
Scopes only say what the application may do. The user's global roles, tournament roles and match assignments still decide each action; every card lists Who may send it. A missing scope fails with { type: "fail", error, requiredScope }.
Rooms and subscriptions
Subscribing joins Socket.IO rooms; server events are sent to rooms. You never join rooms directly. Room membership is lost on disconnect, so subscribe again after reconnecting.
Room
Joined by
Receives
match:<matchGuid>
Full workflow including pending scores and realtime frames.
match:subscribe with staff access; viewing an overlay that shows the match
Coordination is match-scoped. Every match:* command takes { matchGuid, tournamentGuid? }, runs one change in a database transaction, and broadcasts the complete new state as match:update to both match rooms. The acknowledgement carries the same detail (same revision) plus your access.
Revisions.detail.revision grows with every change (and is larger after a server restart). Keep the newest; ignore older details that arrive late.
Panel lock. Coordinator and referee commands only work between the match's actual start (after 2020) and a later actual end. match:start / match:end set those times. Setup commands (flow, TA tournament, Discord role, picks and bans visibility for hosts) work before the start.
Access. Coordinators and referees act on matches they are assigned to, or on any match when they hold the role and nobody is assigned. Head coordinators, organisers, admins and staff can do everything. The match settings can give coordinators referee powers, score entry, match management and stream notifications.
cancelAwaitingScores returns to players_selection; warmups (setWarmupMode) never save scores. Realtime frames are display-only and never become results by themselves.
Errors and reconnecting
Show error to users: messages are written for people (e.g. Complete warmups before assigning Side 1.).
An acknowledgement that never arrives means the connection dropped; Socket.IO's timeout() rejects the promise. Do not blindly resend commands that are not idempotent (submitting scores, picks and bans).
After a reconnect: authenticate, subscribe again, and treat the snapshot in the acknowledgement as the truth. Missed events are not replayed, except collaboration operations (collab:join with resume).
The server keeps live state (realtime frames, collaboration sessions, room membership) in one process. Deployments behind a load balancer need sticky sessions.
Connection
Handshake and authentication.
Packets you send 2
emit
authenticate
handshake
Sign the socket in with a BeatKhana access token.
Send this first, and again after every reconnect: authentication belongs to the connection, not the user. Packets that need ws:read or ws:write fail until it succeeds. Signed-out sockets may still use match:subscribe (public view).
Unlike every other packet, the answer uses success instead of type.
Request payload
Field
Type
Description
tokenrequired
string
Bearer <access token>. The Bearer prefix is required.
Reserved generic subscription packet. It currently performs no subscription.
Answers Subscription type not specified! without subscriptionType, otherwise does nothing. Use the specific packets (match:subscribe, getTournamentOverlay, donation:subscribe...).
Plain JSON (not MessagePack). clientGuid identifies this connection in server logs; it changes on every reconnect.
Roomsthe connecting socket
WhenConnection established.
Payload
Field
Type
Description
clientGuidrequired
uuid
Example payload
{
"clientGuid": "2f0c…"
}
Match coordination
Match rooms and every match:* command used by coordinators, referees and stream hosts.
Packets you send 27
emit
match:subscribe
public
Follow a match. Works signed in or out; the server picks the staff or the public room.
Coordinators, referees, stream hosts and staff of the match join match:<matchGuid> and receive the full workflow (pending scores, realtime frames). Everyone else joins match:<matchGuid>:public. Pass overlayGuid from an overlay that currently shows this match to receive the full detail as an overlay viewer.
The acknowledgement contains the current detail, so you do not need a separate REST call. Subscribe again after reconnecting.
WhoAnyone. Staff access needs an authenticated socket whose token has ws:read.
Joinsmatch:<matchGuid>match:<matchGuid>:public
Request payload
Field
Type
Description
matchGuidrequired
uuid
The match to act on.
tournamentGuid
string
Optional sanity check: tournament GUID or id. Rejected when the match belongs to another tournament.
Start the match: sets its actual start time and unlocks the coordinator and referee panels.
Refused when the match is already live. Starting a concluded match again moves the start past the old end and reopens the panels. Swiss brackets progress automatically.
WhoStream hosts, head coordinators, organisers, admins, staff, global staff (access.canStart).
End the live match: sets its actual end time and locks the panels.
Clears the realtime frame and the TournamentAssistant room (currentRoomGuid); referee panels close that room in TournamentAssistant. Only a live match can be ended.
WhoReferees, stream hosts and staff; coordinators when allowCoordinatorManageMatch is on (access.canEnd).
Give the tournament's Discord Current Match Role to every captain, player and reserve of the match's teams.
Requires a configured Current Match Role (settings.discordMatchRole). The notice reports changed, unchanged, missing and failed players and is sent to the staff room only.
WhoReferees, stream hosts and above (access.canManageDiscordRole). Works before the match starts.
Server-deafen or undeafen the confirmed playing players in the tournament's Discord server.
Targets the confirmed players of teamGuid, or both players of a solo match. Players who are not in the server or not in a voice channel are reported in the notice, which is sent to the staff room only.
The tournament's Discord bot needs the Deafen Members permission.
WhoCoordinator. Only once the playing players are confirmed.
Refused while players are locked (cancel awaiting scores first). In solo matches every solo player plays and playerGuids is ignored. Moves the workflow to players_selection.
Refused while the match panel is locked (before the match starts or after it ends).
Relay a realtime TournamentAssistant score frame to staff and overlays.
Never written to the database: the server keeps only the latest frame per match in memory and sends it as match:realtime to the staff room. The acknowledgement carries detail: null.
Send frames at the rate TournamentAssistant produces them; a new frame replaces the previous one.
Refused while the match panel is locked (before the match starts or after it ends).
Save completed TournamentAssistant scores as drafts so they survive panel refreshes.
Upserts the latest completed score of each player without advancing the workflow. Requires locked players and the awaiting_scores or score_review phase; one score per confirmed playing player, on their correct team. Warmup scores are rejected.
Refused while the match panel is locked (before the match starts or after it ends).
Requires one automatic or manual score for every confirmed playing player. A referee submission is final: scores are saved and the map point goes to the single best team under the scoring rule. A coordinator submission moves the match to score_review until a referee sends match:reviewPendingScores.
Referees may call a replay by setting isCallingReplay: true on every score of exactly one team: the round is saved, that team loses one replay, no point is awarded and the workflow stays in awaiting_scores.
Refused while the match panel is locked (before the match starts or after it ends).
WhoReferees; coordinators when allowCoordinatorSetScore is on (access.canSubmitScores).
Referee decision on scores a coordinator submitted.
confirm saves and awards the point; replay saves the round as a replay for replayTeamGuid; discard drops the submission and returns to awaiting_scores. scoreAdjustments can mark individual scores as not counted first.
Refused while the match panel is locked (before the match starts or after it ends).
Add, correct or remove scores on one map of the pool (the score editing on the match summary).
Existing rows are updated in place so replay rounds keep their order. Banned maps are refused. awardPoint gives the point to the best team among the newly added scores.
Refused while the match panel is locked (before the match starts or after it ends).
WhoReferees while the match is live; referees who can also start the match (hosts, head coordinators, staff) at any time.
Requires a map pool, completed warmups and a selected flow. The server validates the step against the flow (expected step number, acting side, map or flair availability, no map in progress) and serialises actions per match, so two panels cannot fill the same step.
With autoPostPicksBans on, the step is announced in the current match channel using the mapPick, mapBan, tiebreaker, categoryPick, categoryBan and sidesDecided message templates. Clearing maps is not announced.
Refused while the match panel is locked (before the match starts or after it ends).
A team timeout needs teamGuid. The match settings (timeouts) decide whether teams may call one before a picks and bans step, before the replay decision and before the next map; never while players are in a map (see MatchOperationsState.timeoutAvailability).
Coordinators are refused outside those times. Referees and stream hosts are refused too unless they send override: true.
Refused while another timeout is running. Posts in the current match channel when autoPostTimeouts is on.
Refused while the match panel is locked (before the match starts or after it ends).
WhoTechnical: referees, stream hosts and staff. Team: the same, and coordinators when allowCoordinatorTimeoutStart is on.
Sent to both match rooms with the same revision: the staff room gets the full detail, the public room the public view (no pending scores, no realtime frame) and no notice for staff-only actions. Replace your copy when detail.revision is newer than yours; ignore older ones.
Roomsmatch:<matchGuid>match:<matchGuid>:public
WhenAny match:* command (or legacy alias) except publishRealtimeScores; REST match edits that change live state.
Overlay snapshots and which match an overlay shows.
Packets you send 2
emit
getTournamentOverlay
ws:read
Load an overlay snapshot and follow it.
Joins the overlay rooms and the staff room of the overlay's current match. When the overlay is assigned another match the server moves the socket to that match's room automatically.
WhoOverlay operators: organisers, admins, staff, referees, head coordinators, stream hosts, global staff, and coordinators of the overlay's match.
The match's own workflow is untouched, so an overlay can join a match that is already live. Every viewer of the overlay is moved to the new match room.
WhoOrganisers, admins, staff, head coordinators, stream hosts and global staff.
Who is casting a match and who of them is speaking in the casters voice channel.
Packets you send 2
emit
casters:subscribe
public
Follow the casters of a match and who of them is speaking.
Matches with assigned casters (and "List assigned casters" on) show exactly those casters. Otherwise everyone in the tournament's casters channel is shown, except the match's stream hosts, coordinators and players.
The bot joins the casters channel (Match settings) while anyone is in it and only watches who sends audio. Subscribe again after reconnecting or when the overlay switches to another match.
WhoAnyone, signed in or not. The list only contains what overlays put on stream.
The casters of a match, or who of them is speaking, changed. Carries the complete new state.
Sent only when something visible changed. Speaking ends a quarter of a second after the last audio packet, so short pauses between words do not flicker.
Roomscasters:<matchGuid>
WhenSomeone joins, leaves, mutes or starts or stops speaking in the casters channel; the bot joins or leaves; the match's staff assignments or the casters channel setting change.
The server always generates the node GUID and returns it as node.guid. Use that GUID for later updates, deletes, parents and undo; a local temporary id may be used for optimistic rendering but must never be sent as the node identity.
Parent and component references are validated: the parent must be in the same document, and component instances cannot form cycles.
merge deep-merges an object into the current value; replace overwrites it and requires expectedRevision (optimistic concurrency). A preset may supply the value. Placeholders are resolved, then the value is validated against the state's schema.
WhoRoles allowed by the state definition (staff and referees by default; coordinators when allowCoordinatorStreamNotifications is on).
Send a registered custom packet to every viewer of an overlay.
The packet type must be registered on the overlay (REST: overlay custom packets). A preset may supply or be merged with the payload; placeholders are resolved and the result is validated against the packet schema.
Viewers receive customOverlayPacket (the full envelope) and also an event named after the packet type carrying just the payload. Integrations may emit the packet type directly as the event name with the same body; the server routes unknown event names here (needs ws:write).
WhoRoles allowed by the packet definition (staff and referees by default; coordinators when allowCoordinatorStreamNotifications is on).
Operational-transform editing of tournament text, update drafts and match notes.
Packets you send 10
emit
collab:join
ws:write
Join a live editing session and receive every field's text and revision.
Joins the session room before snapshotting, so no operation falls between the snapshot and the subscription. After a reconnect, pass resume: { epoch, revisions } from your previous join: when the server still has the history it returns missedOperations per field instead of forcing a full reload. A different epoch means the session restarted; use the returned documents.
Completed and cancelled tournaments answer with readOnly: true.
revision is the last server revision you have applied for that field. The server transforms your operation against newer history, applies it, acknowledges the new revision and broadcasts collab:operation to everyone else. Resending the same opId is safe (idempotent).
Handle the acknowledgement in the synchronous ack callback, not a promise chain: acks and broadcasts can arrive in one chunk and reordering them corrupts your confirmed text. A failure with resync: true means: leave and join again.
Edits are saved about 0.8 s after typing stops (at most every 5 s while typing) and announced with collab:saved.
Add, replace or remove one structured change card on a draft.
Exactly one of upsert (a change object with a guid) or remove (a change guid). Cards are edited individually so concurrent card edits do not overwrite each other.
Change a form's structure: add, duplicate, retype, move or remove questions and options.
Texts (titles, descriptions, option labels) are live fields edited with collab:edit; this packet changes everything else. New questions and options join the session as live fields and are announced to everyone through collab:state (documents added, removedDocuments removed, event saying what happened).
change.kind is one of addQuestion { type, index?, required?, settings? }, duplicateQuestion { questionGuid }, updateQuestion { questionGuid, type?, required?, settings? }, removeQuestion { questionGuid }, moveQuestion { questionGuid, index }, addOption { questionGuid, index?, label? }, removeOption { questionGuid, optionGuid }, moveOption { questionGuid, optionGuid, index }. Settings are normalized per question type (unknown keys dropped, numbers clamped).
WhoEdit access to the form (participants of collab:form:<formGuid>).
Change a shared sheet: cells, formatting, rows, columns, sizes, frozen panes or tabs.
Rows and columns have stable ids and cells are keyed rowId:colId, so concurrent edits never shift each other. Changes are applied in arrival order (the last write to a cell wins) and broadcast to everyone, including you, as collab:sheet:op with the next revision. Edits that point at rows, columns or tabs someone else removed are skipped. Resending the same opId is safe.
change.kind is one of setCells { tab, cells: [{ row, col, value?, format?, clearFormat? }] }, insertRows|insertCols { tab, index, ids, cells?, sizes? }, deleteRows|deleteCols { tab, ids }, rowOrder { tab, rows } (e.g. after sorting), resize { tab, cols?, rows? }, freeze { tab, rows, cols }, addTab { tab, name, index, rows, cols }, updateTab { tab, name?, color? }, deleteTab { tab }, moveTab { tab, index }. Format patches switch b, i, u, s (strikethrough) and w (wrap) with booleans and set c (text colour), bg (fill), a (left/center/right) and va (top/middle/bottom), or remove them with null.
Limits: 20 tabs, 5,000 rows and 200 columns per tab, 250,000 filled cells, 10,000 characters per cell, 25,000 cells per change. The same model ships in bk-client (applySheetChange).
WhoEdit access to the sheet (participants of collab:sheet:<sheetGuid> who are not read-only).
Another participant's edit (or text saved through REST). Apply it in order.
revision is the field's new revision. Transform it against your unacknowledged local operations before applying, as in bk-client's CollaborativeText. clientId: "server" means the text came from a REST save.
Roomscollab:<scope>:<guid>
Whencollab:edit from another socket; REST saves of the same text.
A sheet change was applied. Apply it in revision order.
change is the normalized change (skipped cells removed, values clamped). It also reaches the sender, which drops its matching optimistic change by opId. A revision gap means you missed changes; join again for a fresh snapshot.
Overlay-scoped names from before coordination moved to matches. Prefer the match:* packets.
Packets you send 26
emit
startSelectedMatch
ws:writealias of match:start
Legacy alias of match:start, acting on the overlay's current match.
Takes { tournamentGuid, overlayGuid, …fields of match:start } instead of matchGuid, and answers with the overlay snapshot (overlay and newOverlay) instead of the match detail. Fails when the overlay has no match.
Legacy alias of match:end, acting on the overlay's current match.
Takes { tournamentGuid, overlayGuid, …fields of match:end } instead of matchGuid, and answers with the overlay snapshot (overlay and newOverlay) instead of the match detail. Fails when the overlay has no match.
Legacy alias of match:setDetails, acting on the overlay's current match.
Takes { tournamentGuid, overlayGuid, …fields of match:setDetails } instead of matchGuid, and answers with the overlay snapshot (overlay and newOverlay) instead of the match detail. Fails when the overlay has no match.
Legacy alias of match:setPlayerVolume, acting on the overlay's current match.
Takes { tournamentGuid, overlayGuid, …fields of match:setPlayerVolume } instead of matchGuid, and answers with the overlay snapshot (overlay and newOverlay) instead of the match detail. Fails when the overlay has no match.
Legacy alias of match:resetPicksBans, acting on the overlay's current match.
Takes { tournamentGuid, overlayGuid, …fields of match:resetPicksBans } instead of matchGuid, and answers with the overlay snapshot (overlay and newOverlay) instead of the match detail. Fails when the overlay has no match.
Legacy alias of match:addPicksBansAction, acting on the overlay's current match.
Takes { tournamentGuid, overlayGuid, …fields of match:addPicksBansAction } instead of matchGuid, and answers with the overlay snapshot (overlay and newOverlay) instead of the match detail. Fails when the overlay has no match.
Legacy alias of match:removePicksBansAction, acting on the overlay's current match.
Takes { tournamentGuid, overlayGuid, …fields of match:removePicksBansAction } instead of matchGuid, and answers with the overlay snapshot (overlay and newOverlay) instead of the match detail. Fails when the overlay has no match.
Legacy alias of match:setSide1, acting on the overlay's current match.
Takes { tournamentGuid, overlayGuid, …fields of match:setSide1 } instead of matchGuid, and answers with the overlay snapshot (overlay and newOverlay) instead of the match detail. Fails when the overlay has no match.
Legacy alias of match:setPicksBansVisibility, acting on the overlay's current match.
Takes { tournamentGuid, overlayGuid, …fields of match:setPicksBansVisibility } instead of matchGuid, and answers with the overlay snapshot (overlay and newOverlay) instead of the match detail. Fails when the overlay has no match.
Legacy alias of match:setPlayingPlayers, acting on the overlay's current match.
Takes { tournamentGuid, overlayGuid, …fields of match:setPlayingPlayers } instead of matchGuid, and answers with the overlay snapshot (overlay and newOverlay) instead of the match detail. Fails when the overlay has no match.
Legacy alias of match:confirmPlayingPlayers, acting on the overlay's current match.
Takes { tournamentGuid, overlayGuid, …fields of match:confirmPlayingPlayers } instead of matchGuid, and answers with the overlay snapshot (overlay and newOverlay) instead of the match detail. Fails when the overlay has no match.
Legacy alias of match:cancelAwaitingScores, acting on the overlay's current match.
Takes { tournamentGuid, overlayGuid, …fields of match:cancelAwaitingScores } instead of matchGuid, and answers with the overlay snapshot (overlay and newOverlay) instead of the match detail. Fails when the overlay has no match.
Legacy alias of match:setWarmupMode, acting on the overlay's current match.
Takes { tournamentGuid, overlayGuid, …fields of match:setWarmupMode } instead of matchGuid, and answers with the overlay snapshot (overlay and newOverlay) instead of the match detail. Fails when the overlay has no match.
Legacy alias of match:setTournamentAssistantRoom, acting on the overlay's current match.
Takes { tournamentGuid, overlayGuid, …fields of match:setTournamentAssistantRoom } instead of matchGuid, and answers with the overlay snapshot (overlay and newOverlay) instead of the match detail. Fails when the overlay has no match.
Legacy alias of match:publishRealtimeScores, acting on the overlay's current match.
Takes { tournamentGuid, overlayGuid, …fields of match:publishRealtimeScores } instead of matchGuid, and answers with the overlay snapshot (overlay and newOverlay) instead of the match detail. Fails when the overlay has no match.
Legacy alias of match:savePendingScoreDrafts, acting on the overlay's current match.
Takes { tournamentGuid, overlayGuid, …fields of match:savePendingScoreDrafts } instead of matchGuid, and answers with the overlay snapshot (overlay and newOverlay) instead of the match detail. Fails when the overlay has no match.
Legacy alias of match:submitPendingScores, acting on the overlay's current match.
Takes { tournamentGuid, overlayGuid, …fields of match:submitPendingScores } instead of matchGuid, and answers with the overlay snapshot (overlay and newOverlay) instead of the match detail. Fails when the overlay has no match.
Legacy alias of match:reviewPendingScores, acting on the overlay's current match.
Takes { tournamentGuid, overlayGuid, …fields of match:reviewPendingScores } instead of matchGuid, and answers with the overlay snapshot (overlay and newOverlay) instead of the match detail. Fails when the overlay has no match.
Legacy alias of match:reset, acting on the overlay's current match.
Takes { tournamentGuid, overlayGuid, …fields of match:reset } instead of matchGuid, and answers with the overlay snapshot (overlay and newOverlay) instead of the match detail. Fails when the overlay has no match.
Legacy alias of match:saveMapScores, acting on the overlay's current match.
Takes { tournamentGuid, overlayGuid, …fields of match:saveMapScores } instead of matchGuid, and answers with the overlay snapshot (overlay and newOverlay) instead of the match detail. Fails when the overlay has no match.
Number of pending scores (also in the public view).
latestRealtimeScores
object
Staff room only: the latest realtime frame, keyed by player. Empty in the public view.
playerPlatformIds
object
Staff room only: BeatKhana user GUID -> the platform ID the player is connected to TournamentAssistant with (TA replay streams use this ID). Kept across score resets. Empty in the public view.
Scoring rule: metric (score, accuracy, misses + bad cuts), aggregation (sum or average) and direction.
taTournamentGuid
uuid | null
Default TournamentAssistant tournament.
gameplay
object
Default modifiers, Disable Fail and Disable Pause.
discordMatchRole
boolean
A Discord Current Match Role is configured.
castersChannelId
string | null
Voice channel in the linked Discord server where casters sit.
updatedAt
string | null
type
MatchCastersState
The casters an overlay shows for a match and who of them is speaking.
Field
Type
Description
matchGuidrequired
uuid
tournamentGuidrequired
uuid
channelIdrequired
string | null
The tournament's casters channel, or null when none is set.
connectedrequired
boolean
The bot is in the casters channel, so speaking is live.
sourcerequired
assigned | channel
assigned: the match's assigned casters. channel: everyone in the casters channel except the match's stream hosts, coordinators and players.
castersrequired
object[]
casters[].keyrequired
string
Stable render key: the BeatKhana user GUID, else the Discord id.
casters[].userGuid
uuid | null
casters[].discordIdrequired
string
casters[].namerequired
string
casters[].avatarUrlrequired
string
casters[].inChannelrequired
boolean
casters[].speakingrequired
boolean
casters[].mutedrequired
boolean
Muted or deafened in Discord.
updatedAt
string
type
MatchLiveDetail
The complete live state of one match.
Field
Type
Description
revisionrequired
integer
Monotonic. Ignore a detail older than the one you hold.
matchGuidrequired
uuid
tournamentGuid
uuid
matchrequired
object
The match record with teams, members, map pool, picks and bans, scores and assignments. The public view omits pending scores and other bracket matches.
Tournament GUID (or, where accepted, its URL id). The server always resolves the tournament from the match or overlay and rejects a mismatch.
overlayGuidrequired
uuid
Overlay GUID.
type
OverlaySnapshot
The overlay with its documents (scenes and components) and nodes, the assigned match, and fields derived from that match: matchOperationsState, assignedPicksBansFlow, taServerGuid, taMatchGuid. Fold later match:update details in with bk-client mergeMatchDetailIntoOverlay.
Free-form object.
type
OverlayNodePatch
Partial node. Only known node columns are stored; see OverlayNodePatch in src/modals/websocket/overlayTypes.ts for every field.
Field
Type
Description
parentNodeGuid
uuid | null
Parent node in the same document.
nodeType
leaf | component_instance
componentKind
string | null
Renderer, e.g. text, image, songCard, scoreDisplay, picksBansGrid, videoPlayer, custom.
sourceDocumentGuid
uuid | null
Component document for a component instance. Cycles are rejected.
name
string
posX
number
posY
number
widthPx
number
heightPx
number
rotationDeg
number
opacity
number
zIndex
integer
isVisible
boolean
props
object
Renderer-specific options.
appearance
object
bindings
object
Data bindings (team, player, map index...).
editorMeta
object
type
TextOperation
Operational transform spanning the whole document: positive integer = retain n, negative integer = delete n, string = insert. Indices are UTF-16 code units.
Array of integer | string.
type
CollaborationScope
Which text to edit together.
One of:
Tournament description and rules. Needs general-settings edit access.
Field
Type
Description
typerequired
"tournament-general"
tournamentGuidrequired
uuid
Update drafts. Needs update edit access.
Field
Type
Description
typerequired
"tournament-updates"
tournamentGuidrequired
uuid
Private match notes. Needs canEditNotes on the match.
Field
Type
Description
typerequired
"match-notes"
tournamentGuidrequired
uuid
matchGuidrequired
uuid
A shared doc (fields title, description, content). Viewers join read-only and see edits live; editors can type.
Field
Type
Description
typerequired
"doc"
docGuidrequired
uuid
A shared form (fields title, description, confirmation, q:<questionGuid>:title, q:<questionGuid>:description, o:<optionGuid>). Needs edit access.
Field
Type
Description
typerequired
"form"
formGuidrequired
uuid
A shared sheet (text fields title, description; cells arrive as sheet in the join response and change through collab:sheet:change). Viewers join read-only.
Field
Type
Description
typerequired
"sheet"
sheetGuidrequired
uuid
type
CollaborationParticipant
Field
Type
Description
clientId
string
The participant's socket id.
user
object
user.guid
uuid
user.name
string
user.avatarUrl
string | null
color
string
Presence colour.
focus
string | null
Field the participant is in.
selection
object | null
selection.field
string
selection.anchor
integer
selection.head
integer
joinedAt
string
type
CollaborationDocument
Field
Type
Description
content
string
revision
integer
Server revision of this field.
maxLength
integer
savedAt
string | null
type
CustomStateSnapshot
A saved overlay state: guid, stateKey, value, revision, updatedAt, updatedByGuid.
Free-form object.
type
CustomPacket
Field
Type
Description
packetType
string
Registered packet type (also the Socket.IO event name overlays listen to).
payload
any
Validated against the packet's schema, after presets and placeholders are applied.
tournamentGuid
uuid
overlayGuid
uuid
sentAt
string
sentByGuid
uuid
Generated from backend-ts/src/docs/socketCatalogue.ts.