MQTT contract
Devices talk to the platform over MQTT. Topics are scoped by organization and device, messages are JSON, and identity is proven by a per-device certificate.
Topic taxonomy
Topics follow the pattern v1/{type}/{orgId}/{deviceId}[/...]:
| Topic | Direction | QoS | Retained |
|---|---|---|---|
v1/readings/{orgId}/{deviceId} | device → platform | 1 | no |
v1/gateways/{orgId}/{deviceId}/commands | platform → device | 1 | no |
v1/gateways/{orgId}/{deviceId}/commands/response | device → platform | 1 | no |
v1/gateways/{orgId}/{deviceId}/health | device → platform | 0 | no |
v1/gateways/{orgId}/{deviceId}/status | device → platform | 1 | yes (last will) |
v1/gateways/{orgId}/{deviceId}/config/reported | device → platform | 1 | yes |
Messages
All payloads are JSON. A reading carries a sensorId, an ISO-8601 UTC timestamp, a value, and optional metadata; readings may be batched as a JSON array. The value's JSON type determines its storage type — number, integer, boolean, or string. Commands follow an RPC-style shape (id, method, params, timestamp) with a matching response (id, status, result).
Practical limits apply: a sensorId up to 36 characters, string values up to 1000 characters, metadata up to 10 KB, and a message up to 256 KB. Readings are de-duplicated on (timestamp, organization, sensor).
Example payloads
Reading — published to v1/readings/{orgId}/{deviceId}. The organization comes from the topic, never the body.
{
"sensorId": "s1a2b3c4-d5e6-7890-abcd-ef1234567890",
"timestamp": "2024-01-15T10:30:00.123Z",
"value": 23.5,
"metadata": {}
}
Readings can be batched as a JSON array:
[
{ "sensorId": "s1a2b3c4-…", "timestamp": "2024-01-15T10:30:00.000Z", "value": 23.5 },
{ "sensorId": "s1a2b3c4-…", "timestamp": "2024-01-15T10:30:05.000Z", "value": 23.6 }
]
Command — received on v1/gateways/{orgId}/{deviceId}/commands (RPC-style):
{
"id": "cmd-123456",
"method": "REBOOT",
"params": { "delay_seconds": 5 },
"timestamp": "2024-01-15T10:30:00Z"
}
Command response — published to …/commands/response, correlated by id:
{
"id": "cmd-123456",
"status": "success",
"errorCode": 0,
"result": { "message": "Rebooting in 2000 ms" }
}
status is one of accepted, running, success, error, or rejected.
Health — published to …/health:
{
"uptimeSec": 3600,
"freeHeap": 125000,
"minFreeHeap": 98000,
"rssi": -67,
"mqttConnected": true,
"readingsSent": 1250,
"readingsDropped": 0
}
Status — retained on …/status. The device publishes online on connect; the broker publishes offline as the last will if the device drops:
{ "online": true, "firmwareVersion": "0.1.0" }
{ "online": false }
Security
Devices connect over TLS on port 8883 and authenticate with mutual TLS — the broker derives the device identity from the certificate. The organization in the topic is the source of truth; any organization field inside a message body is ignored. A device subscribes only to its own topics.
Ingestion
Published readings flow from the broker through an internal queue to a dedicated ingestion worker, which validates and writes them to the time-series database. See How it works for the full path.
This page summarizes the device-facing contract. The firmware-authoritative version lives with the embedded sources in the repository under embedded/docs/.