REST API (v1)
A small, key-authed REST API for managing monitors. It exists for provisioning fleets and wiring PulseWatch into your own tooling. Base URL: https://pulsewatcher.up.railway.app/api/v1.
Authentication
Create an API key in Settings and send it as a bearer token. Keys are shown once and stored hashed. Read endpoints work on any plan; write endpoints require Pro or Business (free keys are read-only).
Authorization: Bearer pw_live_…
Listing monitors with a key
curl -fsS "https://pulsewatcher.up.railway.app/api/v1/monitors" \ -H "Authorization: Bearer $PW_API_KEY"
Endpoints
| GET | /api/v1/monitors | List your monitors. Query: state, tag.[read] |
| POST | /api/v1/monitors | Create a monitor. Body: { type, ... }.[write] |
| GET | /api/v1/monitors/:id | Fetch one monitor.[read] |
| PATCH | /api/v1/monitors/:id | Update fields.[write] |
| DELETE | /api/v1/monitors/:id | Soft-delete a monitor.[write] |
| POST | /api/v1/monitors/:id/pause | Pause a monitor.[write] |
| POST | /api/v1/monitors/:id/resume | Resume (state resets to new).[write] |
| GET | /api/v1/monitors/:id/pings | Recent pings. Query: limit (1–100).[read] |
| GET | /api/v1/monitors/:id/checks | Recent uptime checks. Query: limit (1–100).[read] |
Create a monitor
curl -fsS -X POST "https://pulsewatcher.up.railway.app/api/v1/monitors" \
-H "Authorization: Bearer pw_live_…" \
-H "Content-Type: application/json" \
-d '{
"type": "uptime",
"name": "Checkout API",
"url": "https://api.example.com/healthz",
"check_interval_seconds": 60,
"tags": ["payments", "prod"]
}'201 response: the full monitor row
{
"monitor": {
"id": "b0000000-0000-4000-a000-000000000003",
"user_id": "a0000000-0000-4000-a000-000000000001",
"type": "uptime",
"name": "Checkout API",
"state": "up",
"period_seconds": null,
"grace_seconds": null,
"last_ping_at": null,
"ping_token": "9f2c1e7a-0000-4000-a000-0000000000a4",
"url": "https://api.example.com/healthz",
"check_interval_seconds": 60,
"timeout_ms": 10000,
"expected_status_min": 200,
"expected_status_max": 299,
"last_checked_at": "2026-07-21T14:03:00.000Z",
"consecutive_failures": 0,
"on_status_page": true,
"created_at": "2026-07-01T09:00:00.000Z",
"deleted_at": null,
"alert_after_seconds": 0,
"repeat_interval_seconds": 0,
"down_since": null,
"last_alert_at": null,
"ssl_valid_to": "2026-09-30T00:00:00.000Z",
"ssl_checked_at": "2026-07-21T00:00:00.000Z",
"ssl_alerted_days": null,
"expect_text": null,
"forbid_text": null,
"degraded_threshold_ms": null,
"degraded": false,
"schedule_kind": "period",
"cron_expression": null,
"cron_timezone": null,
"next_expected_at": null,
"last_start_at": null,
"max_duration_seconds": null,
"tags": [
"payments",
"prod"
],
"show_latency_on_status": true,
"alert_on_new_error": true,
"escalate_after_consecutive": null,
"last_error_alert_at": null,
"autoprovisioned": false,
"autoprovision_slug": null,
"schedule_learning": false
}
}Heartbeat monitors send period_seconds and grace_seconds instead of url/check_interval_seconds. Pro-only fields (cron schedules, keyword checks, degraded thresholds) return 402 pro_feature on Free.
Reading pings & checks
GET /monitors/:id/pings → { pings: [...] }
{
"pings": [
{
"id": 84210,
"monitor_id": "b0000000-0000-4000-a000-000000000003",
"received_at": "2026-07-21T03:00:12.412Z",
"kind": "success",
"source_ip": "203.0.113.7",
"user_agent": "curl/8.6.0",
"duration_ms": 4120,
"metrics": {
"rows": 48210,
"errors": 0
},
"body_excerpt": "backup complete: 48210 rows in 4.1s",
"stderr_truncated": false
}
]
}GET /monitors/:id/checks → { checks: [...] }
{
"checks": [
{
"id": 553120,
"monitor_id": "b0000000-0000-4000-a000-000000000003",
"checked_at": "2026-07-21T14:03:00.000Z",
"ok": true,
"status_code": 200,
"latency_ms": 142,
"error": null
}
]
}Both accept ?limit= (1–100, default 25), newest first. The monitors list returns all your non-deleted monitors and is not paginated.
Errors & rate limits
Errors are JSON: { "error": "…", "code": "…" }. Requests are limited to 60 per minute per key; over the limit returns 429 with a Retry-After header.
| HTTP | code | When |
|---|---|---|
| 400 | — | Malformed body or invalid field (issues[] included). |
| 401 | — | Missing or invalid API key. |
| 402 | monitor_limit | Plan monitor cap reached. |
| 402 | min_interval | Check interval below your plan minimum. |
| 402 | pro_feature | A field requires the Pro or Business plan. |
| 402 | status_page_plan | Status pages require a paid plan. |
| 403 | — | Free key attempted a write (writes are Pro+). |
| 404 | — | Monitor not found (or not yours). |
| 422 | invalid | Rejected by a database constraint. |
| 429 | — | Rate limit exceeded (60 req/min per key). |
Quickstart: provision a fleet
The multi-site story: loop over your hosts and create one uptime monitor each, tagged so you can filter the whole fleet in the dashboard.
# provision the same uptime check across many sites
KEY="pw_live_…"
for host in $(cat sites.txt); do
curl -fsS -X POST "https://pulsewatcher.up.railway.app/api/v1/monitors" \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d "{\"type\":\"uptime\",\"name\":\"$host\",\"url\":\"https://$host/healthz\",\"check_interval_seconds\":60,\"tags\":[\"fleet\"]}"
done