heartbeetsign in

the API

one POST per job; everything else is optional. authentication is the API key in the Authorization header, nothing fancier.

send a heartbeat

the first heartbeat for a name creates the job (201); later heartbeats are 200. there is no registration endpoint. names are case-insensitive for identity but keep their casing for display: the first heartbeat's spelling names the job, and Nightly-Backup and nightly-backup are the same job from then on, so a name can't be reused under a different casing.

the response tells you what the beets now believe:

POST https://heartbeet.page/<job-name>
Authorization: <api-key>
{
  "ok": true,
  "job": "nightly-backup",
  "created": false,
  "status": "active",        // active | late | down | learning
  "suppression": "none",     // none | muted | paused | decommissioned
  "muted": false,
  "lifecycle": "live",
  "interval": "24h2m",       // the learned rhythm, null while learning
  "interval_ms": 86520000,   // the same, in milliseconds
  "recovered": false         // true when this heartbeat resolved an incident
}

tags

tags ride on the heartbeat; membership is the heartbeat, so rosters can't drift. tags drive dashboard grouping, tag-wide pausing, muting (mute a tag in project settings and every job carrying it goes quiet), and on the team plan, tag-routed paging (page the backend folks for api, ops for cron). the test tag is muted by default in every project, so experiments never page anyone.

POST /<job-name>?tag=prod&tag=api

cold-start hints

a hint seeds a wide prior so the very first heartbeat already has a loose deadline; without one, a brand-new job is bounded by the project's cold-start window (24h by default). a hint is a prior, not a setting: real heartbeats outweigh it quickly, though it keeps a little influence while the job's history is short. use one for any job slower than the cold-start window.

POST /<job-name>?hint=weekly
# buckets: minutely | hourly | daily | weekly | monthly | yearly

idempotent retries

optional. if a POST fails before the response reaches you, you can't tell whether the heartbeat landed. resend it with the same Idempotency-Key: heartbeet returns the original response and records nothing new, so the retry can't double-count. keys live for 24 hours.

POST /<job-name>
Authorization: <api-key>
Idempotency-Key: <any-stable-id>

pause a job (or a whole tag)

pausing is its own command, never folded into a heartbeat: "I'm alive" and "expect me to go quiet" are different statements. it warns the beets a planned disruption is coming, so the silence doesn't page anyone.

a paused job is never paged the instant the pause ends. from that point it gets one normal heartbeat window (its learned cadence plus grace period), so a freshly-restarted job that won't check in for nearly a full cadence is still covered. you get that window for free: size the pause to the expected downtime, not the downtime plus a cadence.

both query params are optional and default to 0.

wait is the downtime you expect, the stretch the job will be silent; set it to the deploy's worst-case outage. a 30-second job and an hourly one both want wait=5m to ride out a 5-minute deploy. at the default wait=0 the pause suits a zero-downtime deploy: there's no silence to budget for, but a restart still resets the job's schedule, so the pause hands it a fresh window to find its rhythm again rather than holding it to its old phase.

drain is a leading stretch where the old instance may still be sending heartbeats as it winds down; those are ignored, so a stray heartbeat mid-deploy can't lift the pause early.

# zero-downtime deploy; back within its usual window:
POST /pause/<job-name>

# expect ~10m of downtime (2m drain, then silence):
POST /pause/<job-name>?drain=2m&wait=10m

# every job carrying a tag, in one call:
POST /pause?tag=api
POST /pause?tag=api&drain=2m&wait=10m

api keys

unlimited on every plan; mint one per environment or per job if you like. a key can only send heartbeats and pauses to its project; it can't read data or change settings, so the blast radius of a leaked key is spurious heartbeats, not access. revoke and re-mint from the project page whenever.

Authorization: <api-key>
# sends heartbeats + pauses; can't read data or change settings

agent onboarding

one more endpoint mints a project + API key without a signed-in user, so a coding agent can set up monitoring by itself and hand the project to a human afterwards; it has its own page.

POST /api/agent/onboard
Content-Type: application/json

{"project_name": "acme-app"}   // optional