Live streams
The Plexus Data API exposes three device-scoped WebSocket endpoints. Each stream is scoped to a single device and requires an API key — there is no org-wide firehose on this layer.
| Stream | Endpoint |
|---|---|
| Metrics | wss://api.plexus.company/v1/sources/{source_id}/metrics/stream |
| Logs | wss://api.plexus.company/v1/sources/{source_id}/logs/stream |
| Video | wss://api.plexus.company/v1/sources/{source_id}/video/stream |
Handshake
The auth flow is identical for all three streams:
- Open the WebSocket.
- Send
{"type": "auth", "api_key": "plx_..."}as the first frame (within 10 s). - The server starts streaming frames for the requested device.
If the auth frame is missing, malformed, or the key is invalid, the server closes the socket immediately with an error code (see close codes).
Auth frame
{ "type": "auth", "api_key": "plx_..." }For metrics and logs, frames start arriving immediately after auth. For video, the server sends an init frame first (see Video stream).
Metrics stream
wss://api.plexus.company/v1/sources/{source_id}/metrics/streamStreams live telemetry frames for one device. Frames are pushed as the gateway
flushes batches — there is no pull/ready ack on this path.
Query parameters
| Parameter | Required | Description |
|---|---|---|
metrics | No | Comma-separated metric names to filter by. Omit to receive all metrics for the device. |
Received frames
telemetry — one or more data points:
{
"type": "telemetry",
"points": [
{
"source_id": "robot-01",
"metric": "battery.percent",
"value": 86.5,
"timestamp": 1746969660000,
"class": "metric",
"alert": 0
}
]
}gateway_reconnecting — sent when the server loses its upstream gateway connection
and is retrying. The client session is preserved — no reconnect needed on your end.
{ "type": "gateway_reconnecting", "attempt": 1, "delay_s": 2 }Logs stream
wss://api.plexus.company/v1/sources/{source_id}/logs/streamStreams live event frames for one device. No query parameters.
Received frames
event — a single log/event point:
{
"type": "event",
"source_id": "robot-01",
"metric": "nav.goal_reached",
"value": "nav.goal_reached",
"timestamp": 1746969660000,
"class": "event"
}gateway_reconnecting — same as metrics (see above).
Video stream
wss://api.plexus.company/v1/sources/{source_id}/video/streamStreams JPEG video frames for one or more cameras on a device. The stream has an optional wall-clock timeout; the server closes the socket when it expires.
Query parameters
| Parameter | Required | Description |
|---|---|---|
camera_id | No | Camera ID(s) to subscribe to. Repeat the parameter to request multiple cameras (e.g. ?camera_id=front&camera_id=rear). Defaults to default. |
timeout | No | Maximum stream duration in seconds. Server default applies when omitted. |
Received frames
init — sent once after auth, lists the device’s online cameras:
{
"type": "init",
"online_sources": [
{
"source_id": "robot-01",
"cameras": ["front", "rear"]
}
]
}video_frame — one JPEG frame per camera per interval:
{
"type": "video_frame",
"source_id": "robot-01",
"camera_id": "front",
"frame": "<base64 JPEG>",
"width": 640,
"height": 480,
"timestamp": 1746969660000
}gateway_reconnecting — same intent as metrics, but includes max_attempts:
{ "type": "gateway_reconnecting", "attempt": 1, "max_attempts": 5, "delay_s": 2 }Rendering frames in a browser:
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
if (msg.type === "video_frame") {
const img = new Image();
img.onload = () => ctx.drawImage(img, 0, 0);
img.src = `data:image/jpeg;base64,${msg.frame}`;
}
};Video is a live relay only — no persistence, no replay. If your client can’t keep up, frames are dropped silently on the gateway side.
Close codes
Codes are stream-specific:
| Stream | Code | Meaning |
|---|---|---|
| Metrics | 4401 | Unauthorized — API key missing, invalid, or first-frame timeout |
| Metrics | 4402 | Payment required — your plan doesn’t include API access |
| Logs | 4401 | Unauthorized — API key missing, invalid, or first-frame timeout |
| Video | 4001 | Unauthorized — API key missing, invalid, or first-frame timeout |
| Video | 4002 | Payment required — your plan doesn’t include API access |
| Video | 4008 | Stream timeout expired |
| All | 1011 | Internal server error — sustained gateway failure |
Reference client
A minimal TypeScript example for the metrics stream:
const ws = new WebSocket(
"wss://api.plexus.company/v1/sources/robot-01/metrics/stream?metrics=battery.percent,cpu.percent"
);
ws.onopen = () => {
ws.send(JSON.stringify({ type: "auth", api_key: "plx_..." }));
};
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
if (msg.type === "telemetry") {
for (const pt of msg.points) {
console.log(pt.metric, pt.value, pt.timestamp);
}
}
};
ws.onclose = ({ code, reason }) => {
console.warn("closed", code, reason);
// reconnect after a delay, mint a fresh API key if needed
};Gotchas
- Auth must be the first frame. The server closes the socket with
4401/4001if it doesn’t receive a valid auth frame within 10 seconds. - Each stream is per-device. To watch multiple devices simultaneously, open one socket per device.
gateway_reconnectingis informational. The server handles upstream reconnects transparently — don’t close the socket when you receive this frame.- Video timeout closes the socket. The server closes with
4008when the timeout expires. Reconnect if you need to continue. - Frames are pushed — no
readyack. Unlike the direct gateway protocol, these endpoints do not use a pull model.