sondahub

sondahub / Fleet API / Devices

Fake devices API

500 devices from the Fleet API, a fake IoT API. A device on a site. Its MQTT topics are fleet/{serial}/telemetry (published by the broker) and fleet/{serial}/commands (you publish).

Reads with filters, sorting, search and relations; writes that answer like a real server and stay in your session token. Same ids on every build. Free, no key, CORS open.

Connect

List
https://api.sondahub.com/v1/fleet/devices
One record
https://api.sondahub.com/v1/fleet/devices/{id}
OpenAPI 3
https://api.sondahub.com/v1/fleet/openapi.json
GraphQL
https://api.sondahub.com/v1/fleet/graphql
JSON file
https://api.sondahub.com/data/fleet/devices.json
MCP
https://api.sondahub.com/mcp · list_records
The first three devices
curl "https://api.sondahub.com/v1/fleet/devices?limit=3"

Sample records

Records 1 to 3 of 500, exactly as GET /v1/fleet/devices/{id} answers them. The data file holds all of them.

[
  {
    "id": 1,
    "created_at": "2025-02-11T22:55:51Z",
    "updated_at": "2026-09-01T11:56:44Z",
    "serial": "WM5-NY5C7A",
    "name": "water meter, dock B",
    "type": "water_meter",
    "model": "WM-5",
    "site_id": 13,
    "firmware": "2.4.3",
    "status": "online",
    "battery_pct": null,
    "rssi_dbm": -91,
    "ip": "10.16.195.63",
    "mac": "02:4c:18:50:2d:70",
    "tags": [
      "energy"
    ],
    "config": {
      "report_interval_s": 30
    },
    "installed_at": "2025-02-11T22:55:51Z",
    "last_seen_at": "2026-09-01T11:56:44Z"
  },
  {
    "id": 2,
    "created_at": "2025-10-22T12:56:10Z",
    "updated_at": "2026-09-01T11:15:26Z",
    "serial": "WM5-TUG79U",
    "name": "water meter, floor 2",
    "type": "water_meter",
    "model": "WM-5",
    "site_id": 30,
    "firmware": "2.4.3",
    "status": "degraded",
    "battery_pct": null,
    "rssi_dbm": -59,
    "ip": "10.26.212.200",
    "mac": "02:d9:b4:fc:14:15",
    "tags": [],
    "config": {
      "report_interval_s": 30
    },
    "installed_at": "2025-10-22T12:56:10Z",
    "last_seen_at": "2026-09-01T11:15:26Z"
  },
  {
    "id": 3,
    "created_at": "2025-01-09T05:12:48Z",
    "updated_at": "2026-08-27T07:29:37Z",
    "serial": "GT4-63VAS4",
    "name": "gps tracker, dock B",
    "type": "gps_tracker",
    "model": "GT-4",
    "site_id": 35,
    "firmware": "3.0.0-rc1",
    "status": "provisioning",
    "battery_pct": 45,
    "rssi_dbm": -88,
    "ip": "10.40.77.235",
    "mac": "02:62:ea:eb:52:bd",
    "tags": [
      "critical"
    ],
    "config": {
      "report_interval_s": 30
    },
    "installed_at": "2025-01-09T05:12:48Z",
    "last_seen_at": "2026-08-27T07:29:37Z"
  }
]

Fields

FieldTypeNotes
idread-onlyintAssigned by the server. Seed records keep their ids across restarts; records you create continue after the seed.
created_atread-onlydatetimeWhen the record was created (ISO 8601, UTC).
updated_atread-onlydatetimeWhen the record last changed.
serialrequiredstringUnique. Also the device’s MQTT topic segment.
namestring
typerequiredenumthermostat power_meter air_quality water_meter gateway door_sensor vibration gps_tracker
modelstring
site_idrequiredint → sites
firmwarestring
statusenumonline offline degraded provisioning retired
battery_pctintnull for mains-powered devices.
min 0, max 100
rssi_dbmint
ipstring
macstring
tagsjson string[]
configjson { report_interval_s, thresholds? }
installed_atdatetime
last_seen_atdatetime

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 nested routes.

Endpoints

GET/v1/fleet/devicesA page, with every filter, sort, search, field and expand option.
POST/v1/fleet/devicesCreate one: 201 with the record, its id, a Location header and the session token that keeps it; 422 names each field that is wrong.
GET/v1/fleet/devices/{id}One record, with an ETag; If-None-Match earns a 304.
PATCH/v1/fleet/devices/{id}Change the fields you send.
PUT/v1/fleet/devices/{id}Replace the record; required fields must all be there.
DELETE/v1/fleet/devices/{id}Answers 200 with what was removed (a real server’s 204 lives at /v1/utils/status/204); gone for the session.
GET/v1/fleet/devices/{id}/siteThe site this record points at.
GET/v1/fleet/devices/{id}/readingsIts readings, as a page with all the list options.
GET/v1/fleet/devices/{id}/alertsIts alerts, as a page with all the list options.

Try it

List with a filter
curl "https://api.sondahub.com/v1/fleet/devices?type=power_meter&battery_pct_gte=1&expand=site&limit=3"
One record
curl https://api.sondahub.com/v1/fleet/devices/1?expand=site
Its readings
curl "https://api.sondahub.com/v1/fleet/devices/1/readings?limit=5"
Create
curl -i -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"}'
Read it back: newest first (send the session token)
curl "https://api.sondahub.com/v1/fleet/devices?sort=-id&limit=3" \
  -H "X-Sondahub-Session: THE_TOKEN_FROM_THE_CREATE"
Change one field
curl -X PATCH https://api.sondahub.com/v1/fleet/devices/1 \
  -H "Content-Type: application/json" \
  -d '{"type":"power_meter"}'
Delete the one you created
curl -X DELETE https://api.sondahub.com/v1/fleet/devices/501 \
  -H "X-Sondahub-Session: THE_TOKEN_FROM_THE_CREATE"

Query recipes

Every list option works on devices; these are ready to paste. All the options.

WhatRequest
Page 2, 50 at a time (of 500)/v1/fleet/devices?page=2&limit=50
Only type = thermostat/v1/fleet/devices?type=thermostat
Battery pct between two values, largest first/v1/fleet/devices?battery_pct_gte=1&battery_pct_lte=500&sort=-battery_pct
Newest first by created at/v1/fleet/devices?sort=-created_at&limit=5
Serial contains “wm5”/v1/fleet/devices?serial_like=wm5
Full-text search/v1/fleet/devices?q=wm5
Only some fields/v1/fleet/devices?fields=id,serial&limit=5
Embed the site/v1/fleet/devices/1?expand=site
Its readings (nested route)/v1/fleet/devices/1/readings?limit=5
Just the count/v1/fleet/devices?limit=1&fields=id

GraphQL

The same devices as the devices query on https://api.sondahub.com/v1/fleet/graphql, and one device as device(id: 1). Introspection is on. More on the GraphQL API.

{
  devices(limit: 3, sort: "-id", filter: { type: thermostat }) {
    total
    data {
      id serial name type
      site { code }
      readings(limit: 2) { id }
    }
  }
}
Run it
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 } } } }"}'

From an AI agent (MCP)

The MCP server reaches the same rows with list_records, get_record, create_record, update_record and delete_record, and as the resource sondahub://fleet/devices.

tools/call
curl https://api.sondahub.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_records","arguments":{"api":"fleet","collection":"devices","filters":{"type":"thermostat"},"limit":3}}}'

More in the Fleet API

sites (40) · readings (4,908) · alerts (500) · commands (300)

The live streams, the MQTT broker, and the whole API on one page: Fleet API. Other worlds: fake e-commerce API, fake banking API, fake social media API, fake helpdesk API, fake flight booking API, fake user directory API.