Ping Clock Ping Clock

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

MethodPathWhat it does
GET/api/v1/monitorsList checks.
POST/api/v1/monitorsAdd 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}/checksThe last ping events.
POST/api/v1/monitors/{id}/checkEvaluate 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.