APIs / Fleet
An IoT fleet: sites, devices, telemetry and alerts — the MQTT one.
Five hundred devices of eight kinds across forty sites. The readings table holds the last window of telemetry; the live stream and the MQTT broker publish fresh readings every few seconds and accept commands back. Devices report firmware, signal and battery so there is always something to alert on. 6,248 records in all.
In Sonda: Import → From a URL with the OpenAPI address and the whole API lands as a project, one request per operation with example bodies. No keys, no headers to add. More on each protocol.
_note and X-Sondahub-Write: simulated. A GET afterwards will not find what you wrote.curl https://api.sondahub.com/v1/fleet
Every list answers { "data": [...], "meta": { "page", "limit", "total", "pages" } } with X-Total-Count and Link headers (next, prev, first, last). These options work on every collection and every nested route:
| Option | Meaning | Example |
|---|---|---|
page, limit | Paging, 1-based; limit 1–200, default 20. offset works too. | ?page=3&limit=50 |
sort | Comma list of fields, - for descending. Default id here. | ?sort=-lat,id |
field=value | Equals. Booleans as true/false, null for missing. | ?kind=warehouse |
_ne _gt _gte _lt _lte | Not equal and comparisons, on numbers, dates and strings. | ?lat_gte=10&lat_lt=100 |
_like | Contains, case-insensitive. | ?code_like=an |
_in | Any of a comma list. | ?id_in=1,2,3 |
_null | true: missing; false: present. | ?kind_null=true |
a.b=value | Inside a JSON field, dotted. | ?address.line1=… |
q | Search across the text fields. | ?q=alpine |
fields | Only these fields back. | ?fields=id,code |
expand | Embed related records. | ?expand=devices |
A name that is not a field answers 400 and lists the fields. Writes answer 422 with one line per problem, 404 for a missing id, 405 with an Allow header for a verb a route does not take.
A place devices are installed. 40 records — the file.
| Field | Type | Notes |
|---|---|---|
idread-only | int | Assigned by the server. Seed records keep their ids across restarts; records you create continue after the seed. |
created_atread-only | datetime | When the record was created (ISO 8601, UTC). |
updated_atread-only | datetime | When the record last changed. |
coderequired | string | |
namerequired | string | |
kind | enum | warehouse office plant store datacenter farm clinic depot |
address | json { line1, city, region, postal_code, country } | |
timezone | string | |
lat | float | |
lon | float | |
device_countread-only | int | |
status | enum | active maintenance decommissioned |
Relations: devices → the devices whose site_id is this site. Use ?expand=devices to embed them, or the routes below.
curl "https://api.sondahub.com/v1/fleet/sites?kind=office&lat_gte=10&limit=3"
curl https://api.sondahub.com/v1/fleet/sites/1
curl "https://api.sondahub.com/v1/fleet/sites/1/devices?limit=5"
curl -X POST https://api.sondahub.com/v1/fleet/sites \
-H "Content-Type: application/json" \
-d '{"code":"MIA-01","name":"Miami Warehouse","kind":"warehouse","timezone":"America/New_York","status":"active"}'
curl -X PATCH https://api.sondahub.com/v1/fleet/sites/1 \
-H "Content-Type: application/json" \
-d '{"kind":"office"}'
curl -X DELETE https://api.sondahub.com/v1/fleet/sites/1
A device on a site. Its MQTT topics are fleet/{serial}/telemetry (published by the broker) and fleet/{serial}/commands (you publish). 500 records — the file.
| Field | Type | Notes |
|---|---|---|
idread-only | int | Assigned by the server. Seed records keep their ids across restarts; records you create continue after the seed. |
created_atread-only | datetime | When the record was created (ISO 8601, UTC). |
updated_atread-only | datetime | When the record last changed. |
serialrequired | string | Unique. Also the device’s MQTT topic segment. |
name | string | |
typerequired | enum | thermostat power_meter air_quality water_meter gateway door_sensor vibration gps_tracker |
model | string | |
site_idrequired | int → sites | |
firmware | string | |
status | enum | online offline degraded provisioning retired |
battery_pct | int | null for mains-powered devices. min 0, max 100 |
rssi_dbm | int | |
ip | string | |
mac | string | |
tags | json string[] | |
config | json { report_interval_s, thresholds? } | |
installed_at | datetime | |
last_seen_at | datetime |
Relations: site → one site through site_id; readings → the readings whose device_id is this device; alerts → the alerts whose device_id is this device. Use ?expand=site,readings,alerts to embed them, or the routes below.
curl "https://api.sondahub.com/v1/fleet/devices?type=power_meter&battery_pct_gte=1&expand=site&limit=3"
curl https://api.sondahub.com/v1/fleet/devices/1?expand=site
curl "https://api.sondahub.com/v1/fleet/devices/1/readings?limit=5"
curl -X POST https://api.sondahub.com/v1/fleet/devices \
-H "Content-Type: application/json" \
-d '{"serial":"TH200-7K2M4Q","name":"Thermostat, floor 2 east","type":"thermostat","model":"TH-200","site_id":1,"firmware":"2.5.2","status":"online","ip":"10.4.12.77","mac":"02:8f:1c:4a:9e:31"}'
curl -X PATCH https://api.sondahub.com/v1/fleet/devices/1 \
-H "Content-Type: application/json" \
-d '{"type":"power_meter"}'
curl -X DELETE https://api.sondahub.com/v1/fleet/devices/1
Telemetry. metrics is an object whose keys depend on the device type (a thermostat reports temperature, humidity and setpoint; a power meter voltage, current, power_kw and energy_kwh). 4,908 records — the file.
| Field | Type | Notes |
|---|---|---|
idread-only | int | Assigned by the server. Seed records keep their ids across restarts; records you create continue after the seed. |
created_atread-only | datetime | When the record was created (ISO 8601, UTC). |
updated_atread-only | datetime | When the record last changed. |
device_idrequired | int → devices | |
recorded_atrequired | datetime | |
metricsrequired | json { [metric]: number | boolean } | |
quality | enum | good estimated suspect |
Relations: device → one device through device_id. Use ?expand=device to embed them, or the routes below.
curl "https://api.sondahub.com/v1/fleet/readings?quality=estimated&expand=device&limit=3"
curl https://api.sondahub.com/v1/fleet/readings/1?expand=device
curl -X POST https://api.sondahub.com/v1/fleet/readings \
-H "Content-Type: application/json" \
-d '{"device_id":1,"recorded_at":"2026-09-30T12:00:00Z","metrics":{},"quality":"good"}'
curl -X PATCH https://api.sondahub.com/v1/fleet/readings/1 \
-H "Content-Type: application/json" \
-d '{"quality":"estimated"}'
curl -X DELETE https://api.sondahub.com/v1/fleet/readings/1
Something a device or a site needs looked at. Acknowledge one with PATCH {"status":"acknowledged"}. 500 records — the file.
acknowledged or resolved stamps the matching timestamp.| Field | Type | Notes |
|---|---|---|
idread-only | int | Assigned by the server. Seed records keep their ids across restarts; records you create continue after the seed. |
created_atread-only | datetime | When the record was created (ISO 8601, UTC). |
updated_atread-only | datetime | When the record last changed. |
device_idrequired | int → devices | |
site_id | int → sites | |
severityrequired | enum | info warning critical |
kindrequired | enum | offline low_battery threshold tamper firmware weak_signal |
message | string | |
status | enum | open acknowledged resolved muted |
opened_at | datetime | |
acknowledged_at | datetime | |
resolved_at | datetime | |
acknowledged_by | string |
Relations: device → one device through device_id; site → one site through site_id. Use ?expand=device,site to embed them, or the routes below.
curl "https://api.sondahub.com/v1/fleet/alerts?severity=warning&expand=device&limit=3"
curl https://api.sondahub.com/v1/fleet/alerts/1?expand=device
curl -X POST https://api.sondahub.com/v1/fleet/alerts \
-H "Content-Type: application/json" \
-d '{"device_id":1,"severity":"info","kind":"offline","status":"open"}'
curl -X PATCH https://api.sondahub.com/v1/fleet/alerts/1 \
-H "Content-Type: application/json" \
-d '{"severity":"warning"}'
curl -X DELETE https://api.sondahub.com/v1/fleet/alerts/1
A command sent to a device (reboot, set a config value, request a report). POST one and the answer carries the device’s acknowledgement. 300 records — the file.
acked with a result when the device is online (a real device would take a second; with nothing stored there is no later, so the answer is the acknowledgement), queued otherwise.| Field | Type | Notes |
|---|---|---|
idread-only | int | Assigned by the server. Seed records keep their ids across restarts; records you create continue after the seed. |
created_atread-only | datetime | When the record was created (ISO 8601, UTC). |
updated_atread-only | datetime | When the record last changed. |
device_idrequired | int → devices | |
actionrequired | enum | reboot set_config report_now update_firmware identify |
params | json object | |
status | enum | queued sent acked failed expired |
issued_by | string | |
sent_at | datetime | |
acked_at | datetime | |
result | json object |
Relations: device → one device through device_id. Use ?expand=device to embed them, or the routes below.
curl "https://api.sondahub.com/v1/fleet/commands?action=set_config&expand=device&limit=3"
curl https://api.sondahub.com/v1/fleet/commands/1?expand=device
curl -X POST https://api.sondahub.com/v1/fleet/commands \
-H "Content-Type: application/json" \
-d '{"device_id":1,"action":"reboot","status":"queued"}'
curl -X PATCH https://api.sondahub.com/v1/fleet/commands/1 \
-H "Content-Type: application/json" \
-d '{"action":"set_config"}'
curl -X DELETE https://api.sondahub.com/v1/fleet/commands/1
The same stream two ways: the world's own activity, one tick a second, generated for your connection alone. Both push JSON text messages; SSE names each one with event: and numbers it with id:. ?topics=a,b narrows either.
| Topic | What arrives | How often |
|---|---|---|
telemetry | A fresh reading from one of the devices, with its serial, type and metrics. | 2 s |
alerts | An alert opening or resolving. | ~15 s |
devices | A device going online, offline or degraded. | ~10 s |
wss://api.sondahub.com/v1/fleet/ws?topics=telemetry
> {"type":"hello","api":"fleet","topics":[…],"subscribed":[…]}
> {"type":"event","topic":"telemetry","api":"fleet","ts":"…","data":{…}}
< {"type":"subscribe","topics":["telemetry"]} # narrow to some topics
< {"type":"ping"} # → {"type":"pong"}
< anything else # → echoed back as {"type":"echo"}
curl -N "https://api.sondahub.com/v1/fleet/events?topics=telemetry"
retry: 3000
id: 1
event: telemetry
data: {"type":"event","topic":"telemetry",…}
A broker over WebSocket at wss://api.sondahub.com/mqtt, MQTT 3.1.1 and 5, subprotocol mqtt, any username and password. QoS 0 and 1 (2 is handshaken and delivered at 1), retained messages, + and #, topic aliases. It serves one connection at a time — what you publish reaches your own subscriptions, as on a real broker, but not another client's, because sondahub keeps no shared state.
| Topic | What |
|---|---|
fleet/{serial}/telemetry | Readings, JSON, every 2 s from a rotating set of devices. Subscribe to fleet/+/telemetry for all of them. |
fleet/{serial}/status | Retained. online | offline | degraded, changing now and then. |
fleet/{serial}/ack | The answer to a command you published. |
fleet/alerts | Alerts as they open and resolve. |
| Topic | What |
|---|---|
fleet/{serial}/commands | Publish {"action":"reboot"} (or set_config, report_now, identify) and the device acknowledges on fleet/{serial}/ack. |
anything else | A plain broker: whatever you publish reaches your own matching subscriptions, retained flag honoured. |
Also $SYS/broker/clients/connected and $SYS/broker/uptime every couple of seconds. Retained messages: up to 500 per connection.
Broker wss://api.sondahub.com/mqtt
Version 5 (or 3.1.1)
Subscribe fleet/+/telemetry, fleet/alerts, $SYS/#
Publish fleet/TH200-7K2M4Q/commands {"action":"reboot"}
→ the device answers on fleet/TH200-7K2M4Q/ack
One endpoint, https://api.sondahub.com/v1/fleet/graphql: POST {"query", "variables"} or GET ?query=. Introspection is on, so Sonda's GraphQL mode loads the schema; the SDL is a click away. Every collection is a paged query with the same filter, sort and q options as REST (operators as suffixes: price_lt), a by-id query, relation fields both ways, and create, update, replace and delete mutations — simulated like every write, with the note in extensions.
curl https://api.sondahub.com/v1/fleet/graphql -H "Content-Type: application/json" -d '{"query": "{ devices(limit: 3, sort: \"-id\", filter: { type: thermostat }) { total data { id serial name type site { code } readings(limit: 2) { id } } } }"}'
{
devices(limit: 3, sort: "-id", filter: { type: thermostat }) {
total
data {
id serial name type
site { code }
readings(limit: 2) { id }
}
}
}