A schedule starts a flow's published version by the clock: every weekday at 08:00, or once next Thursday. Like webhook runs, scheduled runs are headless: there is no contact and nothing is sent to a channel. The run's output is kept on the run record.
Before you create one
- The flow must be published, and its start point must list the Schedule trigger. Otherwise creating a schedule is refused with
409(FLOW_NOT_PUBLISHEDorTRIGGER_TYPE_NOT_ACCEPTED), and with422 TRIGGER_PAYLOAD_INCOMPLETEifpayloadlacks a value the flow always needs. These readiness checks run before the cron and timezone are checked. - You need the
builder.bots:schedulepermission to create, change or delete schedules, andbuilder.bots:readto read them.
Create a schedule
POST /api/v1/bots/{botId}/schedules takes either cron or runAt, never both and never neither.
| Field | Type | Notes |
|---|---|---|
cron | string | A recurring schedule. Six fields (see below). |
timezone | string | IANA zone the cron is read in, such as Asia/Riyadh. Blank means UTC. An unknown zone is a 400. |
runAt | string | A one-time run: an ISO 8601 instant such as 2026-11-12T08:00:00Z. It must be in the future. |
payload | object | Becomes the run's trigger.payload. |
curl -X POST https://api.cx.na6iq.com/api/v1/bots/42/schedules \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{
"cron": "0 0 8 * * MON-FRI",
"timezone": "Asia/Riyadh",
"payload": { "report": "daily-summary" }
}'curl -X POST https://api.cx.na6iq.com/api/v1/bots/42/schedules \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{ "runAt": "2026-11-12T08:00:00Z", "payload": { "campaign": "launch" } }'The response is the schedule:
{
"id": 5,
"botId": 42,
"cron": "0 0 8 * * MON-FRI",
"timezone": "Asia/Riyadh",
"runAt": null,
"nextRunAt": "2026-11-09T05:00:00Z",
"status": "ENABLED",
"payload": { "report": "daily-summary" },
"nextFireTimes": ["2026-11-09T05:00:00Z", "2026-11-10T05:00:00Z", "2026-11-11T05:00:00Z"],
"createdAt": "2026-11-07T10:00:00Z",
"updatedAt": "2026-11-07T10:00:00Z"
}nextFireTimes are computed by the server, up to three. A one-off shows its single instant. It's empty once a schedule is completed, or when a one-off's time has passed.
Cron format: six fields
Natiq uses Spring's cron, which has six fields, not the five most tools use. The first field is seconds.
┌───────────── second (0-59)
│ ┌─────────── minute (0-59)
│ │ ┌───────── hour (0-23)
│ │ │ ┌─────── day of month (1-31)
│ │ │ │ ┌───── month (1-12 or JAN-DEC)
│ │ │ │ │ ┌─── day of week (0-7 or MON-SUN; 0 and 7 are Sunday)
│ │ │ │ │ │
0 0 8 * * MON-FRI| Expression | Runs |
|---|---|
0 0 8 * * * | Every day at 08:00 |
0 0 8 * * MON-FRI | Weekdays at 08:00 |
0 */15 * * * * | Every 15 minutes |
0 0 9 1 * * | The first of each month at 09:00 |
0 30 6 * * SUN-THU | Sunday to Thursday at 06:30 |
A five-field expression such as 0 8 * * * is refused with 400 SCHEDULE_INVALID. A cron that can never fire (for example 0 0 0 30 2 *, 30 February) is refused with "has no next fire time". The parser's own message names the field it did not like.
There's no minimum, but schedules are checked every 30 seconds: a run can start up to 30 seconds late, and an expression more frequent than that runs about once every 30 seconds.
Timezone
timezone is an IANA zone id (Asia/Riyadh, Asia/Amman, Europe/London). "Every day at 08:00" means 08:00 in that zone, and keeps meaning that when the offset changes for daylight saving. Fixed offsets such as +03:00 are accepted but never follow daylight saving; prefer the zone name, such as Asia/Riyadh. The default is UTC, so always send a timezone for local-time schedules.
Preview without saving
POST /api/v1/bots/{botId}/schedules/validate with { "cron": "…", "timezone": "…" } returns the next fire times and stores nothing. It needs the builder.bots:schedule permission.
{ "cron": "0 0 8 * * MON-FRI", "timezone": "Asia/Riyadh", "nextFireTimes": ["2026-11-09T05:00:00Z", "…"] }Manage schedules
| Call | Purpose |
|---|---|
GET /bots/{botId}/schedules | List the bot's schedules. |
GET /bots/{botId}/schedules/{scheduleId} | Read one. |
PATCH /bots/{botId}/schedules/{scheduleId} | Change cron, timezone, runAt, payload or status. Resuming runs the same checks as creating. |
DELETE /bots/{botId}/schedules/{scheduleId} | Delete it. |
status | Meaning |
|---|---|
ENABLED | It will fire at nextRunAt. |
PAUSED | Switched off; reversible with a PATCH. |
COMPLETED | A one-off that has fired. Terminal. |
Read the runs
Every run, whatever started it, is under GET /bots/{botId}/runs; scheduled ones have trigger type SCHEDULE. Each carries status (PENDING, RUNNING, SUCCEEDED, FAILED, SKIPPED), payload, result and error.
If Natiq was unavailable when runs were due, the schedule fires once when it's back, then resumes its normal rhythm — missed runs aren't replayed one by one. If the flow is republished without a Schedule trigger, the schedule stays enabled and each due time records a failed run explaining why; add the trigger back, or pause the schedule.
Runs aren't queued behind each other: a run still going when the next is due overlaps it.