# Heartbeats

A heartbeat monitors a scheduled job, such as a cron job or a backup. Your job calls the heartbeat's address each time it runs. If no call arrives in time, or a call reports a failure, the heartbeat goes down and alerts.

:::tip[New to Uptime?]
Start with [Get started with Uptime](/docs/uptime/).
:::

:::note[Pending until the first call]
A new heartbeat shows **Pending** until its first call arrives.
:::

## Adding a heartbeat

<Steps>

1. In the sidebar, go to **Uptime**, open the **Heartbeats** tab, and click **Add heartbeat**.

2. Enter a **Name** that says what runs, so an alert says what stopped. The name is required.

3. Set **Expect a request every** to how often the job runs, in minutes, hours, or days. The default is 1 day. The longest is 7 days.

4. Set **With a grace period of** to how late a call can be before it counts as missed, from 0 to 7 days. The default is 1 hour. With 0, the heartbeat alerts as soon as a call is late.

5. Save the heartbeat, then copy its address with **Copy**. The address is shown only to people who can edit monitors.

</Steps>

The address has the form `<your dashboard address>/api/push/<token>`. If no call arrives within the period plus the grace period, the heartbeat goes **Down** and alerts at once: there's no confirmation period, because the grace period is the confirmation. You can't run a heartbeat on demand.

## Reporting success

Call the address at the end of your job. Any HTTP method works, including `GET`, `POST`, and `HEAD`.

```bash title="Check in when the job finishes"
curl -fsS -m 10 --retry 5 -o /dev/null "$HEARTBEAT_URL"
```

In a crontab, chain the call after the job, so it only runs when the job succeeds:

```bash title="Crontab: back up, then check in"
0 3 * * * /usr/local/bin/backup.sh && curl -fsS -m 10 --retry 5 -o /dev/null "$HEARTBEAT_URL"
```

## Reporting failures

To report a failure straight away instead of waiting for a missed call, add `/fail` or an exit code to the address:

| Address | Means |
| --- | --- |
| `<address>` | Success. |
| `<address>/fail` | Failure, shown as "Reported failure". |
| `<address>/<exit code>` | An exit code from 0 to 999. `0` is success; anything else is a failure, shown as "Exited with code N". |

To pass your job's exit code, call `<address>/$?` right after it:

```bash title="Report the job’s exit code"
/usr/local/bin/backup.sh
curl -fsS -m 10 --retry 5 -o /dev/null "$HEARTBEAT_URL/$?"
```

You can also add these query parameters:

| Parameter | Description |
| --- | --- |
| `msg` | A message to record with the call. |
| `ping` | A response time to record, in milliseconds. |
| `status` | `up` for success; any other value counts as a failure. Only applies to the bare address, and is ignored after `/fail` or an exit code. |

## Responses

| Call | Response |
| --- | --- |
| A valid call | `200` with `{"ok": true}` |
| An unknown or paused heartbeat | `404` with `{"ok": false, "msg": "Monitor not found or not active."}` |
| Anything else after the token | `404` with `Unknown heartbeat path "/xyz". Use /fail or /<exit-code>.` |
| A negative or unrealistic `ping` value | `404` with "Invalid ping value" |
| Too many calls | `429` with `{"ok": false, "msg": "Too many requests"}` |

Each heartbeat accepts up to 60 calls a minute, and each IP address up to 600.

## Availability and the bars

In a heartbeat's health timeline and in the uptime figure on its card, uptime is measured by time, not by counting calls: the time it was up, out of the time its state was known. Time while it's paused, and time before its first call, count as neither up nor down. The availability table on its page counts outages instead, as for every check. See [Availability](/docs/uptime/checks/#availability).

In the health timeline, each bar covers a stretch of time:

- A bar is red if the heartbeat was down at any point in it, and green if it was up for all the time its state was known. An hour in which it went down and came back is red; the hours after it recovered are green.
- An hour with no call in it still shows the heartbeat's state. Its tooltip says "Between calls (expected every 1d)", or "No call arrived (expected every 1d)" while the heartbeat is down, and its **Uptime** is the share of that hour it was up.

Other checks, and tunnels, count their results instead. They run on a fixed schedule, so counting results and measuring time give nearly the same figure.

## Heartbeats in the API

In the [REST API](/docs/api/), `POST /api/v1/servers` with `"type": "push"` creates a heartbeat:

| Field | Details |
| --- | --- |
| `name` | Required. |
| `period` | The longest time between two calls from your job, in seconds. Default `86400` (1 day), from `60` to `604800` (7 days). Below your plan's shortest period, the answer is `403` "The shortest period on your plan is N seconds (requested M)." |
| `grace` | How late a call can be before the heartbeat goes down, in seconds. Default `3600` (1 hour), from `0` to `604800`. |
| `tags`, `active` | Optional, as for other checks. |

The answer includes `period`, `grace`, and `heartbeat_url`, the address your job calls, in the form `<your dashboard address>/api/push/<token>`. `GET /api/v1/servers/:id` and `PUT /api/v1/servers/:id` return it too, but list rows never do, and it's only returned to people who can edit monitors (Owner, Admin, or Manager). Anyone who has the address can report the job up or down, so treat it as a secret.

A heartbeat has no target, so `url`, `host`, `port`, `keyword`, `domain`, and `accepted_statuscodes` are refused, as are an `ip_version` other than `null` and an `ssl_expiration` or `domain_expiration` other than `0`. The settings other checks use for timing are refused with a pointer to the heartbeat's own:

| Field | Refused with |
| --- | --- |
| `interval` | "A heartbeat's interval is its period: send period (seconds) instead." |
| `timeout` | "timeout is not for heartbeats: nothing is requested." |
| `maxretries` | "maxretries is not for heartbeats: grace is how long to wait before it is down." |

`PUT /api/v1/servers/:id` changes a heartbeat's `period`, `grace`, `name`, `tags`, and `active` (pause and resume). Its address never changes.
