list_alert_history
const url = 'https://example.com/api/v1/alerts/history';const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request GET \ --url https://example.com/api/v1/alerts/history \ --header 'Authorization: Bearer <token>'Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Query Parameters
Section titled “Query Parameters”Max rows (1–1000, default 100).
Keyset cursor, first half: the last row’s recorded_at, as an RFC 3339 timestamp.
Keyset cursor, second half: the same row’s id. Send both. A whole flush of alerts is
written in one transaction and therefore shares one recorded_at, so a timestamp-only
cursor lands inside that group and skips its remaining rows. Omitting it is still accepted
and means “strictly before that instant”, which is what an older client sends.
Only transitions recorded at or after this RFC 3339 timestamp.
Bounds recorded_at (when the row was written), not at_unix_ms (when the alert fired), so
one index serves the ordering, the cursor and the range. The two differ by the ingest
writer’s flush latency — under a second — so a row may sit marginally outside the window its
displayed time suggests.
Only transitions recorded at or before this RFC 3339 timestamp. Bounds recorded_at; see
since.
Comma-separated severities (info, warning, critical); empty or absent means every
severity. An unknown token is rejected rather than ignored — a dropped token would silently
widen the answer to everything.
Comma-separated node states; empty or absent means every state.
false for fires only, true for clears only. Omit for both.
true for transitions whose incident has been acknowledged, false for those that have
not. Omit for both. Acknowledgement is per incident (node + check + severity), so every
transition of one incident answers the same way.
Only transitions whose metric name contains this text (case-insensitive). Liveness rows store no metric and therefore never match.
Only transitions about this node. Rows about something other than a node (a poller pool) are excluded by construction.
Only transitions about nodes whose current name contains this text (case-insensitive).
Distinct from node_id, which names exactly one node.
Only transitions about nodes in this folder group or any group beneath it.
Responses
Section titled “Responses”A page of history rows, newest first; empty when this deployment keeps no history
An alert-history row plus its current inbound ack state (keyed by the dedup identity, so all transitions of one incident share it).
object
This row’s identity, and the second half of the keyset cursor — pass it as before_id
beside before. Also the stable key for a list: no other field, or combination of them, is
unique.
The SNMP ifIndex of the port this was about, for a per-interface metric (ADR-076).
None for a node-level alert and for every row written before ADR-076 shipped — both mean
“no port was involved”, which is why the column is nullable rather than defaulted (there is
no ifIndex value free to mean “none”; 0 is a real one on some agents).
Metric the check measured (e.g. icmp_rtt_ms, or the liveness sentinel). None for
rows recorded before this was captured (legacy) so the WebUI can show “—”.
The node this transition was about; null when the subject is not a node — read
subject_kind first. It is non-null exactly when subject_kind is node.
Observed sample value that committed the transition (threshold checks only).
Insertion time as an RFC 3339 timestamp, and the first half of the keyset cursor: the
WebUI passes the last row’s recorded_at as before and its id as before_id to
fetch the next (older) page. Distinct from at_unix_ms (the event time).
⚠️ Both halves are required, and this is not defensive. recorded_at defaults to now(),
which in PostgreSQL is the transaction timestamp, and [AlertHistoryStore::record_batch]
writes a whole flush as one multi-row INSERT — so every row of a flush shares a
recorded_at to the microsecond. A page boundary landing inside a flush and paging on the
timestamp alone silently skipped that flush’s remaining rows. A fleet-wide event is
exactly when a flush is large and exactly when someone is reading this log.
The row of a vendor table this was about — a memory pool, a CPU, a sensor — as the row key
its samples carry (ADR-143). None for an alert about the node as a whole or about a port,
and for every row recorded before a table row could alert on its own.
What that row was called when the alert fired, e.g. I/O. None when it had no name.
How serious an alert is. Variants are declared low → high so the derived
Ord ranks Critical above Warning above Info.
The current state of a monitored node or check.
What the transition was about.
The subject’s name, for a subject identified by name rather than by id (a poller pool).
The bound crossed for the committed severity (threshold checks only).
The acknowledgement view attached to an alert / history row in API responses. Carries who acked it, when, from which external tool, and an optional note. Never carries a secret.
object
When the external tool recorded the ack (Unix ms, UTC).
External actor reference (id / handle) — not a secret.
Optional free-text note from the external tool.
Originating tool: pagerduty | jsm | manual | …
The port’s name, as it is called now (ADR-196 decision 6) — not necessarily what it was
called when this row was written. See [ActiveAlertView::if_name].
What the alert is called, in English (ADR-196). See [ActiveAlertView::title].
Example
[ { "direction": "above", "severity": "info", "state": "ok", "subject_kind": "node" }]A cursor or range bound is not RFC 3339, or severity/state is not one of the listed values
The ADR-019 envelope every failure renders as. pub(crate) and schema-bearing so the OpenAPI
document can name one error shape for every endpoint (ADR-035) instead of leaving 4xx/5xx bodies
undescribed — a client that has to guess the failure shape ends up parsing the success shape and
reading undefined.
object
object
Stable machine-readable code. Clients branch on this, never on the message.
Operator-facing sentence. Safe to display; never carries an internal error’s own text.
Examplegenerated
{ "error": { "code": "example", "message": "example" }}No valid bearer token
The ADR-019 envelope every failure renders as. pub(crate) and schema-bearing so the OpenAPI
document can name one error shape for every endpoint (ADR-035) instead of leaving 4xx/5xx bodies
undescribed — a client that has to guess the failure shape ends up parsing the success shape and
reading undefined.
object
object
Stable machine-readable code. Clients branch on this, never on the message.
Operator-facing sentence. Safe to display; never carries an internal error’s own text.
Examplegenerated
{ "error": { "code": "example", "message": "example" }}Role lacks the read permission
The ADR-019 envelope every failure renders as. pub(crate) and schema-bearing so the OpenAPI
document can name one error shape for every endpoint (ADR-035) instead of leaving 4xx/5xx bodies
undescribed — a client that has to guess the failure shape ends up parsing the success shape and
reading undefined.
object
object
Stable machine-readable code. Clients branch on this, never on the message.
Operator-facing sentence. Safe to display; never carries an internal error’s own text.
Examplegenerated
{ "error": { "code": "example", "message": "example" }}The requested node_id or group_id is outside the caller’s scope
The ADR-019 envelope every failure renders as. pub(crate) and schema-bearing so the OpenAPI
document can name one error shape for every endpoint (ADR-035) instead of leaving 4xx/5xx bodies
undescribed — a client that has to guess the failure shape ends up parsing the success shape and
reading undefined.
object
object
Stable machine-readable code. Clients branch on this, never on the message.
Operator-facing sentence. Safe to display; never carries an internal error’s own text.
Examplegenerated
{ "error": { "code": "example", "message": "example" }}