WebSocket API
The RosFit WebSocket API provides a persistent, bidirectional connection for real-time telemetry streams, device status updates, and instant command dispatch. It is the backbone of the dashboard's live features and is available for custom integrations.
Connection
Connect to the WebSocket endpoint with a valid authentication token:
| Environment | URL |
|---|---|
| RosFit Cloud | wss://api.rosfit.io/ws |
| Self-hosted (API) | ws://localhost:8000/ws |
| Self-hosted (MQTT over WS) | ws://localhost:8083/mqtt |
The token can be passed as a query parameter or in the first message after connection:
ws://localhost:8000/ws?token=<access_token>
Or authenticate after connecting by sending an auth message:
{
"type": "auth",
"data": {
"token": "eyJhbGciOiJIUzI1NiIs..."
}
}
The server responds with:
{
"type": "auth_result",
"data": {
"status": "authenticated",
"user_id": "usr_a1b2c3",
"role": "admin"
}
}
Message format
All WebSocket messages use a consistent JSON envelope:
{
"type": "<message_type>",
"channel": "<optional_channel>",
"data": { },
"timestamp": "2026-03-30T10:05:32.123Z",
"request_id": "<optional_correlation_id>"
}
| Field | Type | Required | Description |
|---|---|---|---|
type | string | yes | Message type (subscribe, unsubscribe, telemetry, status, command, error) |
channel | string | no | Topic channel (e.g. telemetry:dev_k8m2n4:odom) |
data | object | yes | Message payload |
timestamp | ISO 8601 | no | Server-generated timestamp (present on server messages) |
request_id | string | no | Client-supplied ID for request/response correlation |
Subscribing to telemetry
Subscribe to live telemetry from one or more devices:
const ws = new WebSocket('ws://localhost:8000/ws?token=<access_token>')
ws.onopen = () => {
// Subscribe to specific topics on a device
ws.send(JSON.stringify({
type: 'subscribe',
channel: 'telemetry:dev_k8m2n4',
data: {
topics: ['odom', 'battery_state', 'scan'],
throttle_ms: 100,
},
}))
// Subscribe to all telemetry from all devices
ws.send(JSON.stringify({
type: 'subscribe',
channel: 'telemetry:*',
data: {},
}))
}
ws.onmessage = (event) => {
const msg = JSON.parse(event.data)
if (msg.type === 'telemetry') {
console.log(`[${msg.data.device_id}/${msg.data.topic}]`, msg.data.payload)
}
}
The server pushes telemetry messages as they arrive:
{
"type": "telemetry",
"channel": "telemetry:dev_k8m2n4:odom",
"data": {
"device_id": "dev_k8m2n4",
"topic": "odom",
"payload": {
"position": { "x": 12.4, "y": 3.7, "z": 0.0 },
"orientation": { "x": 0.0, "y": 0.0, "z": 0.38, "w": 0.92 }
}
},
"timestamp": "2026-03-30T10:05:32.123Z"
}
To unsubscribe:
ws.send(JSON.stringify({
type: 'unsubscribe',
channel: 'telemetry:dev_k8m2n4',
}))
Device status changes
Subscribe to connection state changes across the fleet:
ws.send(JSON.stringify({
type: 'subscribe',
channel: 'status:*',
data: {},
}))
Status update messages look like:
{
"type": "status",
"channel": "status:dev_k8m2n4",
"data": {
"device_id": "dev_k8m2n4",
"previous_status": "online",
"status": "offline",
"reason": "heartbeat_timeout",
"last_seen": "2026-03-30T10:04:00Z"
},
"timestamp": "2026-03-30T10:05:00Z"
}
You can filter to specific devices:
ws.send(JSON.stringify({
type: 'subscribe',
channel: 'status:dev_k8m2n4',
data: {},
}))
Sending commands
Send commands to devices and receive real-time status updates through the same WebSocket connection:
// Send a command
ws.send(JSON.stringify({
type: 'command',
data: {
device_id: 'dev_k8m2n4',
command: 'navigate_to',
data: {
goal: {
position: { x: 5.0, y: 3.0, z: 0.0 },
orientation: { x: 0.0, y: 0.0, z: 0.0, w: 1.0 },
},
},
},
request_id: 'req_001',
}))
// Handle responses
ws.onmessage = (event) => {
const msg = JSON.parse(event.data)
if (msg.type === 'command_ack') {
console.log(`Command ${msg.data.command_id}: ${msg.data.status}`)
// status: pending → delivered → acked
}
if (msg.type === 'command_result') {
console.log('Command completed:', msg.data.result)
}
}
Error handling
The server sends error messages for invalid requests or connection issues:
{
"type": "error",
"data": {
"code": "INVALID_CHANNEL",
"message": "Channel 'telemetry:nonexistent' does not match any device",
"request_id": "req_002"
},
"timestamp": "2026-03-30T10:05:33Z"
}
| Error Code | Description |
|---|---|
AUTH_REQUIRED | No authentication token provided |
AUTH_EXPIRED | Token has expired, re-authenticate or refresh |
INVALID_CHANNEL | Requested channel does not exist |
RATE_LIMITED | Too many messages per second (default limit: 100 msg/s) |
DEVICE_OFFLINE | Command target is not connected |
PERMISSION_DENIED | Role does not allow this operation |
Implement reconnection logic with exponential backoff for production use:
function connect() {
const ws = new WebSocket('ws://localhost:8000/ws?token=<access_token>')
let retryDelay = 1000
ws.onopen = () => {
retryDelay = 1000
// Re-subscribe to channels
}
ws.onclose = () => {
setTimeout(() => {
retryDelay = Math.min(retryDelay * 2, 30000)
connect()
}, retryDelay)
}
}