Define alert rules, the notification channels they page, and the maintenance windows that silence them, then read and act on the alerts that fire. Everything here uses the same base URL and bearer key as the rest of the Lumen API, and every create call is an upsert: send an Idempotency-Key and the same key later updates the same rule, channel or window instead of making a second one.
monitoring:manage scope.monitoring:channels scope. monitoring:* covers both.403 INSUFFICIENT_SCOPE.Create or update an alert rule. Requires an Idempotency-Key: the first call with a key creates the rule, and a later call with the same key replaces that rule's settings with the new request. Send the complete rule every time, since a field left out is reset to its default. A rule's kind cannot change once it exists; a call that tries returns INVALID_KIND_IMMUTABLE, so delete the rule and create a new one. Lumen remembers a key for 30 days from the call that created the rule.
curl -X POST https://projecthollow.com/lumen/ingest/v1/alert-rules \
-H "Authorization: Bearer hlw_sk_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: checkout-down-alert" \
-d '{
"name": "Checkout API is down",
"kind": "monitor_state",
"condition": "status:down",
"monitorId": "mon_123",
"channelIds": ["chan_456"],
"cooldownMinutes": 15,
"consecutiveFailureThreshold": 2
}'There are three kinds of rule. monitor_state watches a monitor's status, metric_threshold compares an aggregate of a metric over a window with a number, and log_pattern counts matching log lines in a window.
monitor_state, metric_threshold or log_pattern.CHANNEL_OUT_OF_SCOPE.monitor_state and log_pattern: a HEL filter expression of up to 500 characters. A monitor_state condition can test status (pending, up, down, degraded, paused), previousstatus, name, type and change (none, minor, significant). A log_pattern condition can test service, level and message. An expression that does not parse is refused.monitor_state only, optional. Omit it to match every monitor.metric_threshold. aggregation is avg, max or min; windowMinutes is a whole number from 1 to 1440; operator is >, >=, < or <=; threshold is a finite number. service optionally narrows the metric to one service.log_pattern: windowMinutes (1 to 1440) and countThreshold (a whole number of 1 or more) are required, service is optional. The rule fires when at least countThreshold lines match condition within the window.{ "id": "rule_789" }{ "id": "rule_789", "duplicate": true }: the key matched an earlier create, and the rule was updated.RESOURCE_LIMIT_EXCEEDED once you have as many alert rules as your plan allows.List every rule you own plus every rule shared into a Space you belong to, sorted by name. lastFiredAt (epoch milliseconds) is missing for a rule that has never fired, and spaceId appears only on a shared rule.
{
"rules": [
{ "id": "rule_789", "name": "Checkout API is down", "kind": "monitor_state", "cooldownMinutes": 15, "lastFiredAt": 1790000000000, "channelIds": ["chan_456"] }
]
}One rule in full, including the fields specific to its kind (condition, monitorId, metricName, aggregation, windowMinutes, operator, threshold, countThreshold) and any escalation settings. An id that does not exist, or that you cannot see, is 404 NOT_FOUND, never 403.
Delete a rule. Returns { "ok": true } whether or not it still existed. A rule shared into a Space can be deleted by the Space's owner or an admin, not by a plain member.
Create or update a channel that alert rules can page. Needs the monitoring:channels scope and an Idempotency-Key: the first call with a key creates the channel, a later call with the same key updates it. To create or edit a channel in a Space you must be the Space's owner or an admin, because channels hold secrets. email is not a channel kind and is refused.
curl -X POST https://projecthollow.com/lumen/ingest/v1/notification-channels \
-H "Authorization: Bearer hlw_sk_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: checkout-team-slack" \
-d '{ "name": "Checkout team Slack", "kind": "slack", "url": "https://hooks.slack.com/services/…" }'slack, pagerduty or generic_webhook.slack and generic_webhook. Must start with https://.pagerduty: the PagerDuty Events API v2 routing key.generic_webhook only, optional. Lumen generates one if you leave it out.Returns 201 with { "id": "chan_456" }, or 200 with { "id": "chan_456", "duplicate": true } when the key matched an earlier create and the channel was updated. A generic_webhook channel also returns its signingSecret, once, on the call that generates or changes it. It cannot be read again, so store it then. Updating the channel without sending a new secret keeps the existing one. Each webhook delivery carries an X-Lumen-Signature: sha256=<hex> header, an HMAC-SHA256 of the exact JSON body under that secret. A plan caps how many channels you can have (RESOURCE_LIMIT_EXCEEDED).
List every channel you own plus every channel shared into a Space you belong to. The URL, key and secret are never returned. There is no endpoint for a single channel.
{ "channels": [{ "id": "chan_456", "name": "Checkout team Slack", "kind": "slack" }] }Send one real test message from values you have not saved. Send kind with url (slack or generic_webhook) or integrationKey (pagerduty). Nothing is stored and a failed delivery is not retried. The call itself succeeds with 200 either way, so read ok in the body: { "ok": true, "status": 200 } when the destination accepted the message, and ok: false when it did not.
Delete a channel. Returns { "ok": true } whether or not it still existed. A channel shared into a Space can only be deleted by the Space's owner or an admin.
An alert is a rule firing. Alerts are raised by Lumen, never created through the API, and each is open, acknowledged or resolved.
List the alerts you own plus those in Spaces you belong to, newest first. All three filters are optional query parameters: state (open, acknowledged or resolved), monitorId and ruleId.
curl "https://projecthollow.com/lumen/ingest/v1/alerts?state=open" \
-H "Authorization: Bearer hlw_sk_…"
{
"alerts": [
{ "id": "alert_1", "monitorId": "mon_123", "ruleId": "rule_789", "severity": "critical", "title": "Checkout API is down", "detail": "…", "state": "open", "openedAt": 1790000000000 }
]
}One alert in full, with acknowledgedAt and resolvedAt when they apply, and a deliveries array holding a receipt for each notification sent to a channel: its channelId, kind, eventType, whether it went through (ok), the number of attempts, and the last HTTP status.
Mark an alert as acknowledged. Returns { "ok": true }. Allowed for the alert's owner and any member of its Space.
Mark an alert as resolved. Returns { "ok": true }. Allowed for the alert's owner and any member of its Space.
Schedule a period during which alerts for chosen monitors do not notify anyone. Requires an Idempotency-Key: the first call with a key creates the window, and a later call with the same key replaces its settings, which is also how you end a window early (send the same key again with endAt set to now).
curl -X POST https://projecthollow.com/lumen/ingest/v1/maintenance-windows \
-H "Authorization: Bearer hlw_sk_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: checkout-deploy-2026-10-06" \
-d '{
"title": "Checkout deploy",
"monitorIds": ["mon_123"],
"startAt": 1790000000000,
"endAt": 1790003600000
}'endAt must be after startAt, and a window can last at most 30 days.false to switch the window off without deleting it. Defaults to true.Returns 201 with { "id": "win_1" }, or 200 with { "id": "win_1", "duplicate": true } when the key matched an earlier create and the window was updated. A plan caps how many windows you can have (RESOURCE_LIMIT_EXCEEDED). Recurring windows are not supported: schedule each one.
List every window you own plus every window shared into a Space you belong to.
{
"windows": [
{ "id": "win_1", "title": "Checkout deploy", "monitorIds": ["mon_123"], "startAt": 1790000000000, "endAt": 1790003600000, "active": true }
]
}Delete a window. Returns { "ok": true } whether or not it still existed.