Realtime Events
Polling the REST API for live state is wasteful. For anything realtime — console output, power transitions, metric ticks — subscribe to a stream.
Browser-facing realtime is Server-Sent Events (SSE). The panel’s WebSocket
gateway at /ws carries the node-agent control plane and plugin clients, not the
console or metrics views.
Channels
Section titled “Channels”| Channel | Transport | Endpoint |
|---|---|---|
| Console output | SSE | GET /api/servers/:serverId/console/stream |
| Console input | REST | POST /api/servers/:serverId/console/command |
| Server events (state, backups, tasks, alerts) | SSE | GET /api/servers/:serverId/events |
| All your servers | SSE | GET /api/servers/all-servers/events |
| Server metrics | SSE | GET /api/servers/:serverId/metrics/stream |
| Admin entity events | SSE | GET /api/admin/events |
| Node agents and plugins | WebSocket | GET /ws |
Every stream sends a : heartbeat comment and a named ping event
({ "t": <epoch ms> }) every 25 s, so proxies keep the connection open and your
client can detect a half-open one.
Authentication
Section titled “Authentication”- Console stream, console command, and metrics stream accept a session
cookie or an API key (
Authorization: Bearer) — the same auth as REST. The console requiresconsole.readand the metrics streamserver.readon that server; the owner, a subuser with the permission, and admins all qualify. An API key must also hold the permission itself, not just its owner. - Server events (
/events,/all-servers/events) and admin events require a session cookie; an API key is not accepted there. - Admin events additionally requires
admin.read.
Unauthenticated or unauthorized subscribers get an HTTP status before any data
flows (401, 403, 404, or 503 below).
Access limits
Section titled “Access limits”Long-lived streams are capped per backend process. Over the cap the connection is
refused with 503:
| Code | Cap |
|---|---|
CONSOLE_VIEWER_LIMIT_REACHED |
50 concurrent console viewers per server |
SSE_SUBSCRIBER_LIMIT_REACHED |
100 concurrent event subscribers per server |
SSE_SUBSCRIBER_LIMIT_REACHED |
200 concurrent subscribers on /api/servers/all-servers/events, which shares its registry with the metrics streams |
SSE_SUBSCRIBER_LIMIT_REACHED |
150 concurrent subscribers on /api/admin/events |
Console events
Section titled “Console events”| Event | Payload |
|---|---|
connected |
{ serverId, timestamp } |
console_output |
{ serverId, stream, data, timestamp, logId } — stream is stdout/stderr; logId is the persisted log row id |
eula_required |
{ serverId, serverUuid, eulaText, timestamp } |
ping |
{ t } — epoch milliseconds |
error |
{ serverId?, error } |
The metrics stream sends connected, then a resource_stats event on every
agent tick (plus one immediately on subscribe).
Server and admin events
Section titled “Server and admin events”The SSE event name (what addEventListener receives) is the type
discriminator; most payloads also carry a type field. Switch on the event name
and ignore unknown names — new event types are added over time and must not break
your client.
Server event names:
server_state_update— power/state transitionsresource_stats— CPU, memory, disk, network tickbackup_started,backup_complete,backup_restore_started,backup_restore_complete,backup_restore_failed,backup_delete_started,backup_delete_complete,backup_updated,backup_deletedtask_progress,task_complete,task_created,task_updated,task_deletedserver_operation_progress— dense install/transfer/clone progressserver_files_changed,storage_resize_complete,eula_required,alertserver_created,server_updated,server_deleted,server_suspended,server_unsuspended,clone_faileddatabase_created,database_deleted,database_password_rotatedmod_install_complete,mod_uninstall_complete,mod_update_completeand the matchingplugin_*eventspermissions_updated— the caller’s permission set changedresync— the panel lost events (bus reconnect); re-fetch authoritative state
The admin stream (/api/admin/events) carries entity-level create/update/delete
events (user_*, node_*, template_*, alert_*, role_*, api_key_*,
location_*, nest_*, task_*, database_*, network_*, allocation_*,
agent_update_*, node_metrics_updated, audit_log_created,
auth_lockout_created/auth_lockout_cleared, panel_update_*, …).
Client rules
Section titled “Client rules”- Let EventSource reconnect. There is no server-side resume cursor: treat every reconnect as fresh and re-fetch authoritative state over REST.
- Handle the HTTP failures (
401,403,503) instead of reconnect-spinning; a refused stream will not recover without a change on your side. - One connection per view, then fan out locally. The panel’s own SSE hub
already shares one
EventSourceper URL across all widgets. - Fall back to REST polling only when streams are unavailable, and say so in your UI.
Example (console)
Section titled “Example (console)”const src = new EventSource( `/api/servers/${serverId}/console/stream`, { withCredentials: true },);src.addEventListener('console_output', ({ data }) => { appendOutput(JSON.parse(data).data);});src.addEventListener('eula_required', ({ data }) => { showEula(JSON.parse(data).eulaText);});src.addEventListener('error', handleStreamError);
// Sending input is a normal POST, not a socket frame:await fetch(`/api/servers/${serverId}/console/command`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${key}` }, body: JSON.stringify({ command: 'say Hello' }),});The command body is { "command": "..." } (1–4096 characters) and requires
console.write.
The WebSocket gateway
Section titled “The WebSocket gateway”GET /ws is the bidirectional channel:
- Nodes connect as
/ws?nodeId=<id>and authenticate with the node’s agent API key in thenode_handshakemessage; a socket that has not authenticated within 10 s is closed. - Plugin/browser clients connect to the same path and authenticate with the
session cookie on the upgrade, or with a
client_handshakemessage carrying a session token within the first 3 s. Plugin traffic is addressed by message typeplugin:{pluginName}:{eventType}with adatapayload.
Related
Section titled “Related”- API reference — the REST endpoints the streams pair with
- Rate limits — why SSE streams bypass the global bucket
- Errors —
401,403, and503handling
