illari

Docs

illari watches scheduled jobs. Your job makes one HTTP request each time it runs; if a request doesn't arrive on schedule (or, on Pro, arrives reporting a failure), you get alerted.

Point a coding agent at /llms-full.txt to wire illari into a repo in one pass. Index at /llms.txt. Managing monitors from code? See the Management API reference.

Ping URL
https://illari.dev/ping/<key>
Methods
GET, POST, or HEAD
Auth
the key in the URL is the credential; no headers
Alerts on
a missed check-in, and on Pro a failed, slow, or off-baseline run

Quickstart

About two minutes from zero to a working monitor.

  1. Create an account (email or GitHub, no card).
  2. Create a monitor: give it a name, and either a cron expression + timezone or nothing (an open-ended silence check).
  3. Copy its ping URL, shown on the monitor page right after you create it.
  4. Have the job call that URL when it finishes:
crontab
0 3 * * *  /usr/local/bin/backup.sh && curl -fsS -m 10 https://illari.dev/ping/YOUR_KEY

The && means the ping only fires if backup.sh exited 0. To check it end to end, run the curl once by hand. The monitor flips from Pending to Up within a second, and the check-in shows on the monitor page.

The ping URL

Every monitor has a unique HTTPS URL, https://illari.dev/ping/<key>. The 32-hex key is the identifier and the credential: no headers, no token. Anyone with the URL can check the monitor in, so keep it out of public repos and logs. If it leaks, hit Regenerate key on the monitor's page. The old URL stops working at once, and history is kept.

Request

  • GET, POST, or HEAD: all count as a ping. A request body is only read on POST (see run data).
  • Extra query parameters are ignored, so it's safe to append your own (e.g. ?host=$(hostname)) for your job's logs.
  • illari responds in well under a second. Still, set a client timeout (curl -m 10) so a network stall can't hang the job.

Responses

StatusMeaning
200Recorded. The monitor is Up and the next deadline is recomputed.
404Unknown key (deleted or mistyped monitor).
429More than 60 pings in a minute for one monitor; includes Retry-After: 60. A flood guard, not a real limit, and nothing on a sane schedule comes close.

Use curl -fsS: -f makes curl exit non-zero on an HTTP error, -s silences the progress meter, -S still prints errors. Add --retry 3 for transient network failures. A failed ping then shows up in the job's own logs and exit status.

Sending several pings within one expected window is harmless; each one just refreshes the deadline.

Status badge

Each monitor has an SVG status badge at /badge/<badge-key>.svg (up / late / down / paused). The badge key is separate from the ping key, so it's safe in a public README. Grab the Markdown from the monitor's page.

Integrations

Anything that can make an HTTP request works. The rule is the same everywhere: ping last, and only if the real work succeeded. A run that starts but exits non-zero should look silent, because that's what you want an alert for.

crontab
# ping only if backup.sh exited 0
0 3 * * *  /usr/local/bin/backup.sh && curl -fsS -m 10 --retry 3 https://illari.dev/ping/YOUR_KEY
bash wrapper
#!/bin/bash
set -euo pipefail          # abort (no ping) if any step fails

./run-nightly-etl.sh

curl -fsS -m 10 --retry 3 https://illari.dev/ping/YOUR_KEY
python
import subprocess, urllib.request

subprocess.run(["./run-etl.sh"], check=True)   # raises -> no ping
urllib.request.urlopen("https://illari.dev/ping/YOUR_KEY", timeout=10)
node
import { execFileSync } from "node:child_process";

execFileSync("./run-etl.sh", { stdio: "inherit" }); // throws -> no ping
await fetch("https://illari.dev/ping/YOUR_KEY");
systemd .service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
# runs only if ExecStart succeeded
ExecStartPost=/usr/bin/curl -fsS -m 10 https://illari.dev/ping/YOUR_KEY
Kubernetes CronJob
spec:
  schedule: "*/15 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: job
              image: your/image
              command: ["/bin/sh", "-c"]
              args:
                - ./run.sh && curl -fsS -m 10 https://illari.dev/ping/YOUR_KEY
GitHub Actions
# last step of a scheduled workflow
- name: Ping illari
  run: curl -fsS -m 10 https://illari.dev/ping/${{ secrets.ILLARI_PING_KEY }}
Windows Task Scheduler
REM run.bat (Windows 10+ ships curl)
@echo off
call backup.bat || exit /b 1
curl -fsS -m 10 https://illari.dev/ping/YOUR_KEY

