Alert and alert-rule endpoints
The API splits alert history (fired notifications) under /api/v1/alerts from alert-rule configuration (the conditions that fire alerts) under /api/v1/alert-rules. Both are scoped by the JWT’s org_id.
Alert-rule bodies use the D95 universal taxonomy (rule_kind + event_category + trigger_topic_pattern + source_filter + system_trigger). The legacy trigger_type column was dropped by migration 000060 and is not part of the request or response contract. The current JSON decoder is permissive: an extra trigger_type property is silently ignored, while a body that relies on it still fails because rule_kind is required. Event payloads nested inside alerts (the “what triggered this alert” sub-object) are pure D95: event_category, trigger_topic, trigger_source. See Alert rule schema for the full field reference.
GET /api/v1/alerts
List fired alerts for the caller’s org.
Since: v1.0 Required role: viewer
Query parameters
| Name | Type | Default | Constraints |
|---|---|---|---|
rule_id | uuid | — | Filters to one rule |
status | enum | — | new, acknowledged, resolved |
date_from | string | — | RFC3339 or YYYY-MM-DD |
date_to | string | — | RFC3339 or YYYY-MM-DD |
cursor | string | null | Pagination cursor |
limit | int | 20 | 1-100 |
Response 200
{ "data": [ { "id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "rule_id": "f5e4d3c2-b1a0-4f9e-8d7c-6b5a4e3d2c1b", "rule_name": "After-hours dock motion", "event_id": "c1d2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f", "camera_id": "550e8400-e29b-41d4-a716-446655440000", "status": "new", "fired_at": "2026-04-21T02:14:00Z", "acknowledged_at": null } ], "next_cursor": null, "total": 1}Error responses
| Code | Body.error | Meaning |
|---|---|---|
| 401 | auth_required | Missing or invalid token |
| 429 | rate_limited | See /developer/rate-limits |
Examples
curl -H "Authorization: Bearer sk_live_abc123" \ "https://novavms.novalien.com/api/v1/alerts?status=new"const alerts = await novavms.alerts.list({ status: 'new' });alerts = client.alerts.list(status="new")GET /api/v1/alerts/{id}/timeline
Return a chronological timeline of state changes for a single alert: fired, acknowledged, status transitions.
Since: v1.1 Required role: viewer
Response 200
{ "alert_id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "entries": [ {"at": "2026-04-21T02:14:00Z", "action": "fired", "actor": null}, {"at": "2026-04-21T02:18:42Z", "action": "acknowledged", "actor": "Jordan Park"}, {"at": "2026-04-21T03:05:00Z", "action": "status_changed", "actor": "Jordan Park", "to": "resolved"} ]}Error responses
| Code | Body.error | Meaning |
|---|---|---|
| 404 | not_found | Alert not in caller’s org |
Examples
curl -H "Authorization: Bearer sk_live_abc123" \ https://novavms.novalien.com/api/v1/alerts/a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d/timelineconst timeline = await novavms.alerts.timeline( 'a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d',);timeline = client.alerts.timeline("a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d")GET /api/v1/alert-rules
List alert rules in the caller’s org.
Since: v1.0 Required role: viewer
Response 200
{ "data": [ { "id": "f5e4d3c2-b1a0-4f9e-8d7c-6b5a4e3d2c1b", "name": "Dock motion", "rule_kind": "camera_event", "event_category": "motion", "trigger_topic_pattern": "VideoSource/MotionAlarm", "source_filter": ["onvif"], "scope_type": "all_cameras", "schedule_type": "always", "cooldown_sec": 300, "enabled": true } ], "total": 1}Error responses
| Code | Body.error | Meaning |
|---|---|---|
| 401 | auth_required | Missing or invalid token |
Examples
curl -H "Authorization: Bearer sk_live_abc123" \ https://novavms.novalien.com/api/v1/alert-rulesconst rules = await novavms.alertRules.list();rules = client.alert_rules.list()POST /api/v1/alert-rules
Create an alert rule.
Since: v1.0 Required role: operator
Request body
All match-axis fields use the D95 universal taxonomy. The legacy trigger_type
column was dropped by migration 000060 and the field is ignored if supplied as
extra JSON. It does not substitute for required D95 fields; a legacy-only body
still returns 400 VALIDATION_ERROR because rule_kind is missing. Full schema
at Alert rule schema.
| Name | Type | Default | Constraints |
|---|---|---|---|
name | string | — | 3-100 chars |
rule_kind | enum | — | Required D95 discriminator: camera_event or system_event. |
event_category | enum | — | D95. One of the 10 categories (list). Allowed only when rule_kind=camera_event. NULL = match any. |
trigger_topic_pattern | string | — | D95. SQL LIKE pattern matched against events.trigger_topic (e.g. %LineDetector%). |
source_filter | string[] | — | D95. Subset of {onvif, software, manual, scheduled}. NULL = match any. |
system_trigger | enum | — | Required when rule_kind=system_event: camera_offline or gateway_offline. gateway_reconnect is rejected. |
custom_tags | string[] | [] | Legacy CRUD-only field. A rule carrying tags is skipped by the immediate evaluator; use a compiled rule for AI predicates. |
min_confidence | float | — | Legacy CRUD-only field, 0.0-1.0. A rule carrying it is skipped by the immediate evaluator. |
scope_type | enum | all_cameras | all_cameras, site, or cameras. Live legacy evaluators fire only all_cameras and cameras; site is accepted/persisted but skipped. |
site_ids | uuid[] | [] | Required when scope_type=site; referenced sites must belong to the org. |
camera_ids | uuid[] | [] | Required when scope_type=cameras; referenced cameras must belong to the org. |
zones_filter | string[] | [] | Legacy CRUD field; the live legacy evaluators do not apply a zone gate. |
schedule_type | enum | always | always or business_hours. Only always fires in the live legacy evaluators. |
cooldown_sec | int | 300 | 60-3600; enforced by the immediate and supported system-event history inserts. |
aggregation_window_seconds | int | 0 | Must be 0; non-zero legacy aggregation is rejected. |
delay_seconds | int | 0 | Must be 0; non-zero legacy delay is rejected. |
conditions_json | object | {} | Must be empty or null; non-empty legacy condition trees are rejected. |
recipients | object[] | [] | Up to 20 recipients, each {user_id, channels}. No lower bound is enforced. |
Response 201
{ "id": "f5e4d3c2-b1a0-4f9e-8d7c-6b5a4e3d2c1b", "name": "Dock motion", "rule_kind": "camera_event", "event_category": "motion", "trigger_topic_pattern": "VideoSource/MotionAlarm", "source_filter": ["onvif"], "scope_type": "cameras", "camera_ids": ["550e8400-e29b-41d4-a716-446655440000"], "schedule_type": "always", "cooldown_sec": 300, "enabled": true, "created_at": "2026-04-21T14:15:00Z"}Error responses
| Code | Body.error | Meaning |
|---|---|---|
| 400 | validation_error | Invalid name, rule shape, scope, schedule, cooldown, or recipient reference |
| 403 | forbidden | Role insufficient |
| 404 | not_found | Referenced camera or site not in caller’s org |
Examples
curl -X POST https://novavms.novalien.com/api/v1/alert-rules \ -H "Authorization: Bearer sk_live_abc123" \ -H "Content-Type: application/json" \ -d '{"name":"Dock motion","rule_kind":"camera_event","event_category":"motion","trigger_topic_pattern":"VideoSource/MotionAlarm","source_filter":["onvif"],"scope_type":"cameras","camera_ids":["550e8400-e29b-41d4-a716-446655440000"],"schedule_type":"always","cooldown_sec":300,"recipients":[{"user_id":"660e8400-e29b-41d4-a716-446655440001","channels":["in_app","email"]}]}'const rule = await novavms.alertRules.create({ name: 'Dock motion', ruleKind: 'camera_event', eventCategory: 'motion', triggerTopicPattern: 'VideoSource/MotionAlarm', sourceFilter: ['onvif'], scopeType: 'cameras', cameraIds: ['550e8400-e29b-41d4-a716-446655440000'], scheduleType: 'always', cooldownSec: 300, recipients: [{ userId: '660e8400-e29b-41d4-a716-446655440001', channels: ['in_app', 'email'] }],});rule = client.alert_rules.create( name="Dock motion", rule_kind="camera_event", event_category="motion", trigger_topic_pattern="VideoSource/MotionAlarm", source_filter=["onvif"], scope_type="cameras", camera_ids=["550e8400-e29b-41d4-a716-446655440000"], schedule_type="always", cooldown_sec=300, recipients=[{"user_id": "660e8400-e29b-41d4-a716-446655440001", "channels": ["in_app", "email"]}],)PATCH /api/v1/alert-rules/{id}
Update fields on an existing rule. Partial updates are supported.
Since: v1.0 Required role: operator
Request body
Any subset of fields accepted by POST /api/v1/alert-rules.
Response 200
Returns the updated rule — same shape as POST.
Error responses
| Code | Body.error | Meaning |
|---|---|---|
| 400 | validation_error | Field value fails schema |
| 404 | not_found | Rule not in caller’s org |
Examples
curl -X PATCH https://novavms.novalien.com/api/v1/alert-rules/f5e4d3c2-b1a0-4f9e-8d7c-6b5a4e3d2c1b \ -H "Authorization: Bearer sk_live_abc123" \ -H "Content-Type: application/json" \ -d '{"cooldown_sec":600}'await novavms.alertRules.update('f5e4d3c2-b1a0-4f9e-8d7c-6b5a4e3d2c1b', { cooldownSec: 600,});client.alert_rules.update( "f5e4d3c2-b1a0-4f9e-8d7c-6b5a4e3d2c1b", cooldown_sec=600,)DELETE /api/v1/alert-rules/{id}
Delete an alert rule. Historical alerts fired by the rule remain accessible via /api/v1/alerts.
Since: v1.0 Required role: operator
Response 200
{"message":"alert rule deleted"}Error responses
| Code | Body.error | Meaning |
|---|---|---|
| 403 | forbidden | Role insufficient |
| 404 | not_found | Rule not in caller’s org |
Examples
curl -X DELETE https://novavms.novalien.com/api/v1/alert-rules/f5e4d3c2-b1a0-4f9e-8d7c-6b5a4e3d2c1b \ -H "Authorization: Bearer sk_live_abc123"await novavms.alertRules.delete('f5e4d3c2-b1a0-4f9e-8d7c-6b5a4e3d2c1b');client.alert_rules.delete("f5e4d3c2-b1a0-4f9e-8d7c-6b5a4e3d2c1b")Sub-routes under /alerts and /alert-rules (pointer)
The alerts surface exposes additional live endpoints not yet broken out into per-section detail on this page. All require a Bearer access token; site-access checks apply. Authoritative shapes live in docs/arch-api-spec.md §21 Alerts and §22 Alert Rules.
Alert history sub-routes:
GET /api/v1/alerts/stats— aggregate stats. Handler:cloud/internal/alerts/alerthistory_handler.go:56.PATCH /api/v1/alerts/{id}/acknowledge— operator acknowledges an alert (D68 lifecycle). Handler:alerthistory_handler.go:65.PATCH /api/v1/alerts/{id}/status— transition statusopen→in_progress→resolved. Handler:alerthistory_handler.go:66.
Alert-rule sub-routes:
GET /api/v1/alert-rules/{id}— return a single rule. Handler:cloud/internal/alerts/alertrule_handler.go:28.POST /api/v1/alert-rules/{id}/test— fire a test notification through the rule’s recipients. Handler:alertrule_handler.go:38.POST /api/v1/alert-rules/{id}/recipients,GET /api/v1/alert-rules/{id}/recipients,DELETE /api/v1/alert-rules/{id}/recipients/{recipientId}— recipient sub-management. Handler:alertrule_handler.go:41-43.POST /api/v1/alert-rules/{id}/activate— activate / publish a draft rule. Handler:alertrule_handler.go:52.GET /api/v1/alert-rules/{id}/nbd,PATCH /api/v1/alert-rules/{id}/nbd— per-rule No-Big-Deal knobs. Handler:cloud/internal/alerts/nbd_handler.go:91-92.
Compiled-rule sub-routes (Phase 6c.1 NL alert compiler):
POST /api/v1/alert-rules/compile,POST /api/v1/alert-rules/test-compiled,POST /api/v1/alert-rules/{rule_id}/compiled-versions,GET /api/v1/alert-rules/{rule_id}/compiled-versions,GET /api/v1/alert-rules/{rule_id}/compiled-versions/active,POST /api/v1/alert-rules/{rule_id}/compiled-versions/{version_id}/activate,DELETE /api/v1/alert-rules/{rule_id}/compiled-versions/{version_id}. Handler:cloud/internal/alerts/compiled_rule_handler.go:135-143.
Budget sub-routes:
GET /api/v1/alert-rules/budget,PATCH /api/v1/alert-rules/budget— Gemini judge daily budget (D82). Handler:cloud/internal/alerts/judge_budget_handler.go:30-34.
Notification-group sub-routes (admin):
GET /api/v1/admin/notification-groups,GET /api/v1/admin/notification-groups/{id}/decisions. Handler:cloud/internal/alerts/group_handler.go:37-38.
The OpenAPI yaml at /openapi.yaml does not yet inventory these routes (see Phase 8 GAP-8-18).
See also
- Alert rule schema — full field reference, validation rules, and worked D95 examples
- Universal ONVIF events — the event taxonomy alert rules match against
- Event endpoints — the events that fire alerts