Provision monitors from Terraform or CI, and read state back into your own dashboard.
https://api.whatping.com/v1
monitor-site.whatping.com/v1 answers identically and is not going away — it is the same
origin under a second name. Anything already integrated against it keeps working.
Authentication
Create a key in workspace settings. It is shown once and stored hashed, so it cannot be recovered — only rotated.
curl https://api.whatping.com/v1/me \
-H "Authorization: Bearer sk_your_key_here"
Keys carry a scope. A read key can call every GET; a write key can call everything.
There is no way to widen a key’s scope after issue — create a new one.
Every rejection — unknown key, revoked key, expired key — returns the same 401. That is
deliberate: distinguishing them would let someone probe which keys once existed.
Conventions
Errors always take one shape:
{ "error": { "code": "invalid_request", "message": "Interval must be between 20 and 86400 seconds" } }
422 carries a field when one input is at fault. Validation is the same code the
dashboard runs — if the interface would refuse it, the API refuses it identically.
Pagination is by cursor, never offset:
curl "https://api.whatping.com/v1/monitors?limit=50" -H "Authorization: Bearer $KEY"
# -> { "data": [...], "next_cursor": "..." }
curl "https://api.whatping.com/v1/monitors?cursor=..." -H "Authorization: Bearer $KEY"
next_cursor is null on the last page. limit is 1–100, default 25.
Rate limits are 600 reads and 60 writes a minute, per key, in continuously refilling buckets — so there is no window edge to burst across. Every response carries them:
ratelimit-limit: 600
ratelimit-remaining: 587
ratelimit-policy: 600;w=60
A 429 includes retry-after in seconds.
Idempotency — send Idempotency-Key on any POST and a retry returns the original
response with idempotent-replay: true instead of creating a second monitor. Reusing a key
with a different body is a 409. Records are kept 24 hours.
curl -X POST https://api.whatping.com/v1/monitors \
-H "Authorization: Bearer $KEY" \
-H "Idempotency-Key: deploy-$(git rev-parse --short HEAD)" \
-H "content-type: application/json" \
-d '{"name":"api","type":"http","url":"https://api.example.com/health"}'
That pattern is the point of the header: a pipeline that reruns does not accumulate monitors.
Field names are snake_case, and an unknown field is an error rather than being ignored —
a typo’d intervall_sec that silently vanished would leave you believing you set something
you did not.
Endpoints
| Method | Path | Scope |
|---|---|---|
| GET | /v1/me |
read |
| GET | /v1/monitors |
read |
| POST | /v1/monitors |
write |
| GET | /v1/monitors/{id} |
read |
| PATCH | /v1/monitors/{id} |
write |
| DELETE | /v1/monitors/{id} |
write |
| POST | /v1/monitors/{id}/pause |
write |
| POST | /v1/monitors/{id}/resume |
write |
| POST | /v1/monitors/{id}/rotate-token |
write |
| GET | /v1/monitors/{id}/results |
read |
| GET | /v1/monitors/{id}/channels |
read |
| PUT | /v1/monitors/{id}/channels/{channelId} |
write |
| DELETE | /v1/monitors/{id}/channels/{channelId} |
write |
| GET | /v1/incidents |
read |
| GET | /v1/channels |
read |
Creating a monitor
The type decides which other fields apply. Full field reference is on each monitor type’s
page under monitor types.
# HTTP with a status range and a body assertion
curl -X POST https://api.whatping.com/v1/monitors \
-H "Authorization: Bearer $KEY" -H "content-type: application/json" \
-d '{
"name": "checkout",
"type": "http",
"url": "https://shop.example.com/health",
"accepted_status": "200-299",
"expected_keyword": "ready",
"interval_sec": 60,
"repeat_every_min": 30
}'
# ICMP
-d '{"name":"gateway","type":"icmp","host":"gw.example.com","packet_count":4,"loss_threshold_pct":25}'
# UDP against a resolver
-d '{"name":"dns","type":"udp","host":"1.1.1.1","port":53,"udp_payload":"dns","dns_query_name":"example.com"}'
# gRPC health
-d '{"name":"svc","type":"grpc","host":"svc.example.com","port":50051,"grpc_service":"my.Service","tls":true}'
# SMTP with STARTTLS
-d '{"name":"mx","type":"smtp","host":"mx.example.com","port":587,"starttls":true}'
# Heartbeat — the response carries `push_token` exactly once
-d '{"name":"nightly-backup","type":"push","push_expected_interval_sec":86400,"push_grace_sec":3600}'
type is immutable. To change it, delete the monitor and create a new one.
Reading state
# Everything currently down
curl "https://api.whatping.com/v1/monitors?state=down" -H "Authorization: Bearer $KEY"
# Open incidents
curl "https://api.whatping.com/v1/incidents?status=open" -H "Authorization: Bearer $KEY"
# Check results since a timestamp (epoch ms)
curl "https://api.whatping.com/v1/monitors/$ID/results?since=1785000000000" \
-H "Authorization: Bearer $KEY"
Results responses include retention_days, so an integration knows the horizon rather than
discovering it when a window comes back short. It is 7.
Alert channels
Create, list, attach and delete notification channels — webhook, ntfy, Telegram or email:
# create (type-specific fields: webhook{url} · ntfy{topic_url} · telegram{bot_token,chat_id} · email{to})
curl -X POST https://api.whatping.com/v1/channels -H "Authorization: Bearer $KEY" \
-H "content-type: application/json" \
-d '{"name":"ops","type":"webhook","url":"https://hooks.slack.com/services/T/B/XXXX"}'
curl https://api.whatping.com/v1/channels -H "Authorization: Bearer $KEY" # list
curl -X PUT "https://api.whatping.com/v1/monitors/$ID/channels/$CHANNEL" -H "Authorization: Bearer $KEY" # attach
curl -X DELETE "https://api.whatping.com/v1/monitors/$ID/channels/$CHANNEL" -H "Authorization: Bearer $KEY" # detach
curl -X DELETE "https://api.whatping.com/v1/channels/$CHANNEL" -H "Authorization: Bearer $KEY" # delete
Destinations come back redacted — https://hooks.slack.com/…, never the full URL, and never
a Telegram bot token. The API cannot be used to read back a credential you stored, and a webhook
URL that resolves to a private address is refused. For fleet-as-code, keep channels and monitors
in one file and converge them — see the whatping-sync tool.
What the API does not do
- No CORS. Server-side and CI use only.
- Workspaces, members and billing are dashboard-only.
- A monitor in another workspace returns
404, not403— telling you an ID exists elsewhere would itself be information about another account.
The OpenAPI document
/openapi.json is OpenAPI 3.1, and it is generated from the route table
rather than maintained beside it — so it cannot describe an endpoint that does not exist, and
it cannot omit one that does. Point a generator at it:
curl -O https://www.whatping.com/openapi.json
openapi-generator-cli generate -i openapi.json -g go -o ./whatping
Recipes
Provision from CI, idempotently. Key the header on the commit so a rerun of the same pipeline is a no-op rather than a duplicate:
curl -X POST https://api.whatping.com/v1/monitors \
-H "Authorization: Bearer $KEY" \
-H "Idempotency-Key: $SERVICE-$(git rev-parse --short HEAD)" \
-H "content-type: application/json" \
-d "{\"name\":\"$SERVICE\",\"type\":\"http\",\"url\":\"$HEALTH_URL\"}"
Mirror open incidents onto an internal board. Use a read key — it can call every GET
and nothing else, so a compromised wall display cannot delete a monitor:
curl -s "https://api.whatping.com/v1/incidents?status=open" \
-H "Authorization: Bearer $READ_KEY" \
| jq -r '.data[] | [.monitor_id, .opened_at, .reason] | @tsv'
An incident carries monitor_id, not a name, so a board that shows names joins the two:
KEY_HDR="Authorization: Bearer $READ_KEY"
names=$(curl -s https://api.whatping.com/v1/monitors -H "$KEY_HDR")
curl -s "https://api.whatping.com/v1/incidents?status=open" -H "$KEY_HDR" | jq -r --argjson m "$names" '
.data[] as $i
| ($m.data[] | select(.id == $i.monitor_id) | .name) as $name
| [$name, $i.opened_at, $i.reason] | @tsv'
Each incident also carries external_check — agreed, disagreed or unavailable — the
second opinion verdict, which belongs on the board beside the
reason.
Pause everything for a deploy. Maintenance windows do not exist yet; this is how you work
around that today, and it is honest about what it is:
for id in $(curl -s https://api.whatping.com/v1/monitors \
-H "Authorization: Bearer $KEY" | jq -r '.data[].id'); do
curl -X POST "https://api.whatping.com/v1/monitors/$id/pause" -H "Authorization: Bearer $KEY"
done
Export results before they age out. Retention is 7 days and every results response carries
retention_days, so a job that runs daily never silently loses a window.
Related
- API overview — why it is shaped this way
- OpenAPI 3.1 spec
- Webhook payload — the push side
- Heartbeat ping endpoint
- Limits