API overview
Base URL, conventions, error shapes, pagination and rate limits for the HTTP API.
Everything the web application does, it does through this API. There is no private surface.
Base URL
https://monitoring.qrdev.org/api/v1All endpoints are under /api/v1 except:
| Prefix | Purpose |
|---|---|
/ingest/* | Browser and SDK ingest — key-authenticated, CORS-enabled |
/scim/v2/* | SCIM 2.0 provisioning — token with admin scope |
/api/v1/public/* | Status pages, public post-mortems — unauthenticated |
/api/v1/agent/* | Agent websocket, terminal, enrolment |
/health, /ready | Liveness and readiness |
Authentication
See Authentication. In short: a JWT in a cookie for the web app, a bearer token for programmatic access.
Conventions
| Content type | application/json both ways |
| Timestamps | RFC 3339, UTC — 2026-09-15T08:14:02Z |
| IDs | UUID v4 |
| Durations | Integer minutes or seconds; the field name says which |
| Booleans | Real JSON booleans, never "true" |
| Empty list | [], never null |
Every response is scoped to the caller's organisation. There is no parameter that widens it.
Errors
{ "error": "a day must be 0 (Sunday) to 6 (Saturday), got 9" }| Status | Means |
|---|---|
400 | The request was malformed or failed validation. The message says what |
401 | Not authenticated, or the token expired |
403 | Authenticated, but your role is not enough |
404 | No such object in your organisation |
409 | Conflict — a duplicate name, a concurrent edit |
429 | Rate limited |
500 | A fault on our side. The message is safe to paste into a ticket |
A 404 on an object that exists in another organisation is indistinguishable
from one that does not exist. That is deliberate: a 403 would confirm the id
is real.
Rate limits
| Bucket | Default | Applies to |
|---|---|---|
| API | 600 / min | Authenticated /api/v1/* |
| Auth | 30 / min | Login, signup, password reset |
| OAuth | 120 / min | Slack and SSO redirects and callbacks |
| Ingest | 240 / min / IP | /ingest/* |
Defaults; see Environment variables to change them.
A limited request returns 429.
Rate limits are per client address. Behind a proxy that does not forward the
real address, every user shares one bucket — set TRUSTED_PROXY_CIDRS so the
forwarded address is trusted.
Pagination
List endpoints take limit and, where the volume warrants it, a cursor. Limits
are capped server-side; asking for more returns the cap rather than an error.
Which role does an endpoint need?
The endpoint index lists the least-privileged role for every route, generated from the router at build time.
Versioning
/api/v1 is the only version. Changes are additive: new fields appear, existing
fields keep their meaning. When behaviour must change incompatibly, the new
behaviour gets a new field and the old one keeps working.
Where this behaviour lives: backend/internal/handler/router.go, backend/internal/config/config.go. If the code and this page disagree, the code is right — please fix the page.
Part of Reference — The exact numbers, names and limits, generated from the code.