API overview
Routing, authentication, error shapes and the full endpoint map.
Every endpoint is served by one catch-all route. statusPage.handler strips
basePath from the incoming URL, so a path below is always reached at
basePath + path — by default /api/heyo-status.
Endpoint map
| Method | Path | Auth | Enabled by |
|---|---|---|---|
| GET | /status | — | always |
| GET | /status/summary | — | always |
| GET | /monitors | — | always |
| GET | /monitors/:name | — | always |
| GET | /monitors/:name/uptime | — | always |
| GET | /badge/:name.svg | — | metrics.badges |
| GET | /metrics | — | metrics.prometheus |
| GET | /rss.xml | — | incidents.enabled |
| GET | /atom.xml | — | incidents.enabled |
| GET | /incidents | — | incidents.enabled |
| GET | /incidents/:id | — | incidents.enabled |
| GET | /maintenance | — | maintenance.enabled |
| POST | /subscribers | — | subscribers.enabled |
| GET | /subscribers/confirm | — | subscribers.enabled |
| DELETE | /subscribers/:id | — | subscribers.enabled |
| GET | /subscribers/:id/unsubscribe | — | subscribers.enabled |
| POST | /subscribers/:id/unsubscribe | — | subscribers.enabled |
| GET | /healthz | — | always |
| POST | /admin/checks/run | token | always |
| POST | /admin/incidents | token | incidents.enabled |
| POST | /admin/incidents/:id/updates | token | incidents.enabled |
| PATCH | /admin/incidents/:id | token | incidents.enabled |
| DELETE | /admin/incidents/:id | token | incidents.enabled |
| POST | /admin/maintenance | token | maintenance.enabled |
| PATCH | /admin/maintenance/:id | token | maintenance.enabled |
| DELETE | /admin/maintenance/:id | token | maintenance.enabled |
| GET | /admin/notifications/log | token | always |
A disabled feature answers 404 { "error": "This feature is disabled." },
which is deliberate — it does not reveal whether the route exists.
Authentication
Everything under /admin/* requires:
The token comes from adminToken in the configuration, or the
HEYO_STATUS_API_TOKEN environment variable. Comparison is constant-time.
| Situation | Response |
|---|---|
| No token configured | 503 { "error": "The management API is disabled. Set adminToken or HEYO_STATUS_API_TOKEN." } |
| Wrong or missing header | 401 { "error": "Unauthorized." } |
Public endpoints are unauthenticated by design — a status page is meant to be readable during an outage, including by people who cannot sign in.
Status codes
| Code | Meaning |
|---|---|
200 | Success. |
201 | Created — an incident, a maintenance window, a confirmed subscription. |
202 | Accepted — a subscription awaiting email confirmation. |
204 | Deleted. No body. |
400 | Invalid input. The message names the field. |
401 | Missing or wrong bearer token. |
404 | Not found, or the feature is disabled. |
501 | Manual checks require the status-page runtime. |
503 | Management API disabled, or an email delivery dependency failed. |
500 | Unexpected error, including an unreachable database. |
Errors are always JSON:
Read routes have nothing to recover from, so a database that is unreachable —
an unbound D1 binding, a missing DATABASE_URL — is reported with the same
shape and the actionable message, rather than a plain-text 500.
Conventions
- Request bodies must be JSON objects; arrays and scalars are rejected.
- Timestamps in requests and responses are ISO-8601 (
2026-01-01T02:00:00Z). - List filters accept comma-separated values:
?status=investigating,identified. limitandoffsetare non-negative integers.- String arrays are trimmed and de-duplicated; empty entries are rejected.
- Monitor names in a path must be percent-encoded.
Quick examples
Calling the handler directly
The HTTP surface is available without a framework, which is what the tests and the CLI dev server use:
Router-relative paths are passed through unchanged, so
new Request("http://localhost/status") works too.