# API

The Pulsefast REST API, with authentication, errors, rate limits, every endpoint and an OpenAPI spec.


The API speaks JSON over HTTPS. Everything here is also described in an **[OpenAPI 3.1 spec](/openapi.json)**, ready for Postman, Insomnia or a generated client.

## Authentication

The account owner creates keys under **Account → API keys**. Send one with every request:

```sh
curl https://pulsefast.io/api/v1/monitors -H "Authorization: Bearer $PULSEFAST_API_KEY"
```

A key works for the whole account. It's shown once, so store it in your secret manager. Delete it from the same page to revoke it.

## Errors and limits

Errors come back as `{"error": "…"}`, with a message meant for people:

| Status | Meaning |
|---|---|
| `400` | Malformed JSON, or a field the API doesn't know (typos are caught, not ignored). |
| `401` | Missing or wrong key. |
| `402` | Your plan's limit is reached, e.g. its number of monitors. |
| `404` | Doesn't exist, or isn't yours. |
| `422` | A value isn't valid; the message says which. |
| `429` | Over 120 requests a minute for the account. Wait for the `Retry-After` seconds. |

## Endpoints

| Method and path | What it does |
|---|---|
| `GET /api/v1/monitors` | List monitors, with status and 24-hour uptime |
| `POST /api/v1/monitors` | Create a monitor |
| `GET /api/v1/monitors/{id}` | Get one |
| `PATCH /api/v1/monitors/{id}` | Change some fields |
| `DELETE /api/v1/monitors/{id}` | Delete it and its history |
| `GET /api/v1/monitors/{id}/incidents` | Its incidents |
| `GET /api/v1/maintenance` | List maintenance windows |
| `POST /api/v1/maintenance` | Start or schedule one |
| `DELETE /api/v1/maintenance/{id}` | End or cancel one |
| `POST /api/v1/deploys` | Record a deploy |
| `GET /api/v1/deploys` | Recent deploys |
| `GET /api/v1/channels` | Alert channel IDs |
| `GET /api/v1/status-pages` | Status pages |

Endpoints your systems call without an API key are covered elsewhere:
- **Heartbeats** (`/hb/{token}`): see [Monitor cron jobs](/docs/cron-jobs).
- **Alert intake** (`/in/{token}`): see [Alert sources](/docs/alert-sources).
- **Agent callbacks** (`/v1/dispatches/{id}/status`): see [Agent on-call](/docs/agents).

All of them are in the OpenAPI spec.

## Monitors

### List

`GET /api/v1/monitors`

```json
{
  "monitors": [
    {
      "id": 12,
      "name": "API",
      "type": "http",
      "url": "https://api.example.com/health",
      "interval_seconds": 30,
      "timeout_seconds": 10,
      "retries": 1,
      "method": "GET",
      "header_names": ["Authorization"],
      "keyword": "ok",
      "ssl_warn_days": 14,
      "remind_seconds": 0,
      "paused": false,
      "channel_ids": [3, 4],
      "status": "up",
      "uptime_24h": 0.9998,
      "created_at": "2026-10-01T09:00:00Z"
    }
  ]
}
```

