Skip to main content

WebSocket

The WebSocket endpoint is wss://api.lumio.vision/v1/ws. It uses a custom JSON protocol, not graphql-ws.

Server-side clients can authenticate API keys with the upgrade request header:

Authorization: Bearer lm_svc_your_key

Browser clients cannot set upgrade headers. API keys are not accepted in ?token=, so browser clients must mint a WebSocket token first with POST /v1/auth/ws-token or GraphQL issueWsToken, then connect with wss://api.lumio.vision/v1/ws?token=<ws_token>.

Invalid, expired, or revoked API keys resolve as anonymous. The upgrade can still return 101 Switching Protocols; the first guarded subscribe returns an error frame such as {"type":"error","code":"UNAUTHORIZED","message":"Not authorized to subscribe to events:..."}.

Frames​

Client frames use a type discriminator:

Client frameJSON shape
Subscribe{"type":"subscribe","channel":"events:<account_id>"}
Unsubscribe{"type":"unsubscribe","channel":"events:<account_id>"}
Ping{"type":"ping"}

Server frames also use type:

Server frameJSON shape
Welcome{"type":"welcome","session_id":"...","server_version":"..."}
Pong{"type":"pong"}
Subscribed{"type":"subscribed","channel":"events:<account_id>"}
Unsubscribed{"type":"unsubscribed","channel":"events:<account_id>"}
Event{"type":"event","channel":"events:<account_id>","event_type":"bootstrap","data":{}}
Error{"type":"error","code":"UNAUTHORIZED","message":"..."}

The socket is read-only for clients: there is no publish frame. Every channel is published to by Lumio itself. Any other frame type, including broadcast, gets an INVALID_FORMAT error frame and has no effect. To emit an event, use the REST/GraphQL mutation for it, for example POST /v1/events/emit.

Limits And Heartbeat​

LimitValue
Client heartbeat intervalSend a ping at least every 5 seconds
Idle timeout30 seconds without inbound activity
Maximum inbound text message65,536 bytes
Maximum subscribed channels per session100

The server replies to client ping frames with pong. It does not send application-level pings; liveness depends on inbound client traffic. The same 5-second heartbeat tick also checks token expiry, and every 30 seconds it re-validates the credential the session was opened with (any token kind) against the database.

Channel Gates​

ChannelSubscribe gateRequired scopeFeature gate
overlay:<key>Overlay token for that overlayNonefeature:overlays
events:<account_id>Account access plus permissionevents:readfeature:events
spotify:<account_id>Account access plus permissionspotify:readfeature:music
chat:<account_id>Account access plus permissionchat:readfeature:multichat
automations:<account_id>Account access plus permissionautomations:readfeature:automation
ext-storage:<install_id>The install's extension token, access to the install's owning account plus permission, or an overlay/widget token that renders the installextension-store:readfeature:extensions
login-assignments:<id>PublicNoneNone
ideas:<id>Public when enabledNonesystem:ideas_hub; anonymous also requires system:ideas_hub_public
widget:<widget_instance_id>Widget token for that widgetNonefeature:widgets
presence:<resource_channel>Account that owns the wrapped resourceNoneNone
ext-logs:<extension_id>Extension owner gate, currently fail-closedNonefeature:extension_development
ext-install-logs:<install_id>Access to the install's owning account plus its category permissionbot-modules:read (bot module) or automations:read (automation node)feature:bot_module_extensions
sounds:<account_id>Account accessNonefeature:sounds
history:<account_id>Account access plus permissionhistory:readfeature:stream_history
public-stats:<platform>:<broadcast_id>Public when enabledNonesystem:public_stats

API keys can use the account-scoped channels whose gates are based on account access and scopes. API keys cannot use overlay, widget, or ext-logs channels by construction. ext-storage is the SDK storage-sync channel for extension installs, not a general account event stream: its id is an install id, and a user API key or service key passes only for an install of its own account. presence and login-assignments have no WebSocket feature gate.

Error Codes​

CodeWhen it appears
UNAUTHORIZEDSubscribe failed the channel gate.
FEATURE_DISABLEDThe channel's feature flag is disabled.
INVALID_FORMATThe frame is not valid JSON or is not a known client frame (subscribe, unsubscribe, ping).
MESSAGE_TOO_LARGEThe inbound text frame exceeds 65,536 bytes.
TOKEN_EXPIREDA JWT or shared overlay token expires during the session.
TOKEN_REVOKEDThe session's credential failed its 30-second re-validation: an API key, service key, developer key, popout, overlay, shared overlay, extension or widget token was revoked, rotated, deleted or expired, or its bound member left the account.
AUTH_UNAVAILABLEThe credential could not be re-validated twice in a row (database timeout or error); the session is closed rather than kept open unchecked. Reconnect with backoff.
CHANNEL_DROPPEDA channel forwarder dropped because the session could not keep up.
RATE_LIMITEDA subscribe or unsubscribe frame exceeded the per-session limit: 120 back-to-back, then 5 per second. The frame is dropped and the session stays open; retry after the delay in the message.