The CLI

illari run wraps a command so you don't have to edit the script: it sends the start ping, runs the command with output passing straight through, then sends the completion ping with the exit code and the tail of the output. It exits with the command's own code, and a failed ping is a warning, never fatal.

install
npm install -g illari      # or run ad-hoc with npx
crontab
0 3 * * *  illari run --key YOUR_KEY -- /usr/local/bin/backup.sh

Pass the key with --key or the ILLARI_KEY environment variable. Use --url for a full ping URL, --tail to change how much output is captured (default ~10 KB), and --no-start to skip the start ping. illari ping --key YOUR_KEY is a bare one-shot check-in for a plain crontab line.

Node 18 or newer, no dependencies. Source and issues: github.com/illari-hq/illari-cli.

Reporting run data

Add a suffix to the ping URL to say how the run went. A POST body is captured alongside it (any content type, stored as text, truncated to ~10 KB; pipe in tail -c 8000 logfile rather than a whole log).

  • POST /ping/<key>/start: the run began. Recorded only: it does not satisfy the schedule on its own, so you still need a completion ping. The next completion records the elapsed time as the run duration. A start with no completion within 24 hours is discarded. Set a max runtime on the monitor to be alerted when a run has been going that long with no completion (a hung run).
  • POST /ping/<key>/<exit_code>: finished with that code. 0 is success; anything else is a failed run. Use the real shell code (0–255).
  • POST /ping/<key>/fail: a failed run with no specific code.
bash: report the outcome
KEY=YOUR_KEY
curl -fsS -m 10 https://illari.dev/ping/$KEY/start

if ./run-etl.sh > /tmp/etl.log 2>&1; then
  code=0
else
  code=$?
fi

tail -c 8000 /tmp/etl.log | curl -fsS -m 10 --data-binary @- \
  "https://illari.dev/ping/$KEY/$code"

A failed or slow completion still counts as a check-in (the job did run), so the monitor stays Up. The failure shows as a red row in the check-in list and, on Pro, fires an alert:

  • Failed run: a non-zero exit or /fail. The alert includes the exit code and the captured output.
  • Ran slow: a completion far slower than the monitor's recent baseline (needs at least five prior timed runs; only flags slower, never faster).
  • Metric rule: a number the job reported crossed a threshold you set, or drifted far from its baseline. See below.

Recording exit code / duration / output is available on every plan; the run-history view of it and the failed / slow / metric alerts are Pro.

Custom metrics

Send numbers alongside the completion ping as form fields or query parameters. Values must parse as numbers; up to 20 per ping.

report row and byte counts
rows=$(psql "$DATABASE_URL" -tAc "select count(*) from orders")
bytes=$(stat -c%s "$DEST")

curl -fsS -m 10 -d "rows=$rows" -d "bytes=$bytes" \
  "https://illari.dev/ping/YOUR_KEY/0"

On the monitor's Metric rules section (Pro), add a rule of one of two kinds:

  • Threshold: a fixed condition, like rows is less than 1 or errors is greater than 0. Fires the moment a ping reports a matching value, no warm-up.
  • Baseline: the value moved far off its own recent norm, either up (cost_usd spikes), down (bytes drops), or both. Needs about eight prior runs of that metric to learn the baseline, then flags a run that is several standard deviations and at least 1.5× off the mean.

Either way the alert names the metric, the value, and the rule, and the captured numbers show on the check-in row and in the webhook payload.

Monitoring a scheduled AI agent

A scheduled agent run is a run like any other, and it can exit 0 having looped without making progress, skipped the step that mattered, or spent the budget on nothing. Ping when it finishes with the exit code, and send the numbers that describe a good run.

run-agent.sh (from cron, CI, or a workflow step)
python agent.py > run.log 2>&1; code=$?

# whatever your run emits: spend, tool calls, rows written
cost=$(jq -r .total_cost_usd run_summary.json)
calls=$(jq -r .tool_calls run_summary.json)

curl -fsS -m 10 \
  -d "cost_usd=$cost" \
  -d "tool_calls=$calls" \
  "https://illari.dev/ping/YOUR_KEY/$code"

Add rules on the monitor (Pro): cost_usd is greater than 5, tool_calls is greater than 100. A missing check-in means the run did not happen; a non-zero exit or a metric past its threshold means it ran and something was off.

Schedules & timezones

