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:

EnvironmentURL
RosFit Cloudwss://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>"
}
FieldTypeRequiredDescription
typestringyesMessage type (subscribe, unsubscribe, telemetry, status, command, error)
channelstringnoTopic channel (e.g. telemetry:dev_k8m2n4:odom)
dataobjectyesMessage payload
timestampISO 8601noServer-generated timestamp (present on server messages)
request_idstringnoClient-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 CodeDescription
AUTH_REQUIREDNo authentication token provided
AUTH_EXPIREDToken has expired, re-authenticate or refresh
INVALID_CHANNELRequested channel does not exist
RATE_LIMITEDToo many messages per second (default limit: 100 msg/s)
DEVICE_OFFLINECommand target is not connected
PERMISSION_DENIEDRole 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)
  }
}