Skip to main content

RelayX Alerts

Server-side alerts that run on the RelayX platform. Once created, they evaluate continuously without your application needing to be running.

Access via app.alert.

Alert Types

TypeDescription
ThresholdTriggers when a metric stays above or below a value for a sustained period. Use for boundary conditions like temperature > 80 or battery < 20.
Rate-changeTriggers when a metric changes too quickly over a time window. Use for detecting sudden spikes or drops like a pressure drop of 10 units in 1 minute.

API

create()

Create a server-side threshold or rate-change alert.

await app.alert.create(params)
ParameterTypeDescription
namestringUnique alert name
descriptionstring(optional) Human-readable description
typestring"THRESHOLD" or "RATE_CHANGE"
metricstringThe telemetry metric to monitor
configobjectAlert configuration (see below)
notification_channelstring[](optional) Notification channel IDs to deliver to

Returns the alert object with a listen() method attached

Threshold Example

const alert = await app.alert.create({
name: "high_temp",
description: "Alert when temperature exceeds 85",
type: "THRESHOLD",
metric: "temperature",
config: {
scope: {
type: "DEVICE",
value: deviceId,
},
operator: ">",
value: 85,
duration: 300,
recovery_duration: 120,
cooldown: 600,
},
notification_channel: [channelId],
});

Example response:

{
"name": "high_temp",
"description": "Alert when temperature exceeds 85",
"type": "THRESHOLD",
"metric": "temperature",
"config": {
"scope": { "type": "DEVICE", "value": "69ca39e4ed4d9fe786145b48" },
"operator": ">",
"value": 85,
"duration": 300,
"recovery_duration": 120,
"cooldown": 600
},
"notification_channel": ["69cb22a4ed4d9fe786145d01"]
}

Rate-Change Example

const alert = await app.alert.create({
name: "pressure_drop",
type: "RATE_CHANGE",
metric: "pressure",
config: {
scope: { type: "DEVICE", value: deviceId },
operator: "<",
value: -10,
duration: 60,
recovery_duration: 30,
},
notification_channel: [channelId],
});

Config Reference

FieldTypeDescription
scope.typestring"DEVICE", "LOGICAL_GROUP" or "HEIRARCHY"
scope.valuestringThe device ID or group identifier
operatorstring>, >=, ==, <=, <
valuenumberThe threshold or rate value
durationnumberSeconds the condition must hold before the alert fires
recovery_durationnumberSeconds the condition must clear before the alert resolves
cooldownnumber(optional) Minimum seconds between re-fires

update()

Update a server-side alert's configuration.

await app.alert.update(params)
ParameterTypeDescription
idstringThe alert ID
configobject(optional) Updated config (partial update)
const updated = await app.alert.update({
id: alertId,
config: { value: 90 },
});

Example response:

{
"name": "high_temp",
"type": "THRESHOLD",
"metric": "temperature",
"config": {
"scope": { "type": "DEVICE", "value": "69ca39e4ed4d9fe786145b48" },
"operator": ">",
"value": 90,
"duration": 300,
"recovery_duration": 120,
"cooldown": 600
},
"notification_channel": ["69cb22a4ed4d9fe786145d01"]
}

listen()

Subscribe to alert state changes (fire, resolved, acknowledged).

await alert.listen(callbacks)
CallbackDescription
onFire / on_fireAlert condition met, alert has fired
onResolved / on_resolvedAlert condition cleared, alert resolved
onAck / on_ackA device's alert incident was acknowledged
await alert.listen({
onFire: (data) => console.log("FIRED:", data),
onResolved: (data) => console.log("RESOLVED:", data),
onAck: (data) => console.log("ACK:", data),
});

onFire continues to fire after acknowledgement

When a device's incident is acknowledged, RelayX stops dispatching internal notifications (webhooks, email, and any channel attached to the rule via notification_channel). However, while the underlying condition is still true, the alerting engine continues to publish fire events on a cooldown for the audit trail. Those events are delivered to every subscriber of listen(), so your onFire / on_fire callback will keep being invoked after an ack.

This is by design: it preserves a complete record of how long a condition stayed in breach.

Custom notifications

If your onFire callback dispatches its own notifications (Slack, PagerDuty, SMS, etc.), it will continue to fire after the user has acknowledged the alert. The SDK does not silence custom notification logic for you. To match the built-in behavior, track the acknowledged state in your callback and skip dispatch while the incident is in acknowledged. The onAck / on_ack callback gives you the transition you need.


The following operations work the same for both RelayX and ephemeral alerts. State changes (ack, mute, unmute) are globally synced — if one App SDK instance acknowledges or mutes an alert, all other instances see the change.

delete()

Permanently remove an alert.

await app.alert.delete(alertId)
await app.alert.delete(alertId);

list()

Retrieve all alerts.

await app.alert.list()
const alerts = await app.alert.list();

Example response:

[
{
"name": "high_temp",
"type": "THRESHOLD",
"metric": "temperature",
"config": {
"scope": { "type": "DEVICE", "value": "69ca39e4ed4d9fe786145b48" },
"operator": ">",
"value": 85,
"duration": 300,
"recovery_duration": 120
},
"notification_channel": ["69cb22a4ed4d9fe786145d01"]
}
]

