Create Metric Alert Rule

POST /v1/traffic/alerts

Creates a threshold rule the evaluator worker will check on each cycle. A rule combines a metric, an operator, a numeric threshold, and a sliding window. When the observed value crosses the threshold the rule transitions to firing and the worker sends one notification per configured channel — subject to cooldown_seconds to suppress repeats.

Endpoint scope is optional: omit endpoint_uuid to evaluate across every endpoint owned by the caller; supply one to scope the metric to a single endpoint.

Plan-gated: requires the metric_alerts entitlement (403 without it).

Request body

name string Req
Free-form label. Max 255 chars.
description string
Optional. Free-form context (e.g. who gets paged, what to do).
metric string Req
One of: request_rate (req/s), error_rate_pct (% of 4xx+5xx), p95_latency_ms, p99_latency_ms, bandwidth_in_bps, bandwidth_out_bps.
operator string Req
Comparison against the threshold. One of: >, >=, <, <=.
threshold number Req
Threshold value in the metric's native unit (req/s, percent, ms, bytes/s).
window_seconds int Req
Aggregation window. Must be between 60 and 86400 (1 minute to 24 hours).
endpoint_uuid string (uuid)
Optional. Scope the metric to a single endpoint. Omit for account-wide.
cooldown_seconds int
Minimum gap between consecutive notifications while the rule stays firing. 0–86400.
enabled boolean
Defaults to true. Disabled rules are persisted but skipped by the evaluator.
notify_channels array<Channel> Req
At least one channel: a rule without one is refused with 400 (see Errors), even when enabled is false. Each channel has type + target (and optional secret). Supported types: email (target must contain @); webhook (target must be an https:// URL; may include a secret the worker uses to HMAC-SHA256-sign the POST body as X-Ngris-Signature: sha256=<hex>); slack (target is a Slack Incoming Webhook https://hooks.slack.com/... URL); discord (target is a Discord webhook https://discord.com/api/webhooks/... URL). Slack/Discord carry no secret — the URL itself is the credential. A secret of **** is refused with 400 on notify_channels: it is the placeholder responses show for a saved secret, never a secret itself.

Response (201 Created)

The newly-created rule (same fields as Get Alert) with its assigned uuid and endpoint_uuid. status starts at unknown until the first evaluator cycle.

The secret is write-only: a channel that has one answers "secret": "****", never the value you sent. A channel without a secret has no secret key.

Deprecated: until the sunset this response also carries the legacy integer id and *_id fields. Don't use them; read uuid and the *_uuid fields.

Errors

400 Bad Request
Validation failure: missing name/metric/operator, out-of-range window or cooldown, unknown metric, or an invalid channel target. The message is in error and, for a field-level problem, also under fields keyed by the field name.
400 no notification channel
notify_channels missing or empty: {"error": "at least one notification channel is required", "fields": {"notify_channels": "at least one notification channel is required"}}.
400 masked secret
A channel's secret is ****. A new rule has no saved secret to keep, so the placeholder is refused, keyed to notify_channels: channel #N: "****" is the masked placeholder of a saved secret, not a secret: send the webhook's secret, or none (N is the channel's 1-based position).
400 endpoint not found
The supplied endpoint_uuid doesn't match an endpoint in the caller's tenant. Returned as a 400 field-error keyed to endpoint_uuid, not a 404.
401 Unauthorized
Missing auth.
403 Forbidden
Plan doesn't include metric_alerts.
Request — error-rate alert
curl -X POST "https://api.ngris.com/v1/traffic/alerts" \ -H "X-API-KEY: <your_api_key>" \ -H "Content-Type: application/json" \ -d '{ "name": "5xx spike on api", "description": "Page on-call when api endpoint errors exceed 1%", "metric": "error_rate_pct", "operator": ">", "threshold": 1.0, "window_seconds": 300, "endpoint_uuid": "019bb1e4-e785-77d2-9966-c3cff31d0a0e", "cooldown_seconds": 600, "notify_channels": [ {"type": "email", "target": "ops@example.com"}, {"type": "webhook", "target": "https://hooks.example.com/ngris", "secret": "<hmac_secret>"} ] }'
Request — account-wide p99 latency
curl -X POST "https://api.ngris.com/v1/traffic/alerts" \ -H "X-API-KEY: <your_api_key>" \ -H "Content-Type: application/json" \ -d '{ "name": "p99 latency over 500ms", "metric": "p99_latency_ms", "operator": ">", "threshold": 500, "window_seconds": 300, "cooldown_seconds": 900, "notify_channels": [{"type": "email", "target": "sre@example.com"}] }'
Response — 201 Created
{ "uuid": "019c12ab-1cf9-7acd-9ca4-e05a5284d4d8", "name": "5xx spike on api", "description": "Page on-call when api endpoint errors exceed 1%", "metric": "error_rate_pct", "operator": ">", "threshold": 1.0, "window_seconds": 300, "endpoint_uuid": "019bb1e4-e785-77d2-9966-c3cff31d0a0e", "cooldown_seconds": 600, "enabled": true, "status": "unknown", "notify_channels": [ {"type": "email", "target": "ops@example.com"}, {"type": "webhook", "target": "https://hooks.example.com/ngris", "secret": "****"} ], "created_at": "2026-05-21T12:00:00Z", "updated_at": "2026-05-21T12:00:00Z" }
Response — 400 (no notification channel)
{ "error": "at least one notification channel is required", "fields": { "notify_channels": "at least one notification channel is required" } }
Was this helpful?
Was this helpful?