`status` is `up`, `down`, `pending`, `unknown`, `paused` or `suspended` (over your plan's limit). Header values are never returned. Heartbeat monitors have `heartbeat_url` and `grace_seconds` instead of `url`.

### Get

`GET /api/v1/monitors/{id}`

### Create

`POST /api/v1/monitors`

```sh
curl -X POST https://pulsefast.io/api/v1/monitors \
  -H "Authorization: Bearer $PULSEFAST_API_KEY" \
  -d '{
    "name": "Checkout API",
    "url": "https://api.example.com/v1/health",
    "interval_seconds": 30,
    "method": "POST",
    "headers": [{"name": "Authorization", "value": "Bearer …"}, {"name": "Content-Type", "value": "application/json"}],
    "body": "{\"ping\": true}",
    "keyword": "\"status\":\"error\"",
    "keyword_absent": true,
    "channel_ids": [3]
  }'
```

| Field | Notes |
|---|---|
| `type` | `http` (default), `port` or `heartbeat`. Can't be changed later. |
| `name` | Required, up to 80 characters. |
| `url` | `http` monitors: a full URL. `port` monitors: `host:port`. |
| `interval_seconds` | `http`/`port`: 5, 10, 30, 60, 120, 300, 600, 900, 1800 or 3600, no faster than your plan. `heartbeat`: 60, 300, 900, 1800, 3600, 21600, 43200, 86400 or 604800. |
| `timeout_seconds` | 1–30. |
| `grace_seconds` | `heartbeat`: 60, 300, 900, 1800, 3600 or 21600. |
| `retries` | 0–5. |
| `method`, `headers`, `body` | `http` only. Body up to 16 KB, not with GET or HEAD. |
| `keyword`, `keyword_absent` | `http` only. With `keyword_absent: true`, the keyword must not appear. |
| `expect_status` | List of status codes; empty means any 2xx/3xx. |
| `ssl_warn_days` | 0–90; 0 turns it off. |
| `remind_seconds` | 0 (alert once), 300, 600, 900, 1800, 3600, 7200 or 21600. |
| `channel_ids` | Channels to alert, from `GET /api/v1/channels`. |
| `paused` | `true` to stop checks and alerts. |

Returns `201` with the monitor. Fields you leave out get your plan's defaults: its fastest interval, a timeout, and retries.

The same request from code:

```js
// Node 18+ or the browser
const res = await fetch("https://pulsefast.io/api/v1/monitors", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.PULSEFAST_API_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Checkout API", url: "https://api.example.com/v1/health", interval_seconds: 30 }),
});
if (!res.ok) throw new Error((await res.json()).error);
const monitor = await res.json();
```

```python
import os, requests

res = requests.post(
    "https://pulsefast.io/api/v1/monitors",
    headers={"Authorization": f"Bearer {os.environ['PULSEFAST_API_KEY']}"},
    json={"name": "Checkout API", "url": "https://api.example.com/v1/health", "interval_seconds": 30},
)
res.raise_for_status()
monitor = res.json()
```

### Update

`PATCH /api/v1/monitors/{id}` changes only the fields you send. Sending `"headers": []` removes saved headers; leaving `headers` out keeps them.

```sh
curl -X PATCH https://pulsefast.io/api/v1/monitors/12 \
  -H "Authorization: Bearer $PULSEFAST_API_KEY" -d '{"paused": true}'
```

### Delete

`DELETE /api/v1/monitors/{id}` returns `204`. The monitor's history is deleted too.

### Incidents

`GET /api/v1/monitors/{id}/incidents?limit=50` (up to 500), newest first:

```json
{"incidents": [{"id": 345, "started_at": "2026-10-12T09:40:31Z", "resolved_at": "2026-10-12T09:43:02Z", "cause": "HTTP 502"}]}
```

`resolved_at` is `null` while the incident is ongoing.

## Maintenance windows

### Start one

`POST /api/v1/maintenance`

| Field | Notes |
|---|---|
| `title` | Required, shown on status pages. |
| `duration_minutes` | 1–1440. |
| `start` | RFC 3339 time; defaults to now. |
| `monitor_ids` | Defaults to every monitor. |
| `weekly` | Repeat every week. |
| `time_zone` | IANA name for weekly windows, e.g. `Europe/Lisbon`. Defaults to UTC. |

A deploy step in CI:

```sh
curl -fsS -X POST https://pulsefast.io/api/v1/maintenance \
  -H "Authorization: Bearer $PULSEFAST_API_KEY" \
  -d '{"title": "Deploy '"$GIT_SHA"'", "duration_minutes": 10}'
```

### List and delete

`GET /api/v1/maintenance` lists windows; `DELETE /api/v1/maintenance/{id}` removes one (end it early).

## Deploy markers

Tell Pulsefast when you deploy, so incidents show what changed just before they started. Add one call at the end of your deploy step:

```sh
curl -fsS -X POST https://pulsefast.io/api/v1/deploys \
  -H "Authorization: Bearer $PULSEFAST_API_KEY" \
  -d '{"service": "checkout", "version": "'"$GIT_SHA"'", "environment": "production"}'
```

| Field | Notes |
|---|---|
| `version` | Required: commit SHA, tag or build number. |
| `service` | Optional. Incidents for that service show its deploys; deploys without a service show on every incident. |
| `environment` | Defaults to `production`. |
| `url`, `description` | Optional: a link to the commit or release, and a note. |
| `at` | RFC 3339 time; defaults to now. |

`GET /api/v1/deploys?service=checkout` lists the last 30 days.

## Channels and status pages

`GET /api/v1/channels` returns `id`, `name` and `type` for each alert channel (settings and secrets aren't included). `GET /api/v1/status-pages` returns each page's `id`, `title`, `url`, `custom_domain` and `monitor_ids`.