get()

Retrieve a single alert by name.

await app.alert.get(name)
const alert = await app.alert.get("high_temp");

Example response:

{
"name": "high_temp",
"type": "THRESHOLD",
"metric": "temperature",
"config": {
"scope": { "type": "DEVICE", "value": "69ca39e4ed4d9fe786145b48" },
"operator": ">",
"value": 85,
"duration": 300,
"recovery_duration": 120,
"cooldown": 600
},
"notification_channel": ["69cb22a4ed4d9fe786145d01"]
}

history()

Fetch a chronological timeline of alert events (fire, resolved, ack) for a device, a rule, or the entire org. Each event carries an incident_id so you can group events by alerting episode.

await app.alert.history(params)
ParameterTypeDescription
rule_typestring"DEVICE", "RULE", or "ORG"
device_identstringSingle-device shorthand. Required for "DEVICE" unless device_idents is set
device_identsstring[]Multi-device variant. Optional on every rule_type. Filters results to events from these devices
rule_idstringRequired when rule_type is "RULE". Optional on "DEVICE" and "ORG" (narrows the result)
rule_statesstring[](optional) Subset of ["fire", "resolved", "ack"]. Defaults to all three
incident_idstring(optional) Filter to a single incident's events
startstringStart time (ISO 8601, UTC)
endstringEnd time (ISO 8601, UTC). Must be after start

rule_type semantics:

rule_typeRequired companionReturns
"DEVICE"device_ident or device_identsEvery alert event for those devices
"RULE"rule_idEvery event for that rule. Optionally narrowed with device_idents
"ORG"(none)Every alert event in the org. Optionally narrowed with device_idents and/or rule_id

Returns { events: [...] } where each event has the shape:

FieldTypeDescription
statestring"fire", "resolved", or "ack"
valueanyThe rolling state captured at the event
timestampnumberUnix milliseconds
incident_idstring | nullServer-generated UUID for the alerting episode. Persists across re-fires/acks until resolved
rule_idstringThe rule that fired
device_idstringThe device the event applies to

Events are returned in chronological order across the whole window.

// Full timeline for a device
const { events } = await app.alert.history({
rule_type: "DEVICE",
device_ident: "sensor_01",
start: "2026-03-01T00:00:00.000Z",
end: "2026-03-25T00:00:00.000Z",
});

// Org-wide, every alert in the window. One round trip.
const { events } = await app.alert.history({
rule_type: "ORG",
start: "2026-03-01T00:00:00.000Z",
end: "2026-03-25T00:00:00.000Z",
});

// Org-wide narrowed to several devices
const { events } = await app.alert.history({
rule_type: "ORG",
device_idents: ["sensor_01", "sensor_02", "gateway_a"],
start: "2026-03-01T00:00:00.000Z",
end: "2026-03-25T00:00:00.000Z",
});

// One incident's full timeline
const { events } = await app.alert.history({
rule_type: "RULE",
rule_id: "rule_abc123",
incident_id: "uuid-here",
start: "2026-03-01T00:00:00.000Z",
end: "2026-03-25T00:00:00.000Z",
});

Querying acknowledgements

Acknowledgement events are part of the same timeline. Pass rule_states: ["ack"] to query only acks.

const { events } = await app.alert.history({
rule_type: "RULE",
rule_id: "rule_abc123",
rule_states: ["ack"],
start: "2026-03-01T00:00:00.000Z",
end: "2026-03-25T00:00:00.000Z",
});

Grouping events into incidents

Every event carries an incident_id. An incident opens on the normal → alerting transition (UUID generated server-side), persists across re-fires and acks, and closes on the return to normal. To present an incident-level UI, reduce events by incident_id.

History retention

Alert history retention depends on your plan. For example, the free plan retains 3 days of history. Queries outside your retention window will return empty results.

ack()

Acknowledge a specific device's alert.

await app.alert.ack(params)
ParameterTypeDescription
device_identstringThe device identifier
alert_idstringThe alert ID
acked_bystringWho is acknowledging (e.g., operator name)
ack_notesstring(optional) Notes about the acknowledgement
await app.alert.ack({
device_ident: "sensor_01",
alert_id: alertId,
acked_by: "operator_jane",
ack_notes: "Investigating issue",
});

mute()

Silence an alert so it stops delivering notifications.

await app.alert.mute(params)
ParameterTypeDescription
idstringThe alert ID
mute_config.typestring"FOREVER" or "TIME_BASED"
mute_config.mute_tillstring(TIME_BASED only) ISO 8601 timestamp to unmute at
Mute TypeBehavior
"FOREVER"The alert stays muted until explicitly unmuted via unmute(). The alert still evaluates but notifications are suppressed.
"TIME_BASED"The alert is muted until the specified mute_till timestamp, then automatically unmutes.
// mute forever
await app.alert.mute({
id: alertId,
mute_config: { type: "FOREVER" },
});

// mute until a specific time
await app.alert.mute({
id: alertId,
mute_config: {
type: "TIME_BASED",
mute_till: "2026-04-01T00:00:00.000Z",
},
});

unmute()

Re-enable a muted alert.

await app.alert.unmute(alertId)
await app.alert.unmute(alertId);