Message Format
All WebSocket text messages use JSON. The top-level type identifies the message direction. topic is used only by requests and events.
type | Direction | Description |
|---|---|---|
request | Client to service | Starts a request |
reply | Service to client | Replies to a request |
event | Service to client | Pushes an event |
Request
json
{
"type": "request",
"requestId": "req_state_001",
"topic": "device.state.get",
"data": {
"deviceId": "abc123456789"
}
}| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Fixed as request |
requestId | string | Yes | Client-generated ID that should be unique within the connection |
topic | string | Yes | Request topic |
data | object | Yes | Request parameters |
Device write operations use device.operation.execute. data.operation.name selects the operation:
json
{
"type": "request",
"requestId": "req_relay_001",
"topic": "device.operation.execute",
"data": {
"deviceId": "abc123456789",
"idempotencyKey": "idem_relay_001",
"operation": {
"name": "device.relay.set",
"params": {
"relays": [
{ "index": 1, "action": "ON" }
]
}
}
}
}Reply
A reply is correlated by requestId, so it does not repeat topic and does not use success or a nested error object.
Successful reply:
json
{
"type": "reply",
"requestId": "req_state_001",
"code": "SUCCESS",
"message": "OK.",
"data": {
"deviceId": "abc123456789",
"status": "ONLINE",
"peripherals": {
"relays": [
{ "index": 1, "on": true }
]
},
"stateUpdatedAt": "2026-07-15T08:30:00Z"
}
}Accepted command:
json
{
"type": "reply",
"requestId": "req_relay_001",
"code": "COMMAND_ACCEPTED",
"message": "Command accepted.",
"data": {
"command": {
"id": "cmd_01HX...",
"deviceId": "abc123456789",
"operation": "device.relay.set",
"status": "SUCCESS",
"params": {
"relays": [
{ "index": 1, "action": "ON" }
]
},
"createdAt": "2026-07-15T08:30:00Z"
}
}
}Error reply:
json
{
"type": "reply",
"requestId": "req_relay_001",
"code": "AUTH_SCOPE_DENIED",
"message": "This operation requires the device:control permission. Update the API Client permissions in the Developer Console and obtain a new access token.",
"data": {
"requiredScope": "device:control"
}
}| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Fixed as reply |
requestId | string | Yes | The matching request ID |
code | string | Yes | SUCCESS, COMMAND_ACCEPTED, or an error code |
message | string | Yes | Human-readable context for display and troubleshooting |
data | object | No | Success data or structured error details |
Branch on code, not on the text in message.
Event
json
{
"type": "event",
"topic": "device.state.changed",
"data": {
"deviceId": "abc123456789",
"occurredAt": "2026-07-15T08:30:00Z",
"changes": [
{ "type": "relay", "indexes": [1] }
],
"peripherals": {
"relays": [
{ "index": 1, "on": true }
]
},
"stateUpdatedAt": "2026-07-15T08:30:00Z"
}
}| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Fixed as event |
topic | string | Yes | Event topic |
data | object | Yes | Event data |
Events do not correspond to a request and therefore have no requestId. WebSocket events have no history replay. Query important device state after reconnecting.
See Peripheral State Reference for peripheral fields.
Naming Rules
typeis one ofrequest,reply, oreventtopicuses lowercase words separated by dots- Write operations use
device.operation.executeand place the operation inoperation.name - Event topics describe facts that occurred, such as
device.state.changedanddevice.connection.online - Timestamps use RFC3339 UTC strings
- JSON fields use camelCase
- Clients should ignore newly added fields they do not recognize
