# Cron scheduling

Most monitors run on a plain [interval](/monitors/intervals/) - "every N minutes". A **cron schedule** runs the
check at fixed calendar times instead: "every weekday at 09:00", "the 1st of each month", "every 15 minutes during
the working day". Use it for checks that only make sense at certain times, or to keep a heavy check away from
your peak hours. Cron times are always **UTC**.

## Settings reference

| Setting (app label) | API field (v2) | Type / allowed values | Default | Plan limits | What it does for you |
|---|---|---|---|---|---|
| **Cron schedule** (Main Settings, beside **Interval schedule**) | `cronSchedule` | A standard 5-field cron expression `minute hour day-of-month month day-of-week` (a 6-field form with seconds first is also accepted); `null` switches back to interval scheduling | None (interval scheduling) | Cron scheduling is a paid-plan feature (the switch shows **Available on paid plans** otherwise). A site crawl always uses cron. | Runs the check at the times the expression names, in UTC. |

While a cron schedule is set, the monitor's `interval` reads `0`; the two schedules are alternatives, never
combined.

## The presets in the app

| Preset | What you enter | Expression it builds |
|---|---|---|
| **Every N minutes** | N from 1 to 59 | `*/N * * * *` |
| **Every N hours** | N from 1 to 23 | `0 */N * * *` |
| **Daily** | A time (UTC) | `MM HH * * *` |
| **Weekly** | One or more weekdays and a time (UTC) | `MM HH * * 1,3,5` (0 = Sunday) |
| **Monthly** | A day of the month (1-31) and a time (UTC) | `MM HH D * *` |
| **Custom (advanced)** | Any expression; suggestions include `*/5 * * * *`, `0 9 * * 1-5` (weekdays at 09:00 UTC), `0 0 1 * *` (monthly on the 1st) | As typed |

Below the builder the app shows a plain-language description and **Next run:** - the next fire time computed by
the server, so you can confirm the schedule does what you meant before saving.

## Set it up in the app

1. On **Sites**, open the monitor (or **Add Monitor**) and expand **Main Settings**.
2. Switch the schedule from **Interval schedule** to **Cron schedule**.
3. Pick a preset and fill in its values, or choose **Custom (advanced)** and type an expression.
4. Check the **Next run:** line, then **Save**.

To go back to interval scheduling, switch to **Interval schedule** and pick an interval.

## Do it with the API or MCP

Check an expression first (scope `monitor:read`; nothing is saved). It always answers `200`:

```bash
curl -X POST https://api2.host-tracker.com/monitor/validate-cron \
  -H "Authorization: Bearer $HT_TOKEN" -H "Content-Type: application/json" \
  -d '{ "cronSchedule": "0 9 * * 1-5" }'
# { "valid": true, "next": [1759136400, 1759222800, ...] }        next 5 fire times, Unix seconds
# { "valid": false, "reason": "tooFrequent", "next": [] }
```

`reason` is one of `unparseable`, `neverFires` (no run in the next ~10 years), `tooFrequent` (runs closer than one
minute apart) or `notEntitled` (your plan does not include cron scheduling).

Then set it (scope `monitor:write`):

```bash
curl -X PATCH "https://api2.host-tracker.com/monitor/$MONITOR_ID" \
  -H "Authorization: Bearer $HT_TOKEN" -H "Content-Type: application/json" \
  -d '{ "cronSchedule": "0 9 * * 1-5" }'
```

- Clear it and return to an interval: `{ "cronSchedule": null, "interval": 300 }`.
- On create, send `cronSchedule` instead of `interval`.
- MCP: the curated `create_monitor` / `update_monitor` tools have no cron argument - use **`api_request`** with
  `PATCH /monitor/{id}` and the body above (and `POST /monitor/validate-cron` to preview).

## What happens next

The server computes the next fire time when you save, and the check runs at each fire time from then on. The
recheck, alerts and statistics work exactly as for an interval monitor.

## Limits and gotchas

- **UTC only.** `0 9 * * *` is 09:00 UTC all year; it does not follow your time zone or daylight saving time.
- **Minimum gap: 1 minute** between two runs (**1 day** for a site crawl). A faster expression is refused
  (`422` on `/cronSchedule`, "Schedule runs too frequently (minimum 1 minute)").
- **`*/N` restarts every hour.** `*/7 * * * *` runs at :00, :07 ... :56 and then :00 again - the last gap of each
  hour is shorter. Pick an N that divides 60 (or 24 for hours) for even spacing.
- **Monthly on day 29-31** is skipped in months that do not have that day ("Skipped in months shorter than the
  chosen day").
- **Not for fixed-cadence types.** DNSBL, Domain expiry, SSL/TLS certificate expiry and Web Risk monitors run on
  their own fixed schedule; a cron schedule sent for them is cleared with a warning.
- **Site crawl** is scheduled by cron only: the app offers **Daily**, **Weekly** and **Monthly**, the default is
  weekly on Mondays at 03:00 UTC (`0 3 * * 1`), and a crawl monitor cannot be left without a cron.
- **Plan gate.** Without the cron entitlement a write carrying `cronSchedule` is refused with `403 package_limit`
  ("Cron scheduling is not available on your package"). If you move to a plan without it, cron-scheduled monitors
  are disabled until you switch them to an interval.

## Related

- [Check intervals](/monitors/intervals/)
- [Anatomy of a check](/monitors/anatomy-of-a-check/)
- [Plan limits](/reference/plan-limits/)
