Create and list monitors and notification channels programmatically — the same operations as the dashboard, scriptable from CI or an agent. Every write runs through the same validation and plan checks as the web app, so an API-created monitor behaves exactly like one made in the UI. API access is included from the Starter plan up.

Authentication

Create a personal access token under Account → API tokens (it's shown once). Send it as a bearer token on every request:

curl -H "Authorization: Bearer cmk_…" \
  https://cronheart.com/api/v1/monitors

A missing, invalid, expired, or revoked token returns 401. A token whose plan no longer includes API access returns 402. Requests are rate-limited per token; every response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset.

Create a monitor

POST /api/v1/monitors — returns 201 with a Location header and the monitor (including its ping_url).

curl -X POST https://cronheart.com/api/v1/monitors \
  -H "Authorization: Bearer cmk_…" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "nightly-backup",
    "schedule_kind": "interval",
    "schedule_expr": "3600",
    "tz": "UTC",
    "grace_seconds": 60,
    "channel_ids": []
  }'

schedule_kind is cron (a 5-field expression), interval (integer seconds, 30–31,622,400), or simple (e.g. "daily at 03:00"). channel_ids is optional — leave it empty to fan out to every verified channel in your project.

List & fetch monitors

GET /api/v1/monitors?limit=50&offset=0 returns { "data": [...], "total": N }. GET /api/v1/monitors/{uuid} returns a single monitor (or 404 if it isn't yours).

Create a channel

POST /api/v1/channels — the body is keyed by kind:

{"kind":"telegram","label":"ops","chat_id":"-100123456789"}
{"kind":"slack","label":"alerts","webhook_url":"https://hooks.slack.com/services/…"}
{"kind":"webhook","label":"my-hook","webhook_url":"https://example.com/hook","secret":"<16+ chars>"}
{"kind":"email","label":"on-call","address":"ops@example.com"}

Channels are created unverified. An email channel triggers a verification link to the address; the others verify on their first successful test send from the dashboard. Webhook URLs must be public HTTPS endpoints — loopback and private addresses are rejected. GET /api/v1/channels lists them (secrets are redacted).

Errors

Errors are application/problem+json (RFC 7807):

{"type":"about:blank","title":"Unprocessable Entity","status":422,
 "detail":"One or more fields are invalid.","errors":{"name":"This value is too short."}}
StatusWhen
400Body is not valid JSON.
401Missing / invalid / expired / revoked token.
402Your plan doesn't include API access.
403Monitor limit reached, or account email unverified.
404Resource not found (or not yours).
409That channel kind is temporarily disabled.
422Validation failed — see the errors map.
429Rate limit exceeded — see Retry-After.

Idempotency: a retried POST /api/v1/monitors creates a second monitor — the endpoint is not idempotent in v1. Guard retries on your side.

PHP integration

If you're on Symfony or Laravel, the cron-monitor/php-sdk wraps the ping endpoint with first-class scheduler integrations — composer require cron-monitor/php-sdk.