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

Alarms API

List, read, acknowledge and resolve alarms, read their timeline and counts, replace a profile's alarm rules and mute devices over REST.

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

MethodPathPermissionDoes
GET/api/iot/alarmsiot_alarm.readList alarms
GET/api/iot/alarms/countsiot_alarm.readOpen and acknowledged counts by severity
GET/api/iot/alarms/{alarmId}iot_alarm.readOne alarm, with its runbook
GET/api/iot/alarms/{alarmId}/eventsiot_alarm.readThe alarm’s timeline
POST/api/iot/alarms/{alarmId}/commentsiot_alarm.acknowledgeAdd a note to the timeline
POST/api/iot/alarms/acknowledgeiot_alarm.acknowledgeAcknowledge up to 100 alarms
POST/api/iot/alarms/resolveiot_alarm.resolveResolve up to 100 alarms by hand
GET/api/iot/alarm-rules/{profileId}iot_alarm.readA profile’s alarm rules
PUT/api/iot/alarm-rules/{profileId}iot_alarm.rules.manageReplace a profile’s alarm rules
GET, POST, DELETE/api/iot/devices/{deviceId}/alarm-muteread: iot_alarm.read; change: iot_alarm.muteA 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.

ParameterValuesDefault
statusactive (open or acknowledged), OPEN, ACKNOWLEDGED, RESOLVED, anyactive
severitycomma list of CRITICAL, MAJOR, MINOR, WARNING, INFOall
deviceId, profileIda UUID
searchup to 128 characters
from, toISO 8601 with offset, on the opening timefor RESOLVED and any: the last 7 days
sortseverity, title, device, status, opened, with :asc or :descnewest first
page, pageSizefrom 1; 1 to 1001, 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:

FieldRule
Rules per profileat most 50; each type once
typeletters, digits, _ . : -, up to 128; device_offline is the inactivity rule
conditiona comparison { key, op, value } with op one of >, >=, <, <=, ==, !=, or { "all": [...] } / { "any": [...] }; at most 16 comparisons and 4 levels
valuea number, text, boolean, or { "attribute": "<name>" } to compare with the device’s attribute
clearoptional; without it the alarm clears when no severity condition holds
debouncemode count (samples) or duration (seconds of device time); raise and clear 1 to 86,400
dampeningmaxTransitions 2 to 100, windowSec 10 to 86,400, holdSec 1 to 86,400
notify.recipients1 to 20 of { kind: "user", userId }, { kind: "role", role: "OWNER" \| "ADMIN" \| "USER" }, { kind: "email", email }
notify.repeatEverySec60 to 604,800
inactivity.afterSec10 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

CallDoes
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

StatusMessage
400The 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.
400Validation error with issues: a rule set that breaks the schema, or a condition key the profile does not have
404Alarm 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.