Skip to content
Docs menu · Realtime
Reference / Realtime

Realtime

Everything in CatzPrint is live. The console, apps and stations each keep one WebSocket open; large lists load over HTTPS and then stay current through events.

Who uses it

ClientConnects toAuthenticates with
Console and signed-in appswss://…/v1/realtimeA ticket from POST /v1/realtime/ticket, or Authorization: Bearer <session token> on the upgrade
Stations (desktop and phone)wss://…/v1/device/realtimeThe station's device token from pairing
IntegrationsServer-to-server integrations using API keys don't need the socket: send jobs with an SDK and receive webhooks for what happens next.

Connecting

A ticket lasts one minute and is tied to your session, so the session cookie never has to cross a WebSocket proxy. Subscribe to each organization you show; you always receive your own personal events, such as your jobs and notifications.

Browser
// 1. Signed in? Ask for a short-lived ticket (the session cookie or token authenticates this).
const { ticket } = await (await fetch("/v1/realtime/ticket", { method: "POST" })).json();

// 2. Open the socket with it.
const ws = new WebSocket(`wss://api-v1.catzprint.com/v1/realtime?ticket=${ticket}`);

ws.onopen = () => ws.send(JSON.stringify({ type: "subscribe", org: orgId }));

ws.onmessage = (e) => {
  const msg = JSON.parse(e.data);
  if (msg.type === "event" && msg.event === "resync") refetchEverything();
  else if (msg.type === "event" && msg.event.startsWith("job.")) updateJob(msg.data); // { id, status }
};

// Any operation can be called over the socket by name:
ws.send(JSON.stringify({ type: "call", id: "7", op: "jobs.list", path: { org: orgId }, query: { limit: "50" }, body: {} }));
// → { "type": "result", "id": "7", "data": { "items": [ … ], … } }

Messages

Every message is a JSON object with a type. Calls carry your own id, and the matching result or error echoes it back. Send a ping every 25–30 seconds to keep the connection open through proxies.

Protocol
// client → server
{"type":"call","id":"7","op":"jobs.list","path":{"org":"<id>"},"query":{"limit":"50"},"body":{}}
{"type":"subscribe","org":"<org id>"}
{"type":"unsubscribe","org":"<org id>"}
{"type":"ping"}

// server → client
{"type":"hello","user":{…},"session_id":"…"}
{"type":"result","id":"7","data":{…}}
{"type":"error","id":"7","error":{"code":"…","message":"…"}}
{"type":"subscribed","org":"…"}
{"type":"event","topic":"org:…","event":"job.updated","data":{"id":"…","status":"printed"}}
{"type":"pong"}

Events

TopicEvents
org:<id>org.updated, team.updated, individuals.updated, location.updated, station.updated { id, connected? }, printer.updated, routing.updated, billing.updated, job.updated / job.printed / job.failed { id, status }
user:<id>job.updated, notification.created
stationconfig.updated, job.cancelled, printers.updated, station.revoked; stations also receive job and update messages

Events are small: they say what changed. Fetch the full object if you need more than the status.

Staying in sync

  • Load lists over HTTPS first, then apply events as they arrive.
  • An event named resync on topic * means the server may have missed events for you — refetch whatever you're showing.
  • If the socket drops, reconnect with a fresh ticket, back off between attempts, and refetch after reconnecting.