Documentation menu

Metrics, uptime, alerts and incidents

Module: metrics. See README.md for authentication and ranges.

Endpoints

Endpoint Scope Permission What it does
GET /apps/{app}/metrics/names read metrics.read Metric names, kinds and units
GET /apps/{app}/metrics/series read metrics.read One metric aggregated into buckets
GET /apps/{app}/monitors read metrics.read Uptime monitors with 30-day uptime
POST /apps/{app}/monitors write metrics.write Create or update a monitor
GET /apps/{app}/alerts read metrics.read Alert rules
POST /apps/{app}/alerts write metrics.write Create or update an alert rule
GET /apps/{app}/incidents read metrics.read Alert incidents

Metrics use their own range vocabulary: 1h, 6h, 24h (default), 7d, 30d, 90d, with intervals 1m, 5m, 15m, 1h, 6h, 1d.

Series

?name=<required>&agg=sum|avg|count|p50|p95|p99|max|min&range=24h&interval=&label.<key>=<value>

Label filters are repeated query parameters: ?label.region=eu&label.table=orders.

{
  "name": "db.query",
  "agg": "p95",
  "interval": "15m",
  "source": "rollups",
  "points": [{ "t": "2026-09-03T10:00:00.000Z", "value": 42.5 }],
  "summary": { "count": 9120, "sum": 402000, "min": 1.2, "max": 980, "avg": 44.1 }
}

source says where the answer came from: ranges up to six hours read raw metric_points, longer ranges read the five-minute rollups. Percentiles over rollups are computed from a bounded per-bucket sample, so they are close but not exact — the field is there so you can say which you are quoting.

Monitors

POST /apps/{app}/monitors
{ "name": "Home", "url": "https://example.com/health", "method": "GET",
  "expectedStatus": 200, "intervalSeconds": 300, "timeoutMs": 10000, "enabled": true }

Pass id to update. The URL must be a public http(s) address: loopback, private ranges and link-local addresses are refused with a 400, because a monitor is a request the platform makes on your behalf. bodyMatch fails the check unless the response body contains the text; bodyAbsent fails it when the body does. Both are optional and may be combined.

Rows carry lastStatus, lastCheckedAt, lastLatencyMs, consecutiveFailures and 30-day uptime.

Alert rules

POST /apps/{app}/alerts
{ "name": "Slow queries", "kind": "metric", "threshold": 500, "comparator": "gt",
  "windowMinutes": 5, "cooldownMinutes": 30, "severity": "warning",
  "condition": { "name": "db.query", "agg": "p95" }, "channelIds": ["c_1"] }

condition depends on kind:

Kind Condition
metric { name, agg, labels? } — required
uptime { monitorId } — required
vital { vital: "LCP" | "CLS" | "INP" | "FCP" | "TTFB", path? } — required
error_rate { level? }
event_volume { name? }
new_issue {}

A rule is bound to the environment resolved from ?env=. Channels must already exist (create them in the console); an unknown channel id is a 400.

Incidents

?status=open|acknowledged|resolved|all   (default open)

An open incident means a rule is firing right now. Acknowledging and resolving are console actions.