Skip to content

HTTP API

Receiving events

Processes send their events to POST /v1/events, as HttpEventExporter does: a batch of polars-telemetry/events@1 events from one process, process event first, as JSON Lines, gzip-compressed with Content-Encoding: gzip or plain, with the token as Authorization: Bearer <token>. Anything that writes that format can send to Nunatak.

Answer When Body
202 Every event is stored, or was already {"accepted": 12, "duplicates": 0}
400 The body is not valid gzip, or not events@1 {"error": "...", "line": 3}, the line when one is to blame
401 The token is missing or wrong {"error": "missing or wrong token"}
413 Over 8 MiB compressed or 64 MiB once decompressed {"error": "..."}; split the batch
503 Storing it failed, with Retry-After {"error": "storage failed; send again"}

An event is known by its stream's id and its seq, so a batch sent again, whole or in part, stores each event once.

GET /v1/health answers {"status": "ok", "protocol": ["polars-telemetry/events@1"]} without a token.

Reading the history

The dashboard reads everything through the API below, on the dashboard's address and without a token. Your own scripts can use it too; until Nunatak 1.0 it can change between versions, as the changelog will say.

Times are Unix nanoseconds, durations milliseconds. Every list takes the same filters, each optional:

Parameter Keeps queries
label, fingerprint With this label or plan fingerprint
service, environment, host From processes with this value
status running, finished, failed or unfinished
since, until That started at or after, at or before this time

GET /api/queries

The queries, newest first or with order=oldest oldest first, 100 at a time: limit takes up to 1000, offset skips. Each is a summary:

{
  "query_id": "01a12532-7bcd-7082-8a3f-bd94907497ea",
  "stream_id": "5f0c2b1e-...",
  "project": "default",
  "service": "orders-etl",
  "environment": "prod",
  "host": "worker-3",
  "label": "orders/daily_totals",
  "fingerprint": "8c41f0a2d9e7",
  "status": "finished",
  "started_unix_ns": 1791712345000000000,
  "wall_ms": 1840.5,
  "cpu_ms": 9120.2,
  "result_rows": 365,
  "failed": null,
  "warnings": 1,
  "rules": ["in_memory_fallback"],
  "recording": "2026/10/11/01a12532-7bcd-7082-8a3f-bd94907497ea.jsonl.gz"
}

rules names the insights that warned. failed holds Polars' error when the query failed.

GET /api/queries/{id}

One query's summary, or 404.

GET /api/queries/{id}/recording

The query's events, gzip-compressed JSON Lines, for the profile viewer or your own reader. 404 while it runs.

GET /api/groups

The queries grouped by by=label (the default) or by=fingerprint, the group run most recently first:

{
  "key": "orders/daily_totals",
  "runs": 214,
  "failed": 2,
  "last_started_unix_ns": 1791712345000000000,
  "total_wall_ms": 401233.0,
  "rules": ["in_memory_fallback"],
  "recent_wall_ms": [1802.1, 1840.5]
}

recent_wall_ms holds the wall times of the last 30 runs that finished, oldest first.

GET /api/overview

Each label that ran in the last days × 24 hours (14 days unless given, up to 90), with its time lately against its usual time. days lists where each local day the range touches starts, the first possibly before since_unix_ns; utc_offset, minutes ahead of UTC, sets where days start. Takes service, environment, host and label.

{
  "now_unix_ns": 1791712345000000000,
  "since_unix_ns": 1790502745000000000,
  "days": [1790460000000000000, ...],
  "queries": [{
    "label": "orders/country_mix",
    "service": "orders-etl",
    "runs": 336, "failed": 0, "total_wall_ms": 171400.0,
    "usual_wall_ms": 324.0, "usual_band_ms": [298.0, 351.0],
    "recent_wall_ms": 1786.0, "ratio": 5.5, "verdict": "slower",
    "recent_runs": 24, "recent_failed": 0, "failed_before": 0,
    "rules": [], "new_rules": [], "plan_changed_unix_ns": 1791557160000000000,
    "bins": [312.0, 330.5, ...],
    "days": [{"runs": 24, "failed": 0, "median_wall_ms": 318.0}, ...]
  }]
}

Lately means the runs of the last 24 hours, or the last three when there were fewer; usual means the finished runs of the 14 days before them. verdict is slower or faster when the median moved by 1.5× or more and a one-sided Mann-Whitney test puts the chance of that by luck under 1%; otherwise null. new_rules are warnings first seen in the last 7 days, and plan_changed_unix_ns is when the plan the query runs now first ran, if it is not the plan it usually ran. bins holds the median time of equal slices of the range, days one entry per day.

GET /api/versions

Each plan a label finished with at least once, the one that ran last first: fingerprint, runs, failed, first_started_unix_ns, last_started_unix_ns, usual_wall_ms, rules and latest_query_id. Takes since and until.

GET /api/facets

Each value of service, environment, host and status, and how many queries have it. Each field is counted under the other fields' filters, so a filter on service leaves every service listed.

{
  "service": [{"value": "orders-etl", "runs": 1904}, {"value": "reports", "runs": 1210}],
  "environment": [{"value": "prod", "runs": 3114}, {"value": null, "runs": 802}],
  "host": [...],
  "status": [...]
}

Live updates

Two server-sent event streams follow running queries:

Stream Sends
GET /api/live snapshot with the running queries and their latest samples, then query when one starts or ends and pulse with each new sample
GET /api/live/{id} events with the query's events so far, then each new batch as it arrives, then finished with its summary when it ends