Task Schedules
Create recurring tasks that run on a schedule using cron expressions
Task Schedules
Create recurring tasks that run on a schedule using cron expressions.
Overview
Task schedules automate recurring work without manually creating tasks. Common uses:
- Daily reports — generate metrics every morning
- Periodic maintenance — run cleanup weekly
- Monitoring checks — test an endpoint hourly
- Data syncs — pull external data on a schedule
The tick that evaluates schedules runs hourly, on the hour. A schedule finer than hourly does not fire more often — it fires at most once an hour, with no error anywhere to tell you. Hourly is the floor for any cadence you write.
Creating a Schedule
Via Dashboard
- Navigate to your workspace
- Click New Task
- Choose Recurring in the segmented control at the top
- Enter a schedule name, a cron expression, a timezone (defaults to UTC), and the task template the schedule will stamp out
- Click Create Schedule
Via API
Creating, editing or deleting a schedule requires an admin-level API key. A
worker-level key gets 401.
curl -X POST https://buildd.dev/api/workspaces/{workspace-id}/schedules \
-H "Authorization: Bearer bld_xxx" \
-H "Content-Type: application/json" \
-d '{
"name": "Daily metrics report",
"cronExpression": "0 9 * * *",
"timezone": "America/Los_Angeles",
"taskTemplate": {
"title": "Generate metrics report",
"description": "Pull metrics and post a summary",
"priority": 5
}
}'Validate an expression before saving it with
GET /api/workspaces/{workspace-id}/schedules/validate?cron=0+9+*+*+*&timezone=UTC,
which returns { valid, description, nextRuns }.
Cron Expression Syntax
minute hour day month weekday
| Field | Values | Special |
|---|---|---|
| Minute | 0-59 | * (every), */5 (every 5) |
| Hour | 0-23 | * (every), */2 (every 2) |
| Day | 1-31 | * (every), 1,15 (1st and 15th) |
| Month | 1-12 | * (every), 1-6 (Jan-Jun) |
| Weekday | 0-7 | * (every), 1-5 (Mon-Fri), 0=7=Sun |
Expressions are parsed by croner, which
also accepts a six-field form with leading seconds and aliases like @daily.
Those parse, but the hourly tick still bounds how often anything fires.
Common Examples
# Every hour at :00 — the finest cadence that behaves as written
0 * * * *
# Every day at 9am in the schedule's timezone
0 9 * * *
# Every Monday at 8am
0 8 * * 1
# Every weekday (Mon-Fri) at 6pm
0 18 * * 1-5
# First day of every month at midnight
0 0 1 * *Why a Schedule Might Not Fire
A due schedule can be skipped without failing. These are the reasons, and each
one writes lastDeferralReason on the schedule row so you can tell which
applied.
| Guard | Default | Behaviour |
|---|---|---|
maxConcurrentFromSchedule | 1 | Counts tasks still active from this schedule and skips the run if the cap is reached. A daily schedule whose task takes longer than a day will silently skip runs. |
pauseAfterFailures | 5 | After this many consecutive failures the schedule sets itself enabled: false. It will not resume on its own. |
| Seat limit | — | If the owning account is at its concurrent-session or worker limit, the run is deferred and a schedule:deferred event fires with reason: 'seats_full'. |
| Mission cost budget | — | If the schedule's mission has spent its costBudgetUsd, the run is deferred with budget_exhausted until the budget is raised. |
| Mission not active | — | If the linked mission is anything other than active, the schedule is disabled, not deferred. |
orchestrationMode: 'manual' | — | The schedule stays dormant until the mission is armed. |
One-Off Schedules
Set oneShot: true and the schedule fires once, then sets itself
enabled: false with no next run. The dashboard exposes this as a date and time
picker rather than a cron field.
Cadence Selects the Model
A schedule's cron interval infers the task's kind and complexity, and those
feed the claim-time model router — so cadence indirectly chooses which model
runs the work. A frequent schedule is classified as cheap observation work.
Set kind and complexity explicitly on the task template to override the
inference; an explicit value always wins over the cadence-derived one.
Triggers
A schedule can be gated on an external value changing instead of firing every
tick. Set taskTemplate.trigger:
{
"trigger": {
"type": "rss",
"url": "https://example.com/feed.xml"
}
}type is rss or http-json; http-json also takes a path to select a
value out of the response, and both accept headers. On each tick the value is
fetched and compared to the last one seen — a task is created only when it
changed, and an unchanged value is recorded as a check without a run.
Inside the task template, {{triggerValue}} is interpolated into the title and
description, and the task's context carries triggerValue,
previousTriggerValue and triggerMetadata.
Triggers are configured through the API or MCP only; the dashboard schedule form has no trigger fields.
Managing Schedules
| Endpoint | Purpose |
|---|---|
GET /api/workspaces/{id}/schedules | List |
GET/PATCH/DELETE /api/workspaces/{id}/schedules/{scheduleId} | Read, edit, remove |
GET /api/workspaces/{id}/schedules/validate?cron=&timezone= | Validate an expression |
POST/PATCH/DELETE .../schedules/{scheduleId}/suggestion | Propose a change for approval |
The suggestion endpoints let an agent propose a cron or enabled change into
pendingSuggestion without applying it. Any principal with workspace access can
propose; only a signed-in user or an admin key can approve.
For self-hosted cron trigger setup, see the deployment guide.