Every StatusKit account ships with a full REST API. Define each client's SSL, domain, uptime and keyword checks in a file, commit it, and apply it from CI. The console is just one view of the same API — nothing here is dashboard-only.
Start in 60 seconds ↓Your api_key is your credential — there are no passwords. Grab it from the console (it's what you sign in with), or mint a fresh account straight from the API:
# One-time: mint an account + key (no password, the key IS the credential)
curl -s -X POST https://statuskit.app/api/accounts \
-H "Content-Type: application/json" \
-d '{"email":"you@agency.com","name":"Your Agency"}'
# => { "id": "...", "email": "...", "api_key": "sk_live_..." } # store it now
Export it once and every call below just works:
export STATUSKIT_KEY="sk_live_..."
Send your key as a bearer token on every request. That's the whole auth model — a key in a header, exactly what a CI job or a Terraform-style workflow wants.
Authorization: Bearer $STATUSKIT_KEY
STATUSKIT_KEY) and reference it as an environment variable — the way you'd treat any deploy credential.curl -s https://statuskit.app/api/monitors \
-H "Authorization: Bearer $STATUSKIT_KEY"
kind is one of http, ssl, domain, keyword. target is the URL or hostname (keyword uses url|word). interval_secs is an integer ≥ 60, default 300.
curl -s -X POST https://statuskit.app/api/monitors \
-H "Authorization: Bearer $STATUSKIT_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Acme — store","kind":"ssl","target":"acme.com","interval_secs":300}'
# Publish a monitor to its public status page:
curl -s -X PATCH https://statuskit.app/api/monitors/mon_123 \
-H "Authorization: Bearer $STATUSKIT_KEY" \
-H "Content-Type: application/json" \
-d '{"public":true}'
Here's the whole point. Keep your fleet in one version-controlled file — monitors.json — that is the request body:
{
"prune": true,
"monitors": [
{ "kind": "ssl", "target": "acme.com", "name": "Acme — SSL cert" },
{ "kind": "domain", "target": "acme.com", "name": "Acme — domain expiry" },
{ "kind": "http", "target": "https://acme.com", "name": "Acme — homepage", "public": true },
{ "kind": "keyword", "target": "https://acme.com|Cart", "name": "Acme — checkout alive" }
]
}
Reconcile the entire file in a single idempotent call to POST /api/monitors/sync. It figures out the diff for you — create the new, update the changed, delete anything prune:true and no longer in the file:
curl -s -X POST "https://statuskit.app/api/monitors/sync" \
-H "Authorization: Bearer $STATUSKIT_KEY" \
-H "Content-Type: application/json" \
--data @monitors.json
# => { "counts": { "created": 4, "updated": 0, "unchanged": 0, "deleted": 0, "stale": 0 }, ... }
# Run it again unchanged => everything "unchanged" (safe to re-run on every push).
# Edit a name / interval => that monitor is "updated" — same monitor, history kept.
# Delete a line (prune:true) => that monitor is "deleted". Your file is the source of truth.
(kind, target) — the thing being watched. Rename a monitor freely and its uptime history is preserved; change a target and it's a new check. Every item is validated before anything is written, and the whole sync applies as one transaction — so a bad line fails the batch, it never leaves you half-reconciled. Leave prune off and the response lists stale monitors (in StatusKit but not your file) without deleting them — a dry run for drift.Now wire it to CI. A pull request to monitors.json becomes a reviewable, revertable monitoring change — exactly like any other code change:
# .github/workflows/statuskit.yml
name: StatusKit sync
on:
push:
branches: [main]
paths: [monitors.json]
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Reconcile monitors
env:
STATUSKIT_KEY: ${{ secrets.STATUSKIT_KEY }}
run: |
curl -sf -X POST "https://statuskit.app/api/monitors/sync" \
-H "Authorization: Bearer $STATUSKIT_KEY" \
-H "Content-Type: application/json" \
--data @monitors.json
Drop your key in as the STATUSKIT_KEY repo secret and your clients' monitoring is version-controlled, diffable, and enforced on every push. That's the difference between "we have a dashboard" and monitoring as code. The monitors.json + workflow above are a complete, copy-pasteable starter — no extra tooling to install.
Every route takes Authorization: Bearer <api_key> except POST /api/accounts. All bodies and responses are JSON. Errors return { "error": "code", "message": "..." } with a matching HTTP status.
| Method | Path | Description |
|---|---|---|
| POST | /api/accounts | Create an account. Returns your api_key once — store it. |
| GET | /api/account | Your account (plan, monitor limit, usage). Never returns the api_key. |
| GET | /api/monitors | List every monitor on your account. |
| POST | /api/monitors | Create a monitor. |
| POST | /api/monitors/sync | Reconcile your whole fleet to a desired-state list. Idempotent — the endpoint behind monitoring as code. prune:true deletes monitors absent from the list. |
| GET | /api/monitors/:id | Fetch one monitor. |
| PATCH | /api/monitors/:id | Update name, target, interval, enabled, or public. |
| DELETE | /api/monitors/:id | Delete a monitor. |
| GET | /api/monitors/:id/detail | Drill-down: uptime %, latency history, recent incidents. |
| POST | /api/monitors/:id/test-notify | Send a [TEST] alert to a public monitor's confirmed subscribers. |
| GET | /api/notes | List status-page incident/maintenance notes. |
| POST | /api/notes | Post a status-page note. |
| PATCH | /api/notes/:id | Edit a note. |
| DELETE | /api/notes/:id | Delete a note. |