A monitor is one of two shapes. The create and edit forms ask you to pick: runs on a fixed schedule or checks in regularly.

Scheduled (has a cron expression)

Standard five-field cron (minute hour day-of-month month day-of-week), with ranges (1-5), steps (*/15), and lists (1,15), plus the macros @hourly, @daily, @weekly, @monthly, @yearly. Pair it with an IANA timezone (UTC, America/New_York, Europe/Berlin, …). illari computes each expected run from the expression in that zone. The form has a Common schedules dropdown (every 15 minutes, weekdays at 9 AM, and so on) that fills the expression for you; the field stays editable, and the cron tester builds and previews any expression.

When you enter a cron expression, on the create form and on each monitor's page, illari shows the next ten computed runs and flags common mistakes: a grace period as long as the interval (the monitor could never go Late), a sub-minute schedule (illari's checks are minute-resolution), and any daylight-saving change coming up in that window.

Daylight saving is handled from the zone database, not guessed. For 0 2 * * * in America/New_York: on the March spring-forward, 02:00 doesn't exist, so illari expects the run at the next valid instant; on the November fall-back, 02:00 occurs twice and illari expects a single run. Either way you don't get a phantom alert on the changeover weekend.

Open-ended (no cron expression)

No fixed cadence. The monitor just expects a ping at least every grace-period seconds. Set the grace period to the longest silence you'd tolerate: e.g. 3600 means “tell me if this job goes quiet for an hour.” Good for queue workers and jobs that run on demand.

Grace period

A buffer on top of the schedule for network jitter and slow starts, in seconds, 0 to 86400 (24 h), default 300. The deadline is:

  • scheduled: next expected run + grace
  • open-ended: last ping + grace

Raise it for jobs that are legitimately slow to check in; keep it well under the interval for tight schedules; a grace period longer than the gap between runs means the monitor can never actually go Late.

Statuses & detection

A worker evaluates every monitor once a minute.

  • Pending: created, never pinged. Not evaluated until the first ping arrives.
  • Up: the last check-in was on time.
  • Late: the deadline passed with no ping. Fires the first alert.
  • Down: still silent ~10 minutes after Late. Fires the second alert.

Because the worker runs on a 60-second tick, a transition lands within about a minute of the actual deadline. Any ping, success or failure, returns the monitor to Up and clears the incident. If an alert had fired, that check-in also sends a “back UP” notification.

Related reading: how to know if your cron job silently failed.

Alert channels

Every alert emails you (from [email protected]; allowlist it). In Settings you can point that at a different address (a team alias, say).

