Setup documentation
Docs
Everything you need to connect a project to Status, expose a working /api/health endpoint, and surface the results on your own site.
Health endpoint contract
This page defines, field by field, the endpoint Status polls. If your store already returns this shape (the Bohuvuj stores do), you can skip straight to registering your project.
Request
| Method | Path | Auth | Query |
|---|---|---|---|
| GET | /api/health (your URL, any path) | None — must be reachable anonymously | None; extra keys are ignored |
Success response (200)
{
"status": "ok",
"results": [
{ "id": "site", "label": "Website", "status": "ok", "latencyMs": 42, "detail": { "http_status": 200 } },
{ "id": "db", "label": "Database", "status": "ok", "latencyMs": 9, "detail": { "query_ms": 7 } },
{ "id": "auth", "label": "Auth service","status": "degraded", "latencyMs": 610, "detail": { "p95_ms": 890 } }
]
}Top level
| Field | Type | Required | Meaning |
|---|---|---|---|
| status | string | optional | Aggregate label for humans. Displayed but not computed from — the app derives its own overall status from the results array. |
| results | array | required | One object per monitored component. May be empty. |
Result object
| Field | Type | Required | Rules |
|---|---|---|---|
| id | string | yes | Stable component identifier (site, db, …). Used as the series key — keep it constant across responses. Non-string values are stringified. Two entries with the same id: the last one wins. |
| label | string | no | Display name. Defaults to the id. Overwrites the previous label for that component. |
| status | string | yes | One of ok, degraded, down. Case is normalized; anything else (including null) is coerced to down. |
| latencyMs | number | no | Milliseconds measured for that component (e.g. the time a DB ping took). Rendered as · 12ms on the page. Non-numbers become null. |
| detail | object | no | Free-form JSONB stored verbatim. Use it for error messages, HTTP codes, stack traces, URLs. Shown in incident day-detail panels. Keep it non-sensitive and small. |
Status semantics
| Status | Meaning | Page color |
|---|---|---|
| ok | Component is fully healthy. | green |
| degraded | Running, but not well — slow, retrying, near capacity, partial failures. | amber |
| down | Unavailable or failing. | red |
Failure handling (what the poller does)
If the fetch fails, the status app still records a result — one synthetic component — instead of failing the whole run:
| Case | Recorded result |
|---|---|
| Network unreachable / DNS failure | { "id": "site", "status": "down", "detail": "unreachable" } |
| Timeout (no response in 15s) | { "id": "site", "status": "down", "detail": "unreachable" } |
| Non-JSON / empty body | { "id": "site", "status": "down", "detail": { "http_status": 502 } } |
| JSON without a results array | { "id": "site", "status": "down", "detail": { "http_status": 200 } } (body parsed, but the shape is wrong) |
Important: any HTTP status is acceptable as long as the body is valid JSON with a results array. A 500 with a valid JSON body is recorded as its components; a 200 with an HTML error page is recorded as site down. Always return proper JSON.
Edge cases and guidance
- Empty results: a valid poll with zero components. The page shows the “no checks” empty state and an ok overall. Prefer at least a site entry.
- Component lifecycle: adding an id makes it appear on the next poll; removing an id makes it disappear from the live list (old history stays until retention). Keep ids stable to avoid chart churn.
- Retention: history is pruned at 370 days by the poller; the calendar covers 365. There is no retention tier concept yet.
- Timing: all times are stored in UTC and the calendar is grouped by UTC day.
- Keep it cheap: checks run every 5 minutes, 288×/day. Fast, lightweight sub-pings are ideal.
- Never leak secrets: detail is stored, so don’t echo tokens/keys into it.
Ready to write it? See the code examples, then test with a quick curl.