Core concepts
Schedules
A schedule creates a job on a recurring timetable: a cron expression, a time zone and the job to create. Use it for reports, cleanups, syncs and anything else that repeats.
On this page
Creating a schedule
Each time a schedule fires, Halyard creates an ordinary job with the queue, task and payload you set, so the run gets the same retries, logs and alerts as any other job. The job's payload also includes scheduled_for, the exact time it was due.
Declare schedules in halyard.yaml so they live in version control and deploy with your code, or manage them through the API.
# halyard.yamlschedules: nightly-report: cron: "30 2 * * *" timezone: Europe/Amsterdam queue: reports task: nightly-report payload: { format: "pdf" } overlap: skipconst schedule = await halyard.schedules.create({ name: "nightly-report", cron: "30 2 * * *", timezone: "Europe/Amsterdam", queue: "reports", task: "nightly-report", payload: { format: "pdf" }, overlap: "skip",}); console.log(schedule.id, schedule.next_run_at);// sch_5Tq2Bw9Nf4 2026-10-18T00:30:00Zschedule = halyard.schedules.create( name="nightly-report", cron="30 2 * * *", timezone="Europe/Amsterdam", queue="reports", task="nightly-report", payload={"format": "pdf"}, overlap="skip",) print(schedule.id, schedule.next_run_at)# sch_5Tq2Bw9Nf4 2026-10-18T00:30:00Z{ "id": "sch_5Tq2Bw9Nf4", "object": "schedule", "name": "nightly-report", "cron": "30 2 * * *", "timezone": "Europe/Amsterdam", "queue": "reports", "task": "nightly-report", "overlap": "skip", "state": "active", "last_run": { "job_id": "job_8Vn4Hc2Lp6", "status": "succeeded", "at": "2026-10-17T00:30:00Z" }, "next_run_at": "2026-10-18T00:30:00Z"}Cron syntax
Schedules use standard five-field cron expressions. Fields are separated by spaces and evaluated in the schedule's time zone. The finest resolution is one minute.
| Field | Allowed values | Special characters |
|---|---|---|
| Minute | 0–59 | * , - / |
| Hour | 0–23 | * , - / |
| Day of month | 1–31 | * , - / L |
| Month | 1–12 or JAN–DEC | * , - / |
| Day of week | 0–6 or SUN–SAT | * , - / L # |
*any value,,a list (1,15),-a range (9-17),/a step (*/10).Lmeans the last day of the month, or with a weekday the last one of that kind (5Lis the last Friday).#picks the nth weekday of the month:MON#1is the first Monday.- Shortcuts
@hourly,@daily,@weeklyand@monthlyare accepted.
Examples
| Expression | Runs |
|---|---|
| */15 * * * * | Every 15 minutes |
| 0 * * * * | At the start of every hour |
| 30 2 * * * | Every day at 02:30 |
| 0 9 * * MON-FRI | Weekdays at 09:00 |
| 0 0 1 * * | Midnight on the first of every month |
| 0 18 L * * | 18:00 on the last day of every month |
| 0 10 * * MON#1 | 10:00 on the first Monday of the month |
Check an expression before you deploy it. The CLI prints the next runs in local time and UTC:
halyard schedules preview "30 2 * * *" --tz Europe/Amsterdam --count 3 Sun 2026-10-18 02:30 CEST (00:30 UTC) Mon 2026-10-19 02:30 CEST (00:30 UTC) Tue 2026-10-20 02:30 CEST (00:30 UTC)Time zones
Set timezone to an IANA name such as America/New_York or Asia/Tokyo. The default is UTC. A schedule for 09:00 in Europe/Amsterdam runs at 09:00 local time all year, which is 07:00 UTC in summer and 08:00 UTC in winter. Timestamps in the API are always UTC.
Daylight saving time
Twice a year, local clocks skip or repeat an hour. Halyard handles both cases predictably:
| Transition | Example (Europe/Amsterdam) | What happens |
|---|---|---|
| Clocks go forward | 02:00–02:59 does not exist on 29 March 2026 | A run due in the skipped hour starts at 03:00, once. |
| Clocks go back | 02:00–02:59 happens twice on 25 October 2026 | A run due in the repeated hour starts on the first pass only. |
| Interval schedules | */15 * * * * | Keep a steady interval in UTC, so they never double up or skip. |
The nightly-report above at 02:30 is exactly in the danger window. If that matters, schedule it at 30 3 * * * or use UTC.
Overlapping runs
A run can still be going when the next one is due, for example a report that usually takes 10 minutes on a 15-minute schedule after a slow day. overlap decides what happens:
| Policy | Behavior |
|---|---|
| skipDefault | If the previous run is still queued or running, the new run is skipped and a schedule.missed event is published. |
| queue | The new run is queued behind the previous one. At most one run waits; extra runs are skipped. |
| allow | Runs start on time regardless of earlier runs. Use only for idempotent, independent work. |
| replace | The running job is cancelled and the new run starts. Useful for snapshot-style syncs. |
Pausing and resuming
Pause a schedule to stop it creating jobs without losing its configuration, for example during a migration. Jobs it already created are not affected. When you resume, missed times are skipped unless you pass catch_up, which creates a single job for the whole gap, never one per missed run.
curl -X POST https://api.halyard.example/v3/schedules/sch_5Tq2Bw9Nf4/pause \ -H "Authorization: Bearer hy_test_51Hq8Zx2rTmV9cKpL3d" curl -X POST https://api.halyard.example/v3/schedules/sch_5Tq2Bw9Nf4/resume \ -H "Authorization: Bearer hy_test_51Hq8Zx2rTmV9cKpL3d" \ -d catch_up=falseawait halyard.schedules.pause("sch_5Tq2Bw9Nf4"); // Later. catch_up: true runs one job now for any missed timeawait halyard.schedules.resume("sch_5Tq2Bw9Nf4", { catch_up: false });halyard.schedules.pause("sch_5Tq2Bw9Nf4") # Later. catch_up=True runs one job now for any missed timehalyard.schedules.resume("sch_5Tq2Bw9Nf4", catch_up=False)Delayed jobs or schedules?
Both run work in the future. Pick by whether the work repeats:
| Delayed job | Schedule | |
|---|---|---|
| Runs | Once, at run_at or after delay | Repeatedly, on a cron expression |
| Created by | Your code, per event | Configuration, once |
| Typical use | Reminder 3 days after sign-up | Nightly report, hourly sync |
| Furthest ahead | 365 days | No limit |
| Cancel | Cancel the job | Pause or delete the schedule |
See Delayed jobs for delay and run_at.