Pro also fans every alert out to any number of channels. Add them in Settings. Each has a send test button and an enable / disable toggle, and once one enabled channel exists you can turn email off. Channels are account-wide: every enabled one gets every alert.

  • Slack / Discord: paste an Incoming Webhook URL. Short message: monitor name, what happened, a link.
  • Telegram: bot token from @BotFather plus the target chat ID.
  • PagerDuty: an Events API v2 routing key.Late / Down / failed / hung map to trigger; the recovery maps to resolve (same dedup_key, the monitor ID).
  • incident.io: an HTTP alert source URL and its bearer token. Sends firing / resolved with a stable deduplication_key.
  • ntfy: a full topic URL (e.g. https://ntfy.sh/your-topic); optional access token for protected or self-hosted topics.
  • Pushover: your user or group key plus an application API token.
  • Webhook: any HTTPS endpoint gets the JSON POST below. Optional Authorization header for authenticated endpoints. One attempt, 10-second timeout, no retries; failures are logged on our side.
webhook payload
POST <your webhook URL>
Content-Type: application/json

{
  "event": "late",                       // late | down | recovered | hung | failed | slow | metric | metric_anomaly
  "sentAt": "2026-08-30T03:14:00.000Z",
  "detail": {},                          // failed:         { exitCode, output }
                                        // slow:           { durationMs, baselineMs }
                                        // recovered:      { from }
                                        // hung:           { startedAt, maxRuntimeSeconds }
                                        // metric:         { metric, value, op, threshold }
                                        // metric_anomaly: { metric, value, direction, baseline }
  "monitor": {
    "id": "a1b2c3d4-…",
    "name": "nightly-etl",
    "command": "/opt/scripts/etl.sh",    // null unless you set one
    "cronExpression": "0 3 * * *",       // null for a no-schedule monitor
    "timezone": "America/New_York",
    "lastPingAt": "2026-08-29T07:03:11.000Z",
    "nextExpectedAt": "2026-08-30T07:00:00.000Z"
  },
  "url": "https://illari.dev/dashboard/a1b2c3d4-…"
}

Repeat Late and failed / slow alerts for one monitor are rate-limited to one every few minutes, so a job that fails on every run won't flood you. Down always goes through.

Managing monitors

Create, rename, re-schedule, pause, and delete monitors from the dashboard, or from scripts and CI with the Management API.

Command / script is an optional free-text field: the job this monitor watches, e.g. /opt/scripts/backup.sh. It's shown in the monitor list, on the monitor's page, and included in alerts (as a Job: line and in the webhook monitor.command), so a page at 3 a.m. tells you exactly what to check. illari never runs it.

Pausing (from a monitor's page) takes it out of evaluation entirely during a deploy or planned maintenance: no status changes, no alerts. Resuming restarts the clock from that moment, so paused time never counts as Late.

Snoozing is a pause that wakes itself: same effect, and the clock resets when it ends so the snoozed window isn't counted. Pick 1 / 4 / 24 hours on the monitor's page (or a quick 1 hour from the list's ⋯ menu); “Wake now” ends it early.

Deleting a monitor removes its check-in history and frees the slot. The ping URL immediately returns 404.

Status pages

Under Settings you can publish a read-only page at illari.dev/s/<link> showing the current state of a chosen set of monitors. The link is an unguessable token; anyone who has it can view the page, which shows only monitor names, schedules, and states (no keys, no history). It refreshes about every 30 seconds. Disable or delete it any time.

Management API

A small REST API for managing monitors: list, create, update (including pause and snooze), delete, and read check-in history. The ping URL is still the only thing your jobs call; this is for provisioning and automation.

Full API reference →

Every endpoint with its request and response fields, error codes, and curl examples. An OpenAPI 3.1 spec lives at /openapi.json.

Create a key under Settings → API keys (shown once), then send it as Authorization: Bearer illari_….

list monitors
curl -fsS https://illari.dev/api/v1/monitors \
  -H "Authorization: Bearer illari_YOUR_KEY"

Plans & limits

Free: 25 monitors, email alerts (including recovery and hung-run notices), cron + timezone scheduling, grace periods, pause, snooze, status badge, and 14 days of check-in history. Recording run data (exit code, duration, output) is included; viewing it and alerting on it is Pro.

Pro: $20/mo (or $204/yr). Unlimited monitors; Slack, Discord, Telegram, PagerDuty, incident.io, ntfy, Pushover, and webhook alert channels; the run-history view of structured run data with failed, duration-anomaly, and custom-metric threshold alerts; 90-day history.

  • Ping rate limit: 60/minute per monitor.
  • Captured body: ~10 KB per ping.
  • Check-in history is pruned daily to the plan's retention window.

Upgrade, change payment method, or cancel from Billing. Cancelling keeps Pro until the end of the paid period (the billing page shows the exact end date), after which the account reverts to Free.

See pricing.

FAQ

How do I monitor a cron job?
Create a monitor in illari, then have the cron job call the monitor's ping URL as its last step (for example, append `&& curl -fsS https://illari.dev/ping/YOUR_KEY` to the crontab line). illari expects that request on schedule and alerts you when it doesn't arrive.
What is a dead man's switch / heartbeat monitor?
A monitor that alerts when an expected signal doesn't arrive, instead of when an error occurs. Your job reports in on every successful run; if a report is late or missing, something is wrong and you get alerted. That includes cases the job itself can't report, like the machine being down.
Do I need to install an agent or SDK?
No. The integration is a single HTTP request to a URL. Anything that can run curl, wget, or make an HTTP call works: no package, no daemon. There is an optional CLI (`npm install -g illari`) that wraps a command, but it is not required.
How do I monitor a job that runs on an irregular schedule?
Create the monitor without a cron expression. It then only expects a ping at least once every grace-period seconds. Set the grace period to the longest gap you'd tolerate between runs.
Does illari send a notification when a job recovers?
Yes. If an alert fired for a monitor being Late or Down, the check-in that clears it sends a 'back UP' notification to email and any channels.
How do I know if illari itself is up?
The status page at illari.dev/status shows the check-in worker's liveness. It writes a heartbeat every minute, and the page flags operational / degraded / down from how recent that heartbeat is. The worker is also monitored by a third party.

Something not covered here? [email protected].