Cuckoo

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

URLMeaning
/ping/<uuid>Success. Computes the next due time.
/ping/<uuid>/startThe 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>/failFailure: 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_slug maps to the check's slug. If no check has that slug and the check-in carries a monitor_config, the check is created. Later configs update the schedule.
  • schedule of type crontab becomes a cron check with timezone; type interval becomes a period check. checkin_margin (minutes) is the grace period, max_runtime (minutes) the maximum run time.
  • Status in_progress is a start, ok a success and error a failure. The SDK's duration is 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 brotli package 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/checksList checks
POST /api/v1/checksCreate 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}/pausePause
POST /api/v1/checks/{id}/resumeResume (the check is new until the next ping)
GET /api/v1/checks/{id}/pings?limit=100Ping 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.

Send feedback

Ideas, bugs, questions: we read every message. Or write to hello@metastic.ch.