# Checks

> Heartbeat checks for cron jobs and scheduled tasks, ping URLs, and HTTP uptime checks.

A check is either a **heartbeat** (your job pings Pulse when it runs) or an **HTTP check** (Pulse requests your URL on an interval). A check is `new` until it hears from its job or first probe, then `up` or `down`. Pulse alerts once when a check goes down and once when it comes back. A paused check doesn't alert.

## Heartbeats

A heartbeat has a schedule and a grace period:

- **Schedule:** every N seconds (`{"every_s": 300}`), or a cron expression in a time zone (`{"cron": "0 3 * * *", "tz": "Europe/London"}`). Daylight saving time is handled.
- **Grace:** how long after the expected time Pulse waits before calling the check down (`grace_s`).

Each heartbeat has its own **ping URL**, `https://pulse.nightroll.app/p/<token>`. It's on the check's page in the app and in `GET /api/v1/checks/:id`. Treat it like a password: anyone with it can ping the check.

| Request | Means |
| --- | --- |
| `GET`, `POST` or `HEAD /p/<token>` | The job ran and succeeded |
| `/p/<token>/start` | The job started (the next ping records how long it took) |
| `/p/<token>/fail` | The job failed: the check goes down now |
| `/p/<token>/<0-255>` | An exit code: 0 is success, anything else is a failure |

Pass the exit code straight from the shell:

```sh
0 3 * * *  /usr/local/bin/backup.sh; curl -fsS -m 10 --retry 5 https://pulse.nightroll.app/p/<token>/$?
```

The first 1 KB of a `POST` body is kept with the ping, so you can send the tail of a log: `... | tail -c 1000 | curl -fsS --data-binary @- https://pulse.nightroll.app/p/<token>/fail`. The check page shows the last 10 pings. A URL for a deleted check answers `404`.

A heartbeat goes down when a ping is late past the grace period (missed run) or reports a failure.

## HTTP checks

| Field | Meaning |
| --- | --- |
| `url` | An `https://` or `http://` URL on the public internet |
| `method` | `GET` or `HEAD` |
| `every_s` | How often to probe |
| `expect` | Status codes that count as up: a range or a list, e.g. `"200-399"` or `"200,204"` |
| `keyword` | Optional text the body must contain (GET only) |
| `timeout_s` | Up to 30 |
| `fail_after` | Consecutive failed probes before the check goes down, default 2 |

Probes run from Cloudflare's network.

## Over the API and MCP

`GET/POST /api/v1/checks`, `GET/PATCH/DELETE /api/v1/checks/:id` (`PATCH {"paused": true}` pauses). Creating a heartbeat:

```sh
curl https://pulse.nightroll.app/api/v1/checks -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"name":"Nightly backup","spec":{"kind":"heartbeat","schedule":{"cron":"0 3 * * *","tz":"Europe/London"},"grace_s":1800}}'
```

The MCP tools are `list_checks`, `get_check`, `create_check`, `update_check` and `delete_check`.

## Billing

Each ping received and each probe run is one check run; see the [pricing page](/pricing). A check probed every minute is about 43,200 runs a month.
