New: the AI firewall watches every module and explains each incident in plain words. See how it works

REST API basics

Paths, JSON conventions, lists with paging, sorting and filters, concurrency checks, times and ids – the rules every Hermetry REST route follows.

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 is multipart/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 a 400 for a field that looks right.
  • PATCH changes only the fields you send. PUT replaces the whole thing (a profile’s alarm rules, a placement, a settings section).
  • A field set to null clears it, where the field allows null.
  • Ids are UUIDs (6f1c0a2e-…). A malformed id in a path is usually a 400 or a 404.

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-store and is the only place the secret ever appears.

Times

Two forms are used. Each reference page says which.

FormWhereExample
ISO 8601 text, UTCRecord fields (createdAt, lastActivityAt), most filters, webhook payloads"2026-11-05T10:00:00.000Z"
Milliseconds since 1 January 1970, UTCTelemetry (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:

Stylepage starts atUsed by (examples)
1-based1Devices, device profiles, alarms, sites and assets, API keys, webhooks and their deliveries
0-based0LoRaWAN 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:

ParameterMeaningLimits
pagePage numberup to 10,000 or 100,000 depending on the list
pageSizeRows per pagedefault 20, at most 100 (some lists allow 200 or 500, as stated)
searchFree text, matched inside names and similar fieldsup to 128 or 200 characters
sortfield:asc or field:desc, up to three, comma-separatedonly the fields the route lists; anything else is 400
filtersNamed per routesee 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

KindFormExample
One of a setcomma liststatus=OPEN,ACKNOWLEDGED
Idscomma list of UUIDs, at most 100deviceId=…,…
Flagtrue or falseonline=true
Day range<name>From, <name>TocreatedFrom=2026-11-01
Number range<name>Min, <name>Maxwhere 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:

WhereHowOn a conflict
Device profilesSend the profile’s version as expectedVersion in the PATCH body409, details.reason = stale_profile
Device settings sectionsSend the section’s ETag in an If-Match header412, 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

AreaPage
Devices, credentials, connection, log, device profilesDevices and profiles API
Latest values, history, attributesTelemetry and attributes API
Commands and the command catalogCommands API
Alarms, alarm rules, mutesAlarms API
Sites, assets, placement, relationsSites and assets API
Firmware packages, rollouts, device settingsFirmware and device settings API
LoRaWAN gateways, devices, pairing, framesLoRaWAN API
WebhooksWebhooks
API keysAuthentication
LoRaWAN codecsCodec API