These routes let a ticket system, an operations centre or a script work with alarms. How alarms behave is explained in Alarms and Working with alarms. Paths are under https://<your host>/tenant/<tenantId>.
Routes
| Method | Path | Permission | Does |
|---|---|---|---|
| GET | /api/iot/alarms | iot_alarm.read | List alarms |
| GET | /api/iot/alarms/counts | iot_alarm.read | Open and acknowledged counts by severity |
| GET | /api/iot/alarms/{alarmId} | iot_alarm.read | One alarm, with its runbook |
| GET | /api/iot/alarms/{alarmId}/events | iot_alarm.read | The alarm’s timeline |
| POST | /api/iot/alarms/{alarmId}/comments | iot_alarm.acknowledge | Add a note to the timeline |
| POST | /api/iot/alarms/acknowledge | iot_alarm.acknowledge | Acknowledge up to 100 alarms |
| POST | /api/iot/alarms/resolve | iot_alarm.resolve | Resolve up to 100 alarms by hand |
| GET | /api/iot/alarm-rules/{profileId} | iot_alarm.read | A profile’s alarm rules |
| PUT | /api/iot/alarm-rules/{profileId} | iot_alarm.rules.manage | Replace a profile’s alarm rules |
| GET, POST, DELETE | /api/iot/devices/{deviceId}/alarm-mute | read: iot_alarm.read; change: iot_alarm.mute | A device’s mutes |
Default roles: reading and acknowledging are User permissions; resolving, muting and changing rules are Admin permissions.
List alarms
GET /api/iot/alarms (pages from 1). Unknown parameters are refused.
| Parameter | Values | Default |
|---|---|---|
status | active (open or acknowledged), OPEN, ACKNOWLEDGED, RESOLVED, any | active |
severity | comma list of CRITICAL, MAJOR, MINOR, WARNING, INFO | all |
deviceId, profileId | a UUID | |
search | up to 128 characters | |
from, to | ISO 8601 with offset, on the opening time | for RESOLVED and any: the last 7 days |
sort | severity, title, device, status, opened, with :asc or :desc | newest first |
page, pageSize | from 1; 1 to 100 | 1, 20 |
A RESOLVED or any list always reads a time range, at most 92 days: “An alarm history range may span at most 92 days.”
{
"items": [
{
"alarmId": "9a7e…",
"type": "high_temperature",
"severity": "CRITICAL",
"status": "OPEN",
"originator": { "type": "DEVICE", "id": "6f1c…", "name": "cold-room-2" },
"deviceId": "6f1c…",
"deviceName": "cold-room-2",
"profileId": "a1b2…",
"ruleId": "3c4d…",
"title": "Temperature too high",
"details": { "temperature": 9.4 },
"topic": null,
"startTs": 1762678800000,
"openedAt": 1762678800000,
"acknowledgedAt": null,
"acknowledgedBy": null,
"resolvedAt": null,
"resolvedBy": null,
"resolveReason": null,
"occurrences": 3,
"flapping": false,
"isAuto": true
}
],
"total": 1
}
Alarm times are milliseconds since the epoch. resolveReason is clear (its rule or flow cleared it), manual (a person) or originator_deleted (its device was deleted).
GET /api/iot/alarms/{alarmId} answers { "alarm": {...} } with the same fields plus the rule’s runbook. An alarm of another organisation is a 404.
Counts
GET /api/iot/alarms/counts?deviceId={deviceId}
{ "open": 4, "acknowledged": 1, "bySeverity": { "CRITICAL": 1, "MAJOR": 2, "MINOR": 0, "WARNING": 2, "INFO": 0 } }
Only alarms that are not resolved are counted. Leave out deviceId for the whole organisation.
Acknowledge and resolve
POST/tenant/{tenantId}/api/iot/alarms/acknowledge
Acknowledge 1 to 100 alarms with one optional note (up to 2,000 characters). Each alarm is handled on its own; an alarm that is already acknowledged, resolved or not yours is reported with ok: false, not as an error.
Request body application/json
{
"alarmIds": [
"9a7e…",
"b81f…"
],
"note": "Technician on the way, ticket INC-4411"
}
Example curl
curl -X POST 'https://<your-host>/tenant/{tenantId}/api/iot/alarms/acknowledge' \
-H 'Content-Type: application/json' \
-d '{"alarmIds":["9a7e…","b81f…"],"note":"Technician on the way, ticket INC-4411"}'
Responses
200OK
{
"results": [
{
"alarmId": "9a7e…",
"ok": true
},
{
"alarmId": "b81f…",
"ok": false,
"reason": "not_active"
}
]
}
POST /api/iot/alarms/resolve takes the same body and answers the same way. A resolved alarm is final; if the condition is still true, the rule opens a new alarm. Resolving a CRITICAL alarm by hand is written to the audit log.
Add a note without changing the state:
POST /api/iot/alarms/{alarmId}/comments
{ "text": "Spare part ordered" }
The answer is 201 { "ok": true }. Notes appear in the timeline (GET .../events).
Alarm rules
GET /api/iot/alarm-rules/{profileId} answers { "rules": [...] }. PUT replaces the profile’s whole rule set: rules you leave out are deleted, and their open alarms are resolved.
{
"rules": [
{
"type": "high_temperature",
"title": "Temperature too high",
"severities": [
{ "severity": "CRITICAL", "condition": { "key": "temperature", "op": ">", "value": { "attribute": "maxTempThreshold" } } },
{ "severity": "WARNING", "condition": { "key": "temperature", "op": ">", "value": 6 } }
],
"debounce": { "mode": "duration", "raise": 300, "clear": 120 },
"hysteresis": 0.5,
"notify": { "recipients": [{ "kind": "role", "role": "ADMIN" }], "minSeverity": "WARNING", "repeatEverySec": 3600 },
"runbook": "Check the door seal, then the compressor."
},
{
"type": "device_offline",
"title": "Sensor offline",
"inactivity": { "afterSec": 900, "severity": "WARNING" }
}
]
}
Rules of the schema:
| Field | Rule |
|---|---|
| Rules per profile | at most 50; each type once |
type | letters, digits, _ . : -, up to 128; device_offline is the inactivity rule |
condition | a comparison { key, op, value } with op one of >, >=, <, <=, ==, !=, or { "all": [...] } / { "any": [...] }; at most 16 comparisons and 4 levels |
value | a number, text, boolean, or { "attribute": "<name>" } to compare with the device’s attribute |
clear | optional; without it the alarm clears when no severity condition holds |
debounce | mode count (samples) or duration (seconds of device time); raise and clear 1 to 86,400 |
dampening | maxTransitions 2 to 100, windowSec 10 to 86,400, holdSec 1 to 86,400 |
notify.recipients | 1 to 20 of { kind: "user", userId }, { kind: "role", role: "OWNER" \| "ADMIN" \| "USER" }, { kind: "email", email } |
notify.repeatEverySec | 60 to 604,800 |
inactivity.afterSec | 10 to 604,800 |
Condition keys are checked against the profile’s keys. The full meaning of each field is in Alarm rules on device profiles and Alarm conditions reference.
Mutes
| Call | Does |
|---|---|
GET /api/iot/devices/{deviceId}/alarm-mute | { "mutes": [...] } |
POST with { "ruleId": null, "until": "2026-11-12T18:00:00+03:00", "reason": "Compressor service" } | Mute every alarm of the device (or one rule with ruleId) until a time; 201 { "mute": {...} } |
DELETE ?muteId=… | Lift one mute; without muteId, all mutes of the device: { "lifted": n } |
A mute must end in the future and last at most 90 days. Alarms are still recorded while muted; nobody is notified. See Muting a device for maintenance.
Errors
| Status | Message |
|---|---|
| 400 | The time range ends before it starts. / An alarm history range may span at most 92 days. / A mute must end in the future. / A mute may last at most 90 days. |
| 400 | Validation error with issues: a rule set that breaks the schema, or a condition key the profile does not have |
| 404 | Alarm not found. / Device not found. / Device profile not found. |
Events instead of polling
Subscribe a webhook to alarm.created, alarm.acknowledged and alarm.cleared to learn of changes as they happen. Payloads are in the Webhook event reference.

