# REST API

The API manages one project's checks, incidents, and alert channels. The machine-readable description is the [OpenAPI 3.1 document](https://checkpulse.foo/api/v1/openapi.json). AI agents can use the same operations over [MCP](/docs/agents).

## Authentication

Create a key under **Project settings → API keys** and send it as a bearer token:

```sh
curl https://checkpulse.foo/api/v1 -H "Authorization: Bearer cpk_…"
```

`X-Api-Key: cpk_…` also works. Keys are read-only or read-write and belong to a single project. `GET /api/v1` describes the project, the key's scope, limits, and usage, and is a good first call.

## Requests

- Send JSON with `Content-Type: application/json`. Unknown fields are rejected, so typos surface as errors.
- `{check}` in a path is a numeric ID or a slug.
- Durations (`period`, `grace`, `duration`) accept seconds or strings such as `"90s"`, `"5m"`, `"1h"`, `"1d"`.
- `tags` and `failure_keywords` accept a string or an array of strings.
- `channels` accepts `"*"` (every channel) or an array of channel IDs.
- Times are RFC 3339 in UTC.

## Errors

Errors use [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) problem details with `Content-Type: application/problem+json`:

```json
{
  "type": "about:blank",
  "title": "Bad Request",
  "status": 400,
  "code": "invalid_period",
  "field": "period",
  "detail": "Interval must be at least 1m on this account."
}
```

`code` is stable for programs; `detail` and, when present, `hint` explain what to do. Common codes:

| Code | Status | Meaning |
|---|---|---|
| `unauthorized` | 401 | Missing, invalid, expired, or revoked key. |
| `read_only_key` | 403 | The key cannot make changes. |
| `limit_reached` | 403 | An account limit (checks, channels, keys) is reached. |
| `check_not_found`, `not_found` | 404 | No such resource in this project. |
| `slug_taken` | 409 | Another check uses that slug. |
| `invalid_<field>`, `invalid_json` | 400 | The request is not valid; see `field` and `detail`. |
| `idempotency_key_reused` | 422 | The Idempotency-Key was used for a different request. |
| `rate_limited` | 429 | Too many requests; wait for `Retry-After` seconds. |

## Idempotency

Send `Idempotency-Key: <unique value>` with any write. Retrying with the same key within 24 hours returns the stored response, including error responses with their `application/problem+json` type, marked `Idempotent-Replayed: true` instead of repeating the change. A retry that arrives while the first request is still running gets `409 request_in_progress`; a request that never finished releases its key after two minutes. `PUT /api/v1/checks/{slug}` is idempotent by design.

## Rate limits

Each key may make 60 requests a minute on free accounts. Responses include `RateLimit-Limit`, `RateLimit-Remaining`, and `RateLimit-Reset`; a 429 includes `Retry-After`.

## Pagination

Lists of pings and audit entries return newest first. Pass the `next_before` value (pings) or the last entry's `id` (audit) as `before` to fetch older items.

## Endpoints

| Method and path | Scope | Purpose |
|---|---|---|
| `GET /api/v1` | read | Project, key, counts, limits, usage, and links. |
| `GET /api/v1/checks` | read | List checks. Filters: `status`, `tag`, `slug`, `q`. |
| `POST /api/v1/checks` | write | Create a check. |
| `GET /api/v1/checks/{check}` | read | Get a check. |
| `PUT /api/v1/checks/{slug}` | write | Create or update by slug (201 created, 200 updated). |
| `PATCH /api/v1/checks/{check}` | write | Change some fields. |
| `DELETE /api/v1/checks/{check}` | write | Delete; restorable for 7 days. |
| `POST /api/v1/checks/{id}/restore` | write | Restore a deleted check. |
| `POST /api/v1/checks/{check}/pause` | write | Pause. |
| `POST /api/v1/checks/{check}/resume` | write | Resume. |
| `POST /api/v1/checks/{check}/silence` | write | Hold alerts: `{"duration": "2h", "reason": "…"}`. |
| `DELETE /api/v1/checks/{check}/silence` | write | End a silence. |
| `POST /api/v1/checks/{check}/rotate-token` | write | Issue a new ping URL. |
| `GET /api/v1/checks/{check}/pings` | read | Recent pings. `limit`, `before`, `include_system`. |
| `GET /api/v1/incidents` | read | Incidents. `status` (open, closed, all), `check`. |
| `GET /api/v1/incidents/{id}` | read | One incident with notes. |
| `POST /api/v1/incidents/{id}/acknowledge` | write | Acknowledge, optionally with `{"note": "…"}`. |
| `POST /api/v1/incidents/{id}/notes` | write | Add `{"note": "…"}`. |
| `GET /api/v1/channels` | read | Alert channels. |
| `POST /api/v1/channels` | write | Add a channel (see [Alert channels](/docs/integrations)). |
| `PATCH /api/v1/channels/{id}` | write | `enabled`, `notify_down`, `notify_up`, `repeat_minutes`. |
| `DELETE /api/v1/channels/{id}` | write | Remove a channel. |
| `POST /api/v1/channels/{id}/test` | write | Send a test alert. |
| `GET /api/v1/audit` | read | Recent changes and who made them. |
| `GET /api/v1/badges` | read | Status badge URLs. |
| `GET /api/v1/metrics` | read | Prometheus metrics. |
| `GET /api/v1/openapi.json` | none | The OpenAPI document. |

## Check fields

| Field | Notes |
|---|---|
| `name`, `slug` | The slug is unique in the project, uses lowercase letters, digits, and hyphens, and must contain a letter. |
| `description`, `runbook` | The runbook is Markdown included in alerts; write it for whoever responds, people or agents. |
| `tags` | Used for filtering, badges, and metrics. |
| `kind` | `interval` (with `period`) or `cron` (with `cron` and `timezone`). |
| `grace` | See [Schedules](/docs/schedules). |
| `methods` | `any` or `post`. |
| `manual_resume` | When true, pings do not resume a paused check. |
| `failure_keywords` | Output containing any of these fails a success ping. |
| `channels` | Channels that receive this check's alerts. |

Responses add `status`, `last_ping`, `next_due`, `started_at`, `last_duration_seconds`, `silenced_until`, `open_incident`, and, for read-write keys only, the secret `ping_url` and `slug_ping_url`.

## Examples

Create or update a cron check:

```sh
curl -X PUT https://checkpulse.foo/api/v1/checks/db-backup \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"name": "DB backup", "kind": "cron", "cron": "0 3 * * *", "timezone": "Europe/Paris", "grace": "30m", "runbook": "Check free disk space on db-1, then rerun."}'
```

List checks that are down:

```sh
curl "https://checkpulse.foo/api/v1/checks?status=down" -H "Authorization: Bearer $KEY"
```

Acknowledge an incident:

```sh
curl -X POST https://checkpulse.foo/api/v1/incidents/42/acknowledge \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"note": "Disk full on db-1; clearing old WAL files."}'
```
