Cuckoo docs
Everything you need to monitor a scheduled job: one URL per job, called when it runs.
Concepts
A check expects a ping on a schedule: either a period ("every 6 hours") or a 5-field cron expression in a time zone. After each successful ping Cuckoo computes when the next one is due.
- new: never pinged. New checks never alert.
- up: the last ping was a success and the next one is not yet due.
- late: the next ping is overdue, but the grace period has not passed yet. No alert.
- down: the grace period passed without a ping, the job reported a failure, or a started run did not finish in time. Everyone is alerted, once.
- paused: no alerts. The next ping resumes the check.
When a down check receives a successful ping, it is up again and a recovery alert is sent. Cuckoo evaluates all checks every 15 seconds.
Ping URLs
| URL | Meaning |
|---|---|
/ping/<uuid> | Success. Computes the next due time. |
/ping/<uuid>/start | The job started. Measures the run duration until the next success or failure; the run must finish within the maximum run time (or the grace period). |
/ping/<uuid>/fail | Failure: the check is down immediately. |
/ping/<uuid>/<0-255> | Exit code: 0 is a success, anything else a failure. Use $?. |
All URLs accept GET, POST and HEAD. The answer is 200 OK, 404 for unknown UUIDs, or 429 above 300 pings per minute per check. With POST, the first 10 KB of the body are stored with the ping: send the tail of your job's output. Pings need no token and set no cookies, and the UUID is the secret.
Examples
crontab
30 2 * * * /opt/backup.sh && curl -fsS -m 10 --retry 5 -o /dev/null https://cuckoo.metastic.ch/ping/0c5f1a2e-7d3b-4f8e-9a61-2b4c6d8e0f13
Run time and exit code with output
#!/bin/sh
URL=https://cuckoo.metastic.ch/ping/0c5f1a2e-7d3b-4f8e-9a61-2b4c6d8e0f13
curl -fsS -m 10 --retry 5 -o /dev/null "$URL/start"
/opt/backup.sh > /tmp/backup.log 2>&1
curl -fsS -m 10 --retry 5 -o /dev/null --data-binary @/tmp/backup.log "$URL/$?"
systemd service
[Service]
Type=oneshot
ExecStartPre=-/usr/bin/curl -fsS -m 10 --retry 5 -o /dev/null https://cuckoo.metastic.ch/ping/0c5f1a2e-7d3b-4f8e-9a61-2b4c6d8e0f13/start
ExecStart=/opt/backup.sh
ExecStopPost=/bin/sh -c '/usr/bin/curl -fsS -m 10 --retry 5 -o /dev/null https://cuckoo.metastic.ch/ping/0c5f1a2e-7d3b-4f8e-9a61-2b4c6d8e0f13/$${EXIT_STATUS:-0}'
Kubernetes CronJob
command: ["/bin/sh", "-c", "./job && wget -q -O- https://cuckoo.metastic.ch/ping/0c5f1a2e-7d3b-4f8e-9a61-2b4c6d8e0f13 || wget -q -O- https://cuckoo.metastic.ch/ping/0c5f1a2e-7d3b-4f8e-9a61-2b4c6d8e0f13/fail"]
Sentry check-ins
Cuckoo speaks the Sentry envelope protocol for cron check-ins, so sentry_sdk.crons, sentry-go, @sentry/node and the other SDKs work unchanged. Copy your organization's DSN from Settings. It has the usual form:
https://<public_key>@cuckoo.metastic.ch/<project_id>
monitor_slugmaps to the check's slug. If no check has that slug and the check-in carries amonitor_config, the check is created. Later configs update the schedule.scheduleof typecrontabbecomes a cron check withtimezone; typeintervalbecomes a period check.checkin_margin(minutes) is the grace period,max_runtime(minutes) the maximum run time.- Status
in_progressis a start,oka success anderrora failure. The SDK'sdurationis used as the run time. - Check-ins for unknown slugs without a config are ignored. Other envelope items (errors, transactions) are accepted and dropped, so use this DSN only where you run jobs.
- Compression: gzip and deflate. sentry-python uses brotli if the
brotlipackage is installed. In that case set_experiments={"transport_compression_algo": "gzip"}.
import sentry_sdk
from sentry_sdk.crons import monitor
sentry_sdk.init(dsn="https://…@cuckoo.metastic.ch/1234567")
@monitor(monitor_slug="nightly-backup", monitor_config={
"schedule": {"type": "crontab", "value": "30 2 * * *"},
"timezone": "Europe/Zurich", "checkin_margin": 10, "max_runtime": 60,
})
def backup():
...
Alerts and webhooks
Alerts go to every member with alert emails on, and to each integration: Slack and Discord incoming webhooks, or a generic JSON webhook. Deliveries are retried with backoff for about 9 hours. A generic webhook receives:
POST /your/endpoint
Content-Type: application/json
X-Cuckoo-Event: check.down
X-Cuckoo-Delivery: 42
X-Cuckoo-Signature: t=1793900000,v1=5f2c…
{"event":"check.down","occurred_at":"2026-11-05T02:41:12Z","text":"Nightly backup is DOWN (…)",
"reason":"no ping within 10 minutes of the expected time","org":{"id":1,"name":"Acme"},
"check":{"id":7,"name":"Nightly backup","slug":"nightly-backup","status":"down",…}}
Verify the signature: compute HMAC-SHA256 of <t>.<raw body> with the signing secret shown when you created the webhook, compare it to v1 in constant time, and reject timestamps older than 5 minutes. Events: check.down and check.up.
REST API
Create a token in Settings and send it as Authorization: Bearer ck_…. 120 requests per minute per token. {id} is the numeric ID or the slug.
GET /api/v1/checks | List checks |
POST /api/v1/checks | Create a check. Idempotent by slug: an existing check is returned with "created": false |
GET /api/v1/checks/{id} | One check |
POST /api/v1/checks/{id}/pause | Pause |
POST /api/v1/checks/{id}/resume | Resume (the check is new until the next ping) |
GET /api/v1/checks/{id}/pings?limit=100 | Ping log, newest first |
curl -X POST https://cuckoo.metastic.ch/api/v1/checks \
-H "Authorization: Bearer $CUCKOO_TOKEN" -H "Content-Type: application/json" \
-d '{"name": "Nightly backup", "slug": "nightly-backup", "cron": "30 2 * * *", "tz": "Europe/Zurich", "grace": 600}'
Fields: name, slug, description, period (seconds) or cron + tz, grace and max_runtime (seconds). Plan limits answer 402 with the limit and an upgrade URL.
Badges
Turn on a public badge on a check's page to get an SVG URL like /badge/<random>.svg that shows the check name and status. Turn it off again to invalidate the URL.
Limits
- Shortest period and grace: 1 minute. Longest: 366 days.
- Ping body: 10 KB stored. Sentry envelopes: 1 MB, 100 KB per check-in.
- 300 pings per minute per check.
- Ping history per plan: Free 30 days / 100 entries per check, Team 1 year / 1,000, Business 2 years / 5,000.
Guides
Step-by-step setups for crontab, systemd timers, Kubernetes CronJobs, GitHub Actions schedules, restic backups, sentry-python and more than 30 other tools. Moving from another service? See the comparisons.
Questions? Use the feedback button or write to hello@metastic.ch.