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 true WebSocket gateway carries the agent control plane (node traffic), not browser clients.
Channels
Section titled “Channels”| Channel | Transport | Endpoint |
|---|---|---|
| Console output | SSE | GET /api/servers/:serverId/console/stream (25 s heartbeats) |
| Console input | REST | POST /api/servers/:serverId/console/command |
| Server events | SSE | GET /api/servers/:serverId/events (state, backups, alerts) |
| Metrics stream | SSE/WebSocket metrics endpoints | CPU/memory/disk ticks |
| Node updates | Panel WebSocket gateway | Node health, agent update progress |
Authenticate stream requests the same way as REST (session or API key). Unauthenticated or unauthorized subscribers are rejected before any data flows.
Message shape
Section titled “Message shape”Frames are JSON with a type discriminator (console, node_updated,
resource_stats, error, …). Always switch on type and ignore unknown
types — new event types are added over time and must not break your client.
Client rules
Section titled “Client rules”- Reconnect with backoff and resume from the last seen marker where the
channel offers one (
resume_consoleand friends). - Handle
errorframes (auth_failed,auth_lockoutwithretryAfterSeconds, connection limits) instead of reconnect-spinning. - One connection per view, not per widget — fan out locally.
- 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', ({ data }) => { appendOutput(JSON.parse(data).line);});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' }),});Exact paths, parameters, and event types are pinned in the API reference — treat this page as concepts, the reference as contract.
