Create, manage, and inspect 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 account (shared
across all of your tokens, regardless of scope); every response carries
X-RateLimit-Limit, X-RateLimit-Remaining, and
X-RateLimit-Reset.
Project scope
A token can be scoped to a single project when you create it. A
scoped token reads and creates monitors only
within that project — a leaked token can't enumerate other projects' monitors. A
token with no scope resolves to your account's default project
(the first one). Fetching a monitor outside the token's scope returns
404, the same mask used for another account's monitors.
Channels are account-level (shared across all your
projects), so GET /api/v1/channels returns every channel on the
account regardless of the token's project scope.
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 lists the
account channels this monitor alerts; leave it empty and the monitor won't
notify anyone (there is no fan-out).
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).
Manage a monitor
Ping & alert history
GET /api/v1/monitors/{uuid}/pings is cursor-paginated:
{ "data": [...], "next_cursor": "…" } — pass
?cursor= (and optional ?limit=) to walk back; a
null cursor is the last page.
GET /api/v1/monitors/{uuid}/alerts is offset-paginated like the
monitor list.
Account & usage
GET /api/v1/account returns your plan, your
monitor_budget (used / limit / remaining), and your current
api_rate_limit standing.
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.
Webhook URLs must be public HTTPS endpoints — loopback
and private addresses are rejected. GET /api/v1/channels lists
them (secrets are redacted).
Manage a channel
OpenAPI
A machine-readable OpenAPI 3.1 description of every endpoint is served at
/api/v1/openapi.json
(no token required) — point your SDK generator or
openapi-aware client at it.
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."}}
Idempotency
Send an Idempotency-Key header on a
POST create and a retried request with the same key replays
the first response instead of creating a duplicate — safe to retry a
timed-out create from CI. Reusing a key with a different request body
returns 409; a replay carries Idempotency-Replayed:
true. Keys are remembered for at least 24 hours.
curl -X POST https://cronheart.com/api/v1/monitors \
-H "Authorization: Bearer cmk_…" \
-H "Idempotency-Key: 9f1c…-your-unique-key" \
-H "Content-Type: application/json" \
-d '{"name":"nightly","schedule_kind":"interval","schedule_expr":"3600"}'
PHP integration
If you're on Symfony or Laravel, the
cron-monitor/php-sdk package wraps the
ping endpoint with first-class scheduler integrations —
composer require cron-monitor/php-sdk
(source on GitHub).
It also ships a typed client for this REST API.