This page explains the conventions the management REST API follows. The routes themselves are listed by area on the reference pages linked at the end.
Paths
Every management path is scoped to your organisation:
https://<your host>/tenant/<tenantId>/api/<resource>
On the reference pages, paths are written from /api/...; put /tenant/<tenantId> in front. For example /api/iot/devices means https://<your host>/tenant/<tenantId>/api/iot/devices.
Send Authorization: Bearer <API key> on every call. See Authentication.
Requests
- Bodies are JSON: send
Content-Type: application/json. The one exception is a firmware upload, which ismultipart/form-data. - Most bodies are strict: a field the route does not know is refused with
400, never silently ignored. Check spelling when you get a400for a field that looks right. PATCHchanges only the fields you send.PUTreplaces the whole thing (a profile’s alarm rules, a placement, a settings section).- A field set to
nullclears it, where the field allows null. - Ids are UUIDs (
6f1c0a2e-…). A malformed id in a path is usually a400or a404.
Answers
- Answers are JSON. A single record comes wrapped in its name:
{ "device": {...} },{ "alarm": {...} }. - A list comes as an array plus the total count. The array’s name depends on the route:
{ "devices": [...], "total": 42 },{ "items": [...], "total": 7 }, or{ "data": [...], "total": 3 }. Each reference page shows the name. - An answer that carries a secret (a new device token, a new API key, a webhook signing secret) is sent with
Cache-Control: no-storeand is the only place the secret ever appears.
Times
Two forms are used. Each reference page says which.
| Form | Where | Example |
|---|---|---|
| ISO 8601 text, UTC | Record fields (createdAt, lastActivityAt), most filters, webhook payloads | "2026-11-05T10:00:00.000Z" |
| Milliseconds since 1 January 1970, UTC | Telemetry (ts, from, to), alarm times (startTs, openedAt) | 1762336800000 |
Date-range filters of the form createdFrom=2026-10-01&createdTo=2026-10-15 take whole days, YYYY-MM-DD, in UTC, both days included.
Lists
Lists are paged on the server. Two page styles exist:
| Style | page starts at | Used by (examples) |
|---|---|---|
| 1-based | 1 | Devices, device profiles, alarms, sites and assets, API keys, webhooks and their deliveries |
| 0-based | 0 | LoRaWAN gateways and devices, a device’s command history with page, device settings jobs and baselines, MCP connected apps |
The reference pages give the base of each list. The shared parameters:
| Parameter | Meaning | Limits |
|---|---|---|
page | Page number | up to 10,000 or 100,000 depending on the list |
pageSize | Rows per page | default 20, at most 100 (some lists allow 200 or 500, as stated) |
search | Free text, matched inside names and similar fields | up to 128 or 200 characters |
sort | field:asc or field:desc, up to three, comma-separated | only the fields the route lists; anything else is 400 |
| filters | Named per route | see below |
Several 0-based lists also accept limit for pageSize and q for search, for older clients.
Sorting
GET /api/iot/devices?sort=lastSeen:desc
GET /api/iot/lorawan/gateways?sort=status:asc,name:asc
An unknown field is refused with 400, for example “Cannot sort by “x”. Sortable fields: name, connection, lastSeen, createdAt.” On the 0-based lists, ties are broken by id, so paging is stable.
Filters
| Kind | Form | Example |
|---|---|---|
| One of a set | comma list | status=OPEN,ACKNOWLEDGED |
| Ids | comma list of UUIDs, at most 100 | deviceId=…,… |
| Flag | true or false | online=true |
| Day range | <name>From, <name>To | createdFrom=2026-11-01 |
| Number range | <name>Min, <name>Max | where a route offers one |
Leave a parameter out rather than sending it empty: most lists ignore an empty parameter, but some refuse it.
Reading every page
// Node.js 18+: read every device, 100 at a time (a 1-based list)
const base = `https://${host}/tenant/${tenant}/api/iot/devices`;
const all = [];
for (let page = 1; ; page++) {
const res = await fetch(`${base}?page=${page}&pageSize=100&sort=createdAt:asc`, {
headers: { Authorization: `Bearer ${key}` },
});
if (res.status === 429) { await new Promise((r) => setTimeout(r, Number(res.headers.get('Retry-After') ?? 60) * 1000)); page--; continue; }
const { devices, total } = await res.json();
all.push(...devices);
if (all.length >= total || devices.length === 0) break;
}
Sort by a field that does not change (createdAt) when you page through a list that others are changing.
Changes made by someone else
Some writes check that the record did not change since you read it:
| Where | How | On a conflict |
|---|---|---|
| Device profiles | Send the profile’s version as expectedVersion in the PATCH body | 409, details.reason = stale_profile |
| Device settings sections | Send the section’s ETag in an If-Match header | 412, details.reason = rev_mismatch; 428 without If-Match |
A profile’s alarm rules have no such check: a PUT replaces the whole set, so read the rules just before you change them.
Read again, apply your change to the fresh copy, and send it again.
Not found and not allowed
A record of another organisation, or one you may not read, answers 404, the same as a record that does not exist. A route you may not use at all answers 403. See Errors and rate limits for the error body and every status.
Permissions
Every route names the permission it needs, the same permission the app checks. The reference pages list it for each route, for example iot_device.devices.read. Give a key that permission in Module permissions. A permission that only the Owner role has cannot be given to a key.
Route references
| Area | Page |
|---|---|
| Devices, credentials, connection, log, device profiles | Devices and profiles API |
| Latest values, history, attributes | Telemetry and attributes API |
| Commands and the command catalog | Commands API |
| Alarms, alarm rules, mutes | Alarms API |
| Sites, assets, placement, relations | Sites and assets API |
| Firmware packages, rollouts, device settings | Firmware and device settings API |
| LoRaWAN gateways, devices, pairing, frames | LoRaWAN API |
| Webhooks | Webhooks |
| API keys | Authentication |
| LoRaWAN codecs | Codec API |

