Skip to content

API ​

The dashboard is a plain SPA talking to these endpoints. They are documented because you may want to script against them — a deploy check, a status page — not because they are a stable public contract. Treat them as internal to the minor version.

All paths are relative to route (/_monitor by default). Everything except POST /api/ingest and POST /api/login requires a valid session cookie, and answers 404 while no credentials are configured at all.

POST /api/login ​

json
{ "username": "admin", "password": "…" }

Sets the session cookie. Answers 401 for a wrong username or a wrong password, with the same message either way — telling the two apart would confirm a valid username to somebody guessing.

POST /api/logout ​

Clears the session. Requires a same-origin Origin header.

POST /api/session ​

Whether the current cookie is valid.

json
{ "authenticated": true }

GET /api/dashboard ​

Everything the overview screen draws, in one call: totals, a trend carrying requests and errors on the same buckets, the breakdowns with each slice's share of traffic beside its errors, the busiest endpoints, the biggest contributor and the most recent issues.

ParameterMeaning
windowMilliseconds
facetsComma-separated dimensions to break down by
Facet namesRepeatable — narrows every figure at once

One endpoint rather than one per widget: the numbers on a screen have to describe one instant, and six requests are six chances to disagree about which.

GET /api/overview ​

The older, narrower version of the same thing: error counts by side, issue counts, a trend series, the worst routes and the most recent issues.

Query: hours (default 24).

GET /api/issues ​

The issue list.

ParameterMeaning
sideclient or server
resolvedtrue or false
searchMessage, file or route
typeError type
manualtrue for exception() reports only, false for caught only
groupA priority group, e.g. payments
levelinfo, warning, error or critical
kindapi, page or asset
limit, offsetPaging
Facet namesRepeatable — ?browser=Safari&browser=Firefox
json
{ "issues": [ … ], "total": 128 }

GET /api/issues/:fingerprint ​

One issue with its recent occurrences, each with its stack resolved to source. Also returns the facet counts for the breakdown, the number of distinct sessions, and how many occurrences match the filter.

Accepts the same facet parameters, so the breakdown and the occurrences below it always describe the same slice.

PATCH /api/issues/:fingerprint ​

json
{ "resolved": true }

Requires a same-origin Origin header. A resolved issue reopens by itself if it happens again.

GET /api/facets ​

Facet counts over a window, for the filter panel.

Query: window (milliseconds), plus any facet filters to count within.

GET /api/interactions ​

What was pressed, ranked, optionally within one page. Counts per (route, label) — never a sequence, never tied to a session.

ParameterMeaning
windowMilliseconds
routeNarrows to one page; normalised, so /posts/1 finds /posts/:id
limitRows returned, 1–200 (default 20)

share is measured against every press in the window, not only the rows returned — a truncated list would otherwise report its top row as dominant.

GET /api/stats ​

Releases, routes, sessions and environments — the four section screens.

Query: window, and section to fetch just one (releases, routes, sessions, environments, traffic, heatmap).

environments also returns traffic_facets: the same dimensions counted over page views, so a share can be judged against the audience rather than against other errors.

They share one endpoint because they are read together and describe the same window; splitting them would mean four round trips and four chances for the numbers on one screen to disagree about which instant they describe.

GET /api/health ​

The collector's own state. See Storage.

GET /api/notifications ​

What is configured and what has been sent. See Notifications.

Query: limit (default 100, max 200).

json
{
  "enabled": true,
  "channels": [{ "name": "ops-chat", "type": "telegram", "enabled": true }],
  "triggers": { … },
  "cooldownMinutes": 60,
  "deliveries": [
    { "id": 41, "at": 1737000000000, "channel": "ops-chat", "reason": "regression",
      "fingerprint": "9f2c…", "alerts": 1, "status": "sent" }
  ]
}

Channel names and types only — a token in a response is a token in a browser's memory and in anything that later reads it.

status is sent, failed with the reason the channel gave, or suppressed with the rule that silenced it. The failures are the point: the question asked of an alerting system is "why did nobody tell me?", and the answer is never among the successes.

POST /api/notifications ​

Sends a test alert to every configured channel and returns what happened. Takes no body — what a test sends is not the caller's choice, or the endpoint becomes a way to post arbitrary text through somebody's bot token.

json
{ "sent": true, "deliveries": [ … ] }

Answers { "sent": false, "reason": … } when no channel is configured.

GET /api/uptime ​

The calm-days bar: one entry per day, each with a verdict and the counts behind it, plus how many of the recorded days were calm.

Query: days (default 90, max 365).

Not bounded by the dashboard's window — see the overview.

GET /api/export ​

Downloads the stored data. Streamed, so an export of a large table does not build the whole body in memory.

ParameterMeaning
tableissues (default) or events
formatjson (default) or csv
sinceEpoch ms; only rows at or after it
limitRow ceiling. Defaults to 50,000; limit=0 means everything

CSV cells beginning =, +, - or @ are prefixed with an apostrophe: those are formulas to Excel and Sheets, and error messages are attacker-influenced text.

POST /api/ingest ​

The one route without a session — the browser posts here and has no credentials to offer. It is therefore treated as hostile input:

  • Same-origin only. A cross-origin post cannot be a genuine report from your app, and letting one through would let any site fill your database.
  • Rate limited to 100 events per address per minute.
  • Bounded body at 512 KB, checked before anything parses it.
  • Bounded fields: 20 events per batch, 10 KB per stack, 1 KB per message, 30 breadcrumbs.

Answers 202 when events were accepted, 413 for an oversized body, 429 when rate limited, and 204 when it declined for any other reason — a cross-origin post, an unreadable body, an empty batch. A client cannot act on a more precise answer, and a precise one would help somebody probing the limits.

Released under the MIT License.