Status · Reference
API
Status has no SDK: a heartbeat is one HTTPS request, a deploy talks to a small JSON API, and Status sends signed webhooks. This page is all three.
Basics
| Base URL | https://services.my-beast.com/status/api/v1, over HTTPS |
| Authentication | Authorization: Bearer sk_…, one of the project's server secrets. The secret says which project it is. See Get a server secret. |
| Format | JSON. A request with a body sends Content-Type: application/json. |
| Rate | Up to 120 requests a minute per secret. |
| IDs and times | A monitor's ID is a UUID. Times are ISO 8601 in UTC. |
The ping needs no secret: its address is the heartbeat. Webhooks go the other way, from Status to your endpoint.
Ping a heartbeat
GET or POST https://services.my-beast.com/status/ping/{token}
The token is in the heartbeat's ping address, on its page, and in the ping_url that Sync the monitors answers with. Add one of these to say more than "it ran":
| Field | What it is |
|---|---|
/start | The job started: its run is timed, and a run that hasn't ended within the heartbeat's period is down. |
/fail | The job failed: down at once. |
/{exit code} | 0 to 999. 0 is a success, like the plain ping; any other is a failure, down at once. |
A POST's body is the job's output: its last 10 KB are kept with the ping and shown on the heartbeat's page. The answer is plain text: 200 with OK, or 404 with Not found for a token that isn't a heartbeat.
curl -fsS -m 10 https://services.my-beast.com/status/ping/abc123def456ghi789jkl012Sync the monitors
PUT /monitors
The project's whole list of the monitors kept in code, in monitors, at most 200. Status adds, changes and removes the monitors with a key to match it, and leaves the ones added on their pages alone. How the list is matched explains the rules.
curl -fsS -X PUT https://services.my-beast.com/status/api/v1/monitors \
-H "Authorization: Bearer $MYBEAST_STATUS_SECRET" \
-H "Content-Type: application/json" \
-d '{"monitors": [
{"key": "api", "name": "API", "type": "http", "url": "https://api.example.com/up", "public": true},
{"key": "backup", "name": "Nightly backup", "type": "heartbeat", "every_minutes": 1440, "grace_minutes": 60}
]}'| Field | For | What it is |
|---|---|---|
key | Both kinds | Required. Finds the monitor again on the next deploy: a-z, 0-9, ., _ and -, starting with a letter or digit, at most 64 characters, once in the list. |
name | Both kinds | Required. As your team says it, at most 80 characters. |
type | Both kinds | Required. http or heartbeat. |
environment | Both kinds | One of the project's environments, such as production or staging. Left out: guessed from url and name, else the project's production environment. |
severity | Both kinds | sev1, sev2 (the default) or sev3: the incident's severity in Incidents, and sent in its webhooks. |
public | Both kinds | true to show it on the public status page. Default false. |
public_name | Both kinds | Its name on the public page, at most 80 characters. Left out: its name. |
public_group | Both kinds | Its group on the public page, at most 60 characters: monitors of one group show together. |
tell_after | Both kinds | Seconds it may be down before anyone is told: 0 (the default), 60, 180, 300, 600, 900 or 1800. |
depends_on | Both kinds | Another monitor's key, in the list or already in the project. While that one is down, this one's down isn't told. |
url | HTTP checks | Required. The address to call, http:// or https://, at most 2,048 characters, at a public address. |
method | HTTP checks | GET (the default) or HEAD. |
expected_status | HTTP checks | One status code the answer must have, 100 to 599. Left out: any 2xx, with redirects followed. A 3xx here is read as it comes, not followed. |
body_contains | HTTP checks | A text the answer must contain, at most 200 characters. GET only. |
interval | HTTP checks | Seconds between checks: 30, 60 or 300, no shorter than your plan allows. Left out: the shortest your plan allows. |
slow_ms | HTTP checks | 50 to 60,000. A good answer slower than this shows as Slow on the public page, and its head says Degraded. |
every_minutes | Heartbeats | How often the job runs, 1 to 10,080 minutes. Required unless cron is given. |
grace_minutes | Heartbeats | How late a ping may be before it's down, 0 to 1,440 minutes. Default 1. |
cron | Heartbeats | A schedule instead of every_minutes: five fields, as in a crontab, like 0 3 * * *. |
timezone | Heartbeats | The schedule's time zone, like Europe/Helsinki or UTC. |
The answer is 200: each listed monitor with its id, type, environment and state (waiting, up, down or paused), a heartbeat's ping_url, and how many were added, changed and removed.
{
"monitors": [
{
"key": "api",
"id": "9d1c4a7e-5b2f-4e8a-9c3d-2f6b8a1e7c40",
"type": "http",
"environment": "production",
"state": "waiting"
},
{
"key": "backup",
"id": "9d1c4a7f-0c3e-4f1b-8a2d-6e5f4c3b2a10",
"type": "heartbeat",
"environment": "production",
"state": "waiting",
"ping_url": "https://services.my-beast.com/status/ping/abc123def456ghi789jkl012"
}
],
"added": 2,
"changed": 0,
"removed": 0
}A list with a mistake is refused whole, with nothing changed:
{
"message": "An HTTP check needs an address.",
"errors": {
"monitors.0.url": [
"An HTTP check needs an address."
]
}
}A list that doesn't fit the plan, or names something the project hasn't got, is refused with a message that names the monitor:
{
"message": "monitors.1 (worker): No other monitor in the list or the project has the key “queue”."
}Start a maintenance window
POST /maintenance
A window from now. Downs it covers are told to nobody while it runs; one still going when it ends is told then. See Plan maintenance.
| Field | What it is |
|---|---|
minutes | Required. How long, from now: 1 to 240. |
note | What customers read when it's announced, at most 500 characters. |
environment | The environment it covers, such as production. Left out: every monitor of the project. |
public | true to announce it on the public page. Default false. |
curl -fsS -X POST https://services.my-beast.com/status/api/v1/maintenance \
-H "Authorization: Bearer $MYBEAST_STATUS_SECRET" \
-H "Content-Type: application/json" \
-d '{"minutes": 10, "note": "Deploying", "environment": "production", "public": true}'{
"id": 12,
"starts_at": "2026-11-01T12:00:00+00:00",
"ends_at": "2026-11-01T12:10:00+00:00"
}End maintenance
POST /maintenance/end
Ends every window of the project that is running now, whoever started it. No body.
curl -fsS -X POST https://services.my-beast.com/status/api/v1/maintenance/end \
-H "Authorization: Bearer $MYBEAST_STATUS_SECRET"{
"ended": 1
}ended is how many it ended.
Record a deploy
POST /deploys
With live, that environment's HTTP checks run within 15 seconds and on every run for 5 minutes, and a down in the 15 minutes after names the deploy.
| Field | What it is |
|---|---|
environment | Required. The environment the deploy went to, such as production. |
phase | Required. started when the deploy began; live when it went live, which runs that environment's HTTP checks. |
ref | What was deployed, at most 80 characters: a commit, a tag, a release number. |
curl -fsS -X POST https://services.my-beast.com/status/api/v1/deploys \
-H "Authorization: Bearer $MYBEAST_STATUS_SECRET" \
-H "Content-Type: application/json" \
-d '{"environment": "production", "phase": "live", "ref": "abc1234"}'{
"id": 31,
"environment": "production",
"checks_due": 4
}checks_due is how many HTTP checks it set to run now.
Webhook messages
Status sends monitor.down and monitor.up to the project's endpoints as a POST with a JSON body, signed the Standard Webhooks way. The headers, the signature and retries are in Receive webhooks.
| Field | Type | What it is |
|---|---|---|
type | string | monitor.down or monitor.up. |
timestamp | string | When the message was made (ISO 8601, UTC). The same on every attempt. |
data.project.slug | string | The project's slug. |
data.project.name | string | The project's name. |
data.monitor.id | string | The monitor's ID. |
data.monitor.key | string or null | Its key in a deploy's list; null for a monitor added on its page. |
data.monitor.name | string | The monitor's name. |
data.monitor.kind | string | http or heartbeat. |
data.monitor.severity | string | sev1, sev2 or sev3. |
data.monitor.environment | string | The environment it watches, such as production. |
data.state | string | down or up. |
data.reason | string | Why, in words: 2 checks in a row failed: Answered 503, Answering again: HTTP 200 in 87 ms. |
data.at | string | When the change happened (ISO 8601, UTC). A down is dated back to its first failed check. |
data.down_for_seconds | integer | On monitor.up only, when Status saw the down: how long it lasted. |
data.url | string | The monitor's page in Status, for someone signed in. |
{
"type": "monitor.down",
"timestamp": "2026-11-01T12:00:45.120Z",
"data": {
"project": {
"slug": "shop",
"name": "Shop"
},
"monitor": {
"id": "9d1c4a7e-5b2f-4e8a-9c3d-2f6b8a1e7c40",
"key": "api",
"name": "API",
"kind": "http",
"severity": "sev1",
"environment": "production"
},
"state": "down",
"reason": "2 checks in a row failed: Answered 503",
"at": "2026-11-01T12:00:30.000Z",
"url": "https://services.my-beast.com/acme/shop/status/monitors/9d1c4a7e-5b2f-4e8a-9c3d-2f6b8a1e7c40"
}
}{
"type": "monitor.up",
"timestamp": "2026-11-01T12:07:15.480Z",
"data": {
"project": {
"slug": "shop",
"name": "Shop"
},
"monitor": {
"id": "9d1c4a7e-5b2f-4e8a-9c3d-2f6b8a1e7c40",
"key": "api",
"name": "API",
"kind": "http",
"severity": "sev1",
"environment": "production"
},
"state": "up",
"reason": "Answering again: HTTP 200 in 87 ms",
"at": "2026-11-01T12:07:00.000Z",
"down_for_seconds": 390,
"url": "https://services.my-beast.com/acme/shop/status/monitors/9d1c4a7e-5b2f-4e8a-9c3d-2f6b8a1e7c40"
}
}A status.test, from an endpoint's Send a test, has these instead:
| Field | What it is |
|---|---|
type | status.test. |
data.project | The project's slug and name. |
data.message | A line saying it's a test. |
data.url | The project's webhooks in Status's settings. |
{
"type": "status.test",
"timestamp": "2026-11-01T11:58:02.310Z",
"data": {
"project": {
"slug": "shop",
"name": "Shop"
},
"message": "A test from Status: this endpoint gets Shop's monitor.down and monitor.up.",
"url": "https://services.my-beast.com/acme/shop/status/settings#webhooks"
}
}Errors
The API answers an error with JSON whose message says what went wrong, in words to show or log. The ping answers in plain text.
| Status | When |
|---|---|
| 401 | No server secret, a wrong one, or a revoked one: {"message": "unauthenticated"}. |
| 402 | PUT /monitors only: the project's subscription has ended. The body has "type": "subscription_ended". |
| 404 | A ping to an address that isn't a heartbeat. |
| 422 | A field is wrong, or Status refuses the change. The message says why, naming the monitor in a list. |
| 429 | Too many requests: more than 120 a minute with one secret, or more than 60 pings a minute to one heartbeat. Wait a minute and try again. |
{
"message": "Shop's subscription ended on 1.11.2026: nothing new is taken in or added.",
"type": "subscription_ended"
}