StatusKit // api
// status & monitoring as code

Your monitors belong in a repo, not a dashboard.

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 ↓

01Quickstart

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_..."

02Authentication

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
Keep the key out of your repo. Store it as a CI secret (STATUSKIT_KEY) and reference it as an environment variable — the way you'd treat any deploy credential.

03Monitors

List them

curl -s https://statuskit.app/api/monitors \
  -H "Authorization: Bearer $STATUSKIT_KEY"

Create one

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 it to a status page

# 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}'

04Monitoring as code

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.
Identity is (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.

05Full endpoint reference

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.

MethodPathDescription
POST/api/accountsCreate an account. Returns your api_key once — store it.
GET/api/accountYour account (plan, monitor limit, usage). Never returns the api_key.
GET/api/monitorsList every monitor on your account.
POST/api/monitorsCreate a monitor.
POST/api/monitors/syncReconcile 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/:idFetch one monitor.
PATCH/api/monitors/:idUpdate name, target, interval, enabled, or public.
DELETE/api/monitors/:idDelete a monitor.
GET/api/monitors/:id/detailDrill-down: uptime %, latency history, recent incidents.
POST/api/monitors/:id/test-notifySend a [TEST] alert to a public monitor's confirmed subscribers.
GET/api/notesList status-page incident/maintenance notes.
POST/api/notesPost a status-page note.
PATCH/api/notes/:idEdit a note.
DELETE/api/notes/:idDelete a note.
StatusKit — status & monitoring as code · checker · console · get your key