# Pinging

Jobs report to Checkpulse with plain HTTP requests. Pings need no API key: the URL itself is the secret, so keep it out of public logs and repositories, and rotate it if it leaks.

## Ping URLs

Every check has two URLs:

| Form | Example |
|---|---|
| Token URL | `https://checkpulse.foo/ping/<64-character token>` |
| Slug URL | `https://checkpulse.foo/ping/<project ping key>/<check slug>` |

Slug URLs use one key per project, which makes it easy to template many jobs. Find the ping key under **Project settings**. Rotating it changes every slug URL in the project; rotating a check's token changes only that check's token URL.

## Signals

Append a signal to either URL:

| Path | Meaning |
|---|---|
| (none) or `/0` | Success. The check is up and the next deadline starts now. |
| `/start` | The run started. If no finish arrives within the grace period, the check goes down. |
| `/fail` | Failure. The check goes down immediately. |
| `/1` … `/255` | Exit status. Non-zero fails the check and is recorded on the incident. |
| `/log` | Records a message without changing the status or the last-ping time. |

A start never hides an existing failure: only a success recovers a check that is down.

## Methods and output

Send `GET`, `HEAD`, or `POST`. To attach output, `POST` it in the body. Checkpulse keeps the first 4 KB and marks longer output as truncated; the ping still counts.

Set a check to **POST only** to ignore `GET` and `HEAD` requests from link previews and email scanners. Those requests receive HTTP 405.

**Failure keywords** turn a success ping into a failure when its output contains any keyword (case-insensitive), for example `ERROR, Traceback`.

## Run IDs

When runs can overlap, add `?rid=<id>` (letters, digits, `.`, `_`, `:`, `-`; up to 64 characters) to the start and finish pings of the same run. Checkpulse then pairs them to measure each run's duration, and finishing one run does not clear another run's deadline. [checkpulse-run](/docs/cli) does this automatically.

## Creating checks on first ping

Add `?create=1` to a slug URL to create the check if it does not exist yet:

```sh
curl -fsS -m 10 --retry 3 "https://checkpulse.foo/ping/$PING_KEY/nightly-backup?create=1"
```

The new check is named after its slug, expects a ping every day with a one-hour grace period, and sends alerts to every channel in the project. Adjust its schedule afterwards in the dashboard, the API, or MCP. Account check limits apply. Without `create=1`, unknown slugs return 404.

## Responses

| Status | Meaning |
|---|---|
| 200 | Recorded. |
| 201 | Recorded, and the check was created by `?create=1`. |
| 400 | Unknown signal or invalid run ID. |
| 403 | The account's check limit prevents creating the check. |
| 404 | Unknown URL, or the check is deleted. |
| 405 | The check only accepts POST. |
| 429 | More pings than the check allows per minute (10 on free accounts). Retry after a minute. |

## Pausing

Pausing a check stops alerts while pings are still recorded. By default the next ping resumes the check; turn on **keep paused until resumed by hand** to prevent that. A resumed check waits for its next ping like a new check, so it never reports a recovery that did not happen. For planned maintenance, [silences](/docs/incidents) are usually better because they end on their own.

## Examples

Shell, with the exit status:

```sh
URL="https://checkpulse.foo/ping/<token>"
curl -fsS -m 10 --retry 3 -o /dev/null "$URL/start"
/usr/local/bin/backup.sh > /tmp/backup.log 2>&1
status=$?
curl -fsS -m 10 --retry 3 -o /dev/null --data-binary @/tmp/backup.log "$URL/$status"
```

If the script uses `set -e`, capture the status explicitly as above so the failure is still reported.

Python:

```python
import urllib.request
urllib.request.urlopen("https://checkpulse.foo/ping/<token>", data=b"42 rows exported", timeout=10)
```

PowerShell:

```powershell
Invoke-RestMethod -Method Post -Uri "https://checkpulse.foo/ping/<token>/$LASTEXITCODE"
```
