Real-time Subscriptions
Each document has its own WebSocket endpoint. A client opens the socket, picks JSONPaths to watch, and receives a frame for every matching change. Everything on this page - the handshake, each frame type, and the change delivery - was captured from a live server.
The endpoint
WS /ws/live-json/{document_id}Mounted at the server root - not under /api/v1. Authenticate with
the usual Authorization: Bearer header, or with a ?token= query
parameter for browser clients that cannot set headers. A missing or bad
token fails the upgrade with 401; a token without the live-json scope
fails with 403.
Watching from the CLI
watch opens the socket, subscribes to a path, and streams every frame
until interrupted. Captured live while a patch ran in a second shell:
snug live-json watch -d doc_vgDjAtvHYhar -p '$.count'Watching 'doc_vgDjAtvHYhar' path '$.count' (press CTRL+C to stop).
{"type":"connection_established","connection_id":"GYKMymuvcVVS","document_id":"doc_vgDjAtvHYhar"}
{"type":"subscribed","subscription_id":"sub_tWmYNJsFFNNr","path":"$.count","initial_value":6}
{"type":"live_json_change","data":{"subscription_id":"sub_tWmYNJsFFNNr","document":"doc_vgDjAtvHYhar","path":"$.count","operation":"replace","value":7,"old_value":6,"timestamp":"2026-08-28T04:35:22.083515Z"}}The subscribed acknowledgement carries initial_value - the current
value at the path - so a client renders immediately without a separate
read.
The socket protocol
Messages are JSON text frames tagged with type. Client to server:
{"type": "subscribe", "path": "$.count"}
{"type": "unsubscribe", "subscription_id": "sub_tWmYNJsFFNNr"}
{"type": "ping"}Server to client, all reproduced live:
connection_established- first frame after the upgrade; carries theconnection_idused by the HTTP subscribe API below.subscribed- acknowledges asubscribe, withsubscription_id,path, andinitial_value.unsubscribed- acknowledges anunsubscribe.live_json_change- a change at a subscribed path (shape above).pong- answersping.error- a client message the server could not process.
One socket watches one document (fixed by the URL) but can hold many
path subscriptions. Use $ as the path to watch the whole document.
Change frames report the new value at the path
The frame carries the resulting value at the subscribed path, not a
diff. An append patch to a watched array arrives as
"operation": "replace" with the entire new array - captured live:
{"type":"live_json_change","data":{"subscription_id":"sub_nbjaXTVGSFGp","document":"doc_vgDjAtvHYhar","path":"$.items","operation":"replace","value":["a","b","c"],"old_value":["a","b"],"timestamp":"2026-08-28T04:36:19.486987Z"}}The HTTP subscribe API is socket-gated
POST /api/v1/live-json/subscribe registers a path watch on an
already open socket - it is an alternative to sending subscribe as
a socket frame, useful when a separate process manages subscriptions.
Pass the connection_id from the handshake:
snug live-json subscribe -d doc_vgDjAtvHYhar -p '$.items' -c VUJtsQpmacbf
snug live-json subscriptions -d doc_vgDjAtvHYhar
snug live-json unsubscribe -s sub_nbjaXTVGSFGpVerified live: the subscribe response echoes subscription_id,
initial_value, and connection_id, and subsequent changes arrive on
the socket exactly as with a socket-frame subscribe. Without a live
socket it refuses:
- No
-cat all:400"connection_id is required: open WS /ws/live-json/{document} and subscribe with its connection_id". - A
connection_idthat is not an open socket:400"connection_id is not an open live-json socket".
Subscriptions die with their socket - after the connection closes,
unsubscribe for its subscription returns 404 subscription_not_found.