Ping Clock REST API
Create and manage heartbeat checks from your own scripts. JSON over HTTPS. The API starts on Pro.
Authentication
Create a key in Settings. It is shown once; we store only a hash. Send it as a bearer token:
curl -H "Authorization: Bearer pngc_..." https://www.pingclock.com/api/v1/monitors
Revoke a key in Settings at any time. Keys act on the account they were created in.
Rate limits
Keep to 60 requests per minute per key. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. Over the limit you may get 429 with Retry-After; back off and retry.
Endpoints
| Method | Path | What it does |
|---|---|---|
| GET | /api/v1/monitors | List checks. |
| POST | /api/v1/monitors | Add a check. Period body {"name": "Nightly backup", "schedule": {"kind": "period", "every_seconds": 86400, "grace_seconds": 1800}}. Cron body {"kind":"cron","expression":"*/5 * * * *","timezone":"Europe/Berlin","grace_seconds":300} nested under schedule, so the full object is {"name": "Nightly backup", "schedule": {"kind":"cron","expression":"*/5 * * * *","timezone":"Europe/Berlin","grace_seconds":300}}. grace_seconds and every_seconds are integers, stored as seconds, not rounded to minutes. Returns 201, 402 over the plan limit, or 409 if the name exists. |
| GET | /api/v1/monitors/{id} | One check. |
| GET | /api/v1/monitors/{id}/checks | The last ping events. |
| POST | /api/v1/monitors/{id}/check | Evaluate due state now. |
| DELETE | /api/v1/monitors/{id} | Remove a check. Returns 204. |
Check object
{
"id": "...",
"name": "Nightly backup",
"hostname": "Nightly backup",
"paused": false,
"status": "new" | "ok" | "problem" | "paused",
"heartbeat_status": "new" | "up" | "down" | "late" | "paused",
"ping_url": "https://www.pingclock.com/ping/<token>",
"ping_token": "...",
"email_address": "abcd1234@ping.pingclock.com",
"schedule": { "kind": "cron", "expression": "*/5 * * * *", "timezone": "Europe/Berlin", "grace_seconds": 300 },
"last_ping_at": null,
"next_expected_at": null,
"last_duration_ms": null,
"created_at": "..."
}
hostname still holds the check name. A new check stays new until the first ping. last_duration_ms is set on Pro and Business when a start ping is consumed by the next success, fail or exit-code ping.
Errors
Errors return {"error": "code", "message": "..."} with codes unauthorized, invalid_json, invalid_check, plan_limit, plan_required, duplicate, not_found and rate_limited.
Signed webhooks
Add a webhook URL in Settings to receive alerts as JSON. Each POST has X-PingClock-Event and X-PingClock-Signature: t=<unix seconds>,v1=<hex>, where v1 is HMAC-SHA256 of t + "." + raw body with your signing secret. Reject requests older than five minutes.
Ping URLs
Public ping ingest is not the REST API. Use /ping/<token>, /start, /fail and /<exit-code>. Those stay cheap and do not need an API key.
Inbound email
Resend email.received webhooks POST to /api/inbound/email. Set RESEND_INBOUND_WEBHOOK_SECRET so we verify svix-id, svix-timestamp and svix-signature. Unsigned requests are rejected when that secret is set. Recipients such as Ping <token@ping.pingclock.com> are accepted. The webhook body does not include the message text; we fetch it from Resend only when the plan stores a log tail.