Schedules
A schedule is a standing instruction to start a checklist from a template on a clock — every Monday at 09:00 in London, the last weekday of each month, once on a given date. It is the API side of the Scheduling screen described in Create a Schedule. A schedule is not a checklist and never becomes one: it is a rule, and the checklists it produces are ordinary checklists from the moment they exist.
Behind every schedule sits a series of runs — one per future occurrence, written out in advance when the schedule is saved. A background job turns each run into a real checklist shortly before it is due. The /runs routes let you read that series, skip an occurrence and create an occurrence's checklist early. They are also the only way to find a run that was due but never produced a checklist. See How Schedules Run.
Schedules and runs are named by integer ids, not GUID keys. Neither table has a key column. A schedule id that belongs to another workspace is a 404, the same as one that does not exist.
What a key may do with schedules depends on who it acts as — see Permissions. A schedule's createdBy is the member the key acts as. See Who a Key Acts As.
Every write on this page accepts an Idempotency-Key header (see Idempotency), and every route is charged to the standard rate-limit budget (see Rate Limits).
Endpoints
| Method | Path | Description | MCP tool |
|---|---|---|---|
GET | /v3/schedules | List schedules | list_schedules |
GET | /v3/schedules/{id} | Get a schedule | get_schedule |
POST | /v3/schedules | Create a schedule | create_schedule |
PATCH | /v3/schedules/{id} | Update a schedule | update_schedule |
DELETE | /v3/schedules/{id} | Delete a schedule | delete_schedule |
GET | /v3/schedules/{id}/runs | List a schedule's runs | list_schedule_runs |
GET | /v3/schedules/{id}/runs/{runId} | Get a run | get_schedule_run |
DELETE | /v3/schedules/{id}/runs/{runId} | Skip a run | delete_schedule_run |
POST | /v3/schedules/{id}/runs/{runId}/checklist | Create a run's checklist now | create_schedule_run_checklist |
Permissions
The schedule routes apply the permissions the app's Scheduling screen applies. A key that acts as an Administrator can do everything on this page, and so can a key that acts as the workspace. A key that acts as a Member needs:
| Operation | What a Member needs |
|---|---|
| List and get schedules, list and get runs | Schedule.Viewer or Schedule.Creator. |
| Create a schedule | Schedule.Creator, and permission to run checklists from the chosen template — Run and View or Run and View Assigned To in its Checklist Permissions. |
| Update or delete a schedule | Schedule.Creator. A schedule has no owner: a Member with Schedule.Creator can change or delete any schedule, as in the app. |
| Skip a run, or create a run's checklist now | Schedule.Creator, and permission to run checklists from the schedule's template. |
A refusal is 403 FORBIDDEN, and its message names what is missing. It comes after the schedule is looked up, so a schedule in another workspace is still a 404.
How Schedules Run
Creating a schedule writes out its run series immediately, from the first occurrence to a horizon that depends on how often it repeats. A background job runs every ten minutes and turns each run due in the next fifteen minutes into a checklist. So a checklist appears up to about fifteen minutes before its time. The checklist's start is set to the time the run was due, not the moment the job made it. Once a week another job tops up any series whose last run is less than three months away.
repeats | Runs generated ahead | Largest every |
|---|---|---|
never | One run only | — |
daily | 400 days | 400 |
weekly | 400 days | 57 |
monthly | 25 months | 25 |
yearly | 10 years | 10 |
last_day_of_month, last_weekday_of_month, first_weekday_of_month | 25 months | — |
last_day_of_quarter, last_weekday_of_quarter | 60 months | — |
The job creates a run's checklist only when all of these hold:
- the schedule is active
- the template is not archived
- the workspace's subscription or trial is current.
When any of them fails, the job skips the run and reports nothing — no error, no notification. The schedule still reads as healthy and still shows a nextRun. Two fields expose this:
templateIsArchivedon the Schedule object. When it istrue, the schedule produces nothing until the template is unarchived. SwitchingisActiveoff and on does not help.status: "missed"on a run — a run whose time has passed with no checklist. List them withGET /v3/schedules/{id}/runs?status=missed.
Each generated checklist fires the new-checklist webhook, and the job notifies the whole team that the scheduler has created it.
Recurrence
recurrence is one object with three fields: repeats, every and daysOfWeek. Only some combinations mean anything. The API refuses the others instead of storing a setting the generator never reads.
repeats | What it produces | every | daysOfWeek |
|---|---|---|---|
never | One run, at startDateTime, and nothing after. | Refused | Refused |
daily | Every every days — or, with daysOfWeek, every one of those days. | Only without daysOfWeek | Optional |
weekly | Every every weeks on the start date's weekday — or, with daysOfWeek, on those days in every everyth week. | Optional | Optional |
monthly | Every every months on the start date's day of the month. | Optional | Refused |
yearly | Every every years on the start date. | Optional | Refused |
last_day_of_month | The last calendar day of each month. | Refused | Refused |
last_weekday_of_month | The last Monday-to-Friday of each month. | Refused | Refused |
first_weekday_of_month | The first Monday-to-Friday of each month. | Refused | Refused |
last_day_of_quarter | The last calendar day of March, June, September and December. | Refused | Refused |
last_weekday_of_quarter | The last Monday-to-Friday of each quarter. | Refused | Refused |
repeatsis matched without regard to case, and always reads back in lower case.everydefaults to1and must be1or more.1is accepted on every kind, so you can send a schedule's recurrence back exactly as you read it. A value above1is refused wherever the table says so, and above the largest value in How Schedules Run, because runs are only generated that far ahead.daysOfWeektakesmondaytosunday, in any case and any order, and reads back Monday first with duplicates removed. Leaving it out and sending an empty list mean the same thing: run on the weekday the start date falls on.neveris a one-off, not a way of switching a schedule off. To stop a schedule, setisActivetofalse.- There is no end date and no occurrence count. A repeating schedule runs until you deactivate or delete it.
- "Weekday" in the fixed-position kinds means Monday to Friday. Public holidays are not taken into account.
- The fixed-position kinds take their time of day from
startDateTime. The first run is that position in the start date's month or quarter — or the next one, if that has already passed.nextRunshows where it landed.
A monthly schedule that starts on the 29th, 30th or 31st moves to the last day of the first shorter month, and stays on that day afterwards. A schedule starting on 31 January runs on 28 February, then on 28 March. For month-end work, use last_day_of_month or last_weekday_of_month. A yearly schedule starting on 29 February behaves the same way.
For daily and weekly with daysOfWeek, choose a startDateTime that falls on one of the listed days.
Time Zones
timeZone is the zone the schedule's clock runs in. It takes an IANA name such as Europe/London or a Windows name such as GMT Standard Time, and reads back exactly as you sent it. An unknown zone is a 400.
startDateTime is a wall-clock time in that zone, not an instant — the one exception, with a run's scheduledDateTime, to the UTC rule in Dates and Times. Send it with no Z and no offset:
"timeZone": "Europe/London",
"startDateTime": "2026-10-05T09:00:00"
That means 09:00 in London. Every run is generated at that wall-clock time, so a 09:00 schedule stays at 09:00 on both sides of a daylight-saving change and the UTC instant moves instead. Responses report both readings: startDateTime next to startDateTimeUtc on a schedule, and scheduledDateTime next to scheduledDateTimeUtc on a run.
- A value with a
Zor an offset is refused with400, not converted. The two readings differ by hours, and a schedule saved an hour out would stay an hour out. - A time that does not exist — the hour skipped when the clocks go forward, such as
2027-03-28T01:30:00inEurope/London— is refused. - A time that happens twice, when the clocks go back, is taken as the standard-time reading. That is the second of the two.
- A start in the past is allowed for a repeating schedule. The first run moves forward to the next occurrence after now. It is refused for
never, whose only run would already be due. To start a checklist now, usePOST /v3/checklists.
startDateTime reads back as the value you set, not as the first run. When a past start has been moved forward, or a fixed-position kind has snapped to its day, nextRun holds the actual next run.
Checklist Names
checklistNameFormat is the name each generated checklist gets. It is separate from the schedule's own name, and defaults to the template's name. It is text with tokens in double braces, and whitespace inside the braces is ignored. Token names are matched exactly, including case.
| Token | Example for a run on Monday 5 October 2026 at 09:00 |
|---|---|
{{ template.name }} | Invoice Review |
{{ schedule.name }} | Weekly Invoice Review |
{{ checklist.date.dd/MM/yy }} | 05/10/26 |
{{ checklist.date.dd/MM/yyyy }} | 05/10/2026 |
{{ checklist.date.MM/dd/yy }} | 10/05/26 |
{{ checklist.date.MM/dd/yyyy }} | 10/05/2026 |
{{ checklist.date.yyyy-MM-dd }} | 2026-10-05 |
{{ checklist.date.MMMM d }} | October 5th |
{{ checklist.date.MMMM d, yyyy }} | October 5th 2026 |
{{ checklist.date.dddd, MMMM d }} | Monday, October 5th |
{{ checklist.date.dddd, MMMM d, yyyy }} | Monday, October 5th 2026 |
{{ checklist.date.MMMM }} | October |
{{ checklist.date.dddd }} | Monday |
{{ checklist.date.yyyy }} | 2026 |
{{ checklist.time.HH:mm }} | 09:00 |
{{ checklist.time.HH:mm:ss }} | 09:00:00 |
{{ checklist.week }} | 41 (the ISO week number) |
{{ checklist.quarter }} | Q4 |
Dates and times are the run's own wall-clock date and time in the schedule's zone. The same tokens are available on the Scheduling screen — see Auto Generated Values.
- A token not in this list is refused. The expander does not leave an unknown token visible: it removes the braces and writes the text into every name.
- Unbalanced
{{or}}is refused. - The pattern may be up to 1,000 characters, but a generated name is stored in 100 and is cut to fit without warning. A pattern whose fixed text — everything outside the braces — is already over 100 characters is refused.
- Names are expanded when the run series is written, not when the checklist is made. A run keeps the name it was generated with. Changing the pattern regenerates the pending runs so they pick it up. Checklists that already exist keep their names.
Parameters
A schedule can set values for the template's parameters, and every checklist it creates is started with them. Parameters the schedule does not mention take the template's defaults. GET /v3/templates/{key} lists a template's parameters.
Name each parameter by its key or its name (matched without regard to case), and give a value as a string. A parameter the template does not have is refused, not dropped. Naming the same parameter twice is refused. On create, an entry whose value is null is ignored.
If the template later removes a parameter, the schedule's stored value stays. GET /v3/schedules/{id} lists it with its key and value but no name or type. The job ignores it when it builds each checklist.
The Schedule Object
| Field | Type | Description |
|---|---|---|
id | integer | The schedule's id. |
name | string | The schedule's own name. Not the name its checklists get — that is checklistNameFormat. |
templateKey | string (GUID) | The template each run is started from. It cannot be changed after creation. |
templateName | string | The template's current name, read at request time. |
templateIsArchived | boolean | true when the template is archived, which means the schedule produces nothing. See How Schedules Run. |
checklistNameFormat | string | The name pattern for generated checklists. See Checklist Names. |
isActive | boolean | Whether the schedule is generating runs. |
recurrence | Recurrence | How often it repeats. |
timeZone | string | The IANA or Windows zone the schedule's clock runs in, as it was sent. |
startDateTime | string | The start as a wall-clock time in timeZone, with no Z — for example 2026-10-05T09:00:00. Absent only for an older schedule whose stored zone this server does not recognise. |
startDateTimeUtc | string | The same moment as a UTC instant. |
nextRun | string | When the next run is due, as a UTC instant. Absent when nothing is queued — the schedule is inactive, or it was a one-off that has already run. |
createdDateTime | string | When the schedule was created, UTC. |
createdBy | object | name and email of the member who created it. Only on the single-schedule read and on write responses. Absent if that member has left the workspace. |
parameters | array of Parameter | The parameter values the schedule sets. Only on the single-schedule read and on write responses. |
runsRegenerated | boolean | Only on the response to PATCH. Whether the change rebuilt the pending runs. |
{
"id": 318,
"name": "Weekly Invoice Review",
"templateKey": "3f2b8c1e-7a4d-4e5b-9c61-2d8f0a7b6e14",
"templateName": "Invoice Review",
"templateIsArchived": false,
"checklistNameFormat": "{{template.name}} — {{checklist.date.yyyy-MM-dd}}",
"isActive": true,
"recurrence": {
"repeats": "weekly",
"every": 1,
"daysOfWeek": ["monday"]
},
"timeZone": "Europe/London",
"startDateTime": "2026-10-05T09:00:00",
"startDateTimeUtc": "2026-10-05T08:00:00Z",
"nextRun": "2026-10-05T08:00:00Z",
"createdDateTime": "2026-09-28T14:12:37Z",
"createdBy": {
"name": "Sarah Chen",
"email": "sarah.chen@acme.example"
},
"parameters": [
{
"key": "8d41c7a2-5e9b-4f13-b0c6-7a2e9d18f354",
"name": "Cost Centre",
"type": "string",
"value": "FIN-01"
}
]
}
The Recurrence Object
| Field | Type | Description |
|---|---|---|
repeats | string | One of never, daily, weekly, monthly, yearly, last_day_of_month, last_weekday_of_month, first_weekday_of_month, last_day_of_quarter, last_weekday_of_quarter. |
every | integer | The interval. Present only for daily without days, weekly, monthly and yearly. |
daysOfWeek | array of string | monday to sunday, Monday first. Present only for daily and weekly, and an empty array when no days are chosen. |
Fields a kind does not read are left out of responses. A recurrence you read can be sent back in a PATCH unchanged. See Recurrence for the rules.
The Parameter Object
| Field | Type | Description |
|---|---|---|
key | string (GUID) | The template parameter's key. |
name | string | The parameter's name. Absent when the template no longer has this parameter. |
type | string | string, datetime or member_or_group. Absent when the template no longer has this parameter. |
value | string | The value the schedule sets. |
The Run Object
A run is one occurrence of a schedule: a checklist it is going to produce, or has produced. A run has an id long before it has a checklist.
status is not stored. It is worked out from whether the run has a checklist and whether its time has passed:
status | Meaning |
|---|---|
pending | Due in the future, no checklist yet. This is the state of nearly every run. |
created | Has a checklist, named in checklist. |
missed | Due in the past, no checklist. |
A run moves from pending to missed because the clock passes it, not because anything happened. A run a few minutes overdue can read as missed while the job is still on its way to it. A run that stays missed means the template was archived, the subscription had lapsed when it was due, or the job failed. You can still create its checklist now.
| Field | Type | Description |
|---|---|---|
id | integer | The run's id. Not the checklist's key. |
scheduleId | integer | The schedule this run belongs to. |
status | string | pending, created or missed. |
checklistName | string | The fully expanded name the run was generated with. |
scheduledDateTime | string | When the run is due, as a wall-clock time in the schedule's zone, with no Z. Absent only when the schedule's stored zone is not recognised. |
scheduledDateTimeUtc | string | The same moment as a UTC instant. Order and filter on this. |
timeZone | string | The schedule's time zone. |
checklist | object | The checklist the run became: key, name and url. Absent until there is one. name is what the checklist is called now. If it differs from checklistName, somebody renamed it. |
startedDateTime | string | When the checklist started, UTC. Usually the same instant as scheduledDateTimeUtc. It is earlier when the checklist was created early. Absent until there is a checklist. |
completedDateTime | string | When the checklist was completed, UTC. Absent while it is outstanding or there is no checklist. |
checklistCreated | boolean | Only on the response to POST .../checklist. true when that request created the checklist. |
{
"id": 52107,
"scheduleId": 318,
"status": "created",
"checklistName": "Invoice Review — 2026-10-05",
"scheduledDateTime": "2026-10-05T09:00:00",
"scheduledDateTimeUtc": "2026-10-05T08:00:00Z",
"timeZone": "Europe/London",
"checklist": {
"key": "c7e2a915-3b8d-4f06-9a4c-e15d2b7f8032",
"name": "Invoice Review — 2026-10-05 (late invoices)",
"url": "https://acme.checkflow.io/Checklist/Index?checklistKey=c7e2a915-3b8d-4f06-9a4c-e15d2b7f8032"
},
"startedDateTime": "2026-10-05T08:00:00Z",
"completedDateTime": "2026-10-05T15:42:10Z"
}
Worked Examples
Each example is the body of a POST /v3/schedules. Replace templateKey with one of your own templates.
Every Monday at 09:00, London Time
{
"name": "Weekly Invoice Review",
"templateKey": "3f2b8c1e-7a4d-4e5b-9c61-2d8f0a7b6e14",
"timeZone": "Europe/London",
"startDateTime": "2026-10-05T09:00:00",
"recurrence": { "repeats": "weekly" }
}
5 October 2026 is a Monday, so a plain weekly repeats on Mondays. "daysOfWeek": ["monday"] gives the same result. The first run is at 08:00 UTC (British Summer Time). From 26 October the runs are at 09:00 UTC, still 09:00 in London.
Every Weekday at 07:30, New York Time
{
"name": "Daily Supplier Check",
"templateKey": "3f2b8c1e-7a4d-4e5b-9c61-2d8f0a7b6e14",
"timeZone": "America/New_York",
"startDateTime": "2026-10-05T07:30:00",
"recurrence": {
"repeats": "daily",
"daysOfWeek": ["monday", "tuesday", "wednesday", "thursday", "friday"]
}
}
Every Other Week, on Monday and Thursday
{
"name": "Fortnightly Supplier Onboarding Review",
"templateKey": "3f2b8c1e-7a4d-4e5b-9c61-2d8f0a7b6e14",
"timeZone": "Europe/London",
"startDateTime": "2026-10-05T10:00:00",
"recurrence": {
"repeats": "weekly",
"every": 2,
"daysOfWeek": ["monday", "thursday"]
}
}
Weeks start on Monday. This runs on 5 and 8 October, then on 19 and 22 October, and so on.
The Last Working Day of Each Month at 17:00
{
"name": "Month-End Close",
"templateKey": "3f2b8c1e-7a4d-4e5b-9c61-2d8f0a7b6e14",
"checklistNameFormat": "{{template.name}} — {{checklist.date.MMMM}} {{checklist.date.yyyy}}",
"timeZone": "Europe/London",
"startDateTime": "2026-10-01T17:00:00",
"recurrence": { "repeats": "last_weekday_of_month" }
}
The start date only supplies the month and the time of day. The first run snaps to Friday 30 October 2026 at 17:00, so the response shows "startDateTime": "2026-10-01T17:00:00" and "nextRun": "2026-10-30T17:00:00Z". "Working day" means Monday to Friday — public holidays are not skipped.
Once, on a Future Date
{
"name": "Q4 Audit Kick-Off",
"templateKey": "3f2b8c1e-7a4d-4e5b-9c61-2d8f0a7b6e14",
"timeZone": "Europe/London",
"startDateTime": "2026-11-02T09:00:00",
"recurrence": { "repeats": "never" }
}
Not Supported
The recurrence model cannot express these patterns:
- an end date or a fixed number of occurrences
- "the second Tuesday of the month" or any nth weekday other than first or last
- skipping public holidays.
To skip a single occurrence, delete that run.
List Schedules
Returns the workspace's schedules. Parameter values and createdBy are not on these rows — use Get a Schedule for those.
GET /v3/schedules
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
templateKey | query | string (GUID) | No | Only the schedules that run this template. |
isActive | query | boolean | No | true or false. Any other value is refused. Being active does not mean producing — check templateIsArchived. |
sort | query | string | No | field or field:asc / field:desc. One of name, templateName, startDateTime, nextRun, createdDateTime. Default name:asc. Ties are broken by id. |
after | query | string | No | The nextCursor from the previous page. Send the same filters, sort and pageSize with it. |
pageSize | query | integer | No | 1–100, default 50. A value that is not a number from 1 to 100 is replaced by 50, not refused. |
See Pagination. total is always present.
Example
GET https://api.checkflow.io/v3/schedules?isActive=true&sort=nextRun
X-API-KEY: your-api-key-here
HTTP/1.1 200 OK
{
"items": [
{
"id": 318,
"name": "Weekly Invoice Review",
"templateKey": "3f2b8c1e-7a4d-4e5b-9c61-2d8f0a7b6e14",
"templateName": "Invoice Review",
"templateIsArchived": false,
"checklistNameFormat": "{{template.name}} — {{checklist.date.yyyy-MM-dd}}",
"isActive": true,
"recurrence": {
"repeats": "weekly",
"every": 1,
"daysOfWeek": ["monday"]
},
"timeZone": "Europe/London",
"startDateTime": "2026-10-05T09:00:00",
"startDateTimeUtc": "2026-10-05T08:00:00Z",
"nextRun": "2026-10-05T08:00:00Z",
"createdDateTime": "2026-09-28T14:12:37Z"
},
{
"id": 324,
"name": "Month-End Close",
"templateKey": "a6d09e3b-41c7-4b2f-8e5a-f3c81d627b90",
"templateName": "Supplier Onboarding",
"templateIsArchived": false,
"checklistNameFormat": "{{template.name}} — {{checklist.date.MMMM}} {{checklist.date.yyyy}}",
"isActive": true,
"recurrence": {
"repeats": "last_weekday_of_month"
},
"timeZone": "Europe/London",
"startDateTime": "2026-10-01T17:00:00",
"startDateTimeUtc": "2026-10-01T16:00:00Z",
"nextRun": "2026-10-30T17:00:00Z",
"createdDateTime": "2026-09-28T14:20:05Z"
}
],
"hasMore": false,
"total": 2
}
Responses
| Status | Code | When |
|---|---|---|
200 | — | The page of schedules. |
400 | VALIDATION_ERROR | field is templateKey (not a GUID), isActive (not true or false), sort (unknown field) or after (malformed, or issued for a different query). |
403 | FORBIDDEN | The key acts as a Member with neither Schedule.Viewer nor Schedule.Creator. |
Get a Schedule
Returns one schedule, with its parameter values and who created it.
GET /v3/schedules/{id}
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer | Yes | The schedule id. |
Example
GET https://api.checkflow.io/v3/schedules/318
X-API-KEY: your-api-key-here
HTTP/1.1 200 OK
Returns a Schedule object, as in the example there.
Responses
| Status | Code | When |
|---|---|---|
200 | — | The schedule. |
400 | VALIDATION_ERROR | field is id: not a positive integer. |
403 | FORBIDDEN | The key acts as a Member with neither Schedule.Viewer nor Schedule.Creator. |
404 | SCHEDULE_NOT_FOUND | No schedule with that id in this workspace. |
Create a Schedule
Creates a schedule and writes out its run series straight away. Everything that decides when a run happens is checked before anything is stored.
POST /v3/schedules
Parameters
This endpoint takes no parameters.
Request Body
{
"name": "Weekly Invoice Review",
"templateKey": "3f2b8c1e-7a4d-4e5b-9c61-2d8f0a7b6e14",
"checklistNameFormat": "{{template.name}} — {{checklist.date.yyyy-MM-dd}}",
"timeZone": "Europe/London",
"startDateTime": "2026-10-05T09:00:00",
"recurrence": {
"repeats": "weekly",
"daysOfWeek": ["monday"]
},
"parameters": [
{ "name": "Cost Centre", "value": "FIN-01" }
]
}
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | The schedule's name. At most 100 characters. |
templateKey | string (GUID) | Yes | The template to start each run from. Must be an unarchived template in this workspace. Cannot be changed later. |
timeZone | string | Yes | IANA or Windows zone name. See Time Zones. |
startDateTime | string | Yes | The first run, as a wall-clock time in timeZone, with no Z and no offset. |
recurrence | Recurrence | Yes | repeats is required. Send { "repeats": "never" } for a one-off. |
checklistNameFormat | string | No | The name pattern for generated checklists. Default: the template's name. |
isActive | boolean | No | Default true. With false, no runs are generated until the schedule is activated. |
parameters | array | No | Entries of { "key" or "name", "value" }. See Parameters. |
There is no duplicate check. Two schedules on the same template at the same time are two schedules and create two checklists. That is useful with different parameter values on each.
Example
POST https://api.checkflow.io/v3/schedules
X-API-KEY: your-api-key-here
Content-Type: application/json
{
"name": "Weekly Invoice Review",
"templateKey": "3f2b8c1e-7a4d-4e5b-9c61-2d8f0a7b6e14",
"checklistNameFormat": "{{template.name}} — {{checklist.date.yyyy-MM-dd}}",
"timeZone": "Europe/London",
"startDateTime": "2026-10-05T09:00:00",
"recurrence": {
"repeats": "weekly",
"daysOfWeek": ["monday"]
},
"parameters": [
{ "name": "Cost Centre", "value": "FIN-01" }
]
}
HTTP/1.1 201 Created
Returns the new Schedule object, exactly as in the example there, with createdBy, parameters and the nextRun the generator settled on.
A start time with a Z is refused:
HTTP/1.1 400 Bad Request
{
"error": {
"code": "VALIDATION_ERROR",
"message": "startDateTime is a wall-clock time in the schedule's own time zone. Send it without a 'Z' and without an offset, such as '2026-03-01T09:00:00', and name the zone in timeZone.",
"requestId": "0f7c2b9e-51a4-4d8e-b3a6-92e41c7d05f8",
"field": "startDateTime"
}
}
Responses
| Status | Code | When |
|---|---|---|
201 | — | The schedule was created. |
400 | VALIDATION_ERROR | No body; field is name (missing or over 100 characters), templateKey (missing or not a GUID), timeZone (missing or unknown), startDateTime (missing, carries a Z or offset, falls in a clocks-forward gap, or in the past on never), recurrence (missing), recurrence.repeats (missing or unknown), recurrence.every (below 1, on a kind that does not read it, on daily with days, or over the limit), recurrence.daysOfWeek (unknown day, or on a kind that does not read it), checklistNameFormat (unknown token, unbalanced braces, over 1,000 characters or over 100 characters of fixed text) or parameters (a parameter the template does not have, one named twice, or an entry with neither key nor name). |
403 | FORBIDDEN | The key acts as a Member without Schedule.Creator, or without permission to run checklists from the template. |
404 | TEMPLATE_NOT_FOUND | No unarchived template with that key in this workspace. |
Update a Schedule
Changes a schedule. Only the fields you send are changed. The template cannot be changed — delete the schedule and create a new one.
PATCH /v3/schedules/{id}
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer | Yes | The schedule id. |
Request Body
This is a partial update, not a JSON merge patch. See Partial Updates with PATCH. The body must name at least one field. An empty body is refused because a misspelled field name is more likely than a request to change nothing.
| Field | Type | Sending null | Description |
|---|---|---|---|
name | string | Refused | The schedule's name. At most 100 characters. |
checklistNameFormat | string | Resets it to the template's name | The name pattern for future checklists. |
timeZone | string | Refused | Changing the zone alone keeps the wall-clock time and moves the instant. A 09:00 schedule moved from Europe/London to America/New_York still runs at 09:00, now five hours later in UTC. Send startDateTime too to set both. |
startDateTime | string | Refused | A new start, with the same rules as on create. |
recurrence | Recurrence | Refused | Replaced as a whole, not merged. { "repeats": "weekly" } on a schedule that ran every two weeks on Monday and Thursday leaves it weekly, every week, on the start date's weekday. Read the schedule first and send the recurrence you want in full. |
isActive | boolean | Refused | false deletes the pending runs. true generates a fresh series from now. Missed occurrences are not made up. |
parameters | array | Changes nothing | Merged into the stored values one at a time. An entry with "value": null removes that value, so the template's default applies again. An empty list changes nothing. |
Changing timeZone or recurrence re-checks the stored start. It is refused if the start now falls in a clocks-forward gap, or if the schedule becomes never with a start in the past.
Example
Adding Thursdays to the weekly schedule, sent on Tuesday 27 October 2026:
PATCH https://api.checkflow.io/v3/schedules/318
X-API-KEY: your-api-key-here
Content-Type: application/json
{
"recurrence": {
"repeats": "weekly",
"daysOfWeek": ["monday", "thursday"]
}
}
HTTP/1.1 200 OK
{
"id": 318,
"name": "Weekly Invoice Review",
"templateKey": "3f2b8c1e-7a4d-4e5b-9c61-2d8f0a7b6e14",
"templateName": "Invoice Review",
"templateIsArchived": false,
"checklistNameFormat": "{{template.name}} — {{checklist.date.yyyy-MM-dd}}",
"isActive": true,
"recurrence": {
"repeats": "weekly",
"every": 1,
"daysOfWeek": ["monday", "thursday"]
},
"timeZone": "Europe/London",
"startDateTime": "2026-10-05T09:00:00",
"startDateTimeUtc": "2026-10-05T08:00:00Z",
"nextRun": "2026-10-29T09:00:00Z",
"createdDateTime": "2026-09-28T14:12:37Z",
"createdBy": {
"name": "Sarah Chen",
"email": "sarah.chen@acme.example"
},
"parameters": [
{
"key": "8d41c7a2-5e9b-4f13-b0c6-7a2e9d18f354",
"name": "Cost Centre",
"type": "string",
"value": "FIN-01"
}
],
"runsRegenerated": true
}
Responses
| Status | Code | When |
|---|---|---|
200 | — | The schedule as it now reads, with runsRegenerated. |
400 | VALIDATION_ERROR | The body is not a JSON object or names no field; field is id (not a positive integer), isActive (null), or any field listed under Create a Schedule for the same reasons, including null where the table says it is refused. Also timeZone when the schedule's stored zone is not recognised — send timeZone and startDateTime together to repair it. |
403 | FORBIDDEN | The key acts as a Member without Schedule.Creator. |
404 | SCHEDULE_NOT_FOUND | No schedule with that id in this workspace. |
404 | TEMPLATE_NOT_FOUND | parameters was sent and the schedule's template no longer exists. |
Notes
The pending runs are rebuilt when you change startDateTime, timeZone, recurrence, checklistNameFormat or isActive, or change name while the pattern uses {{ schedule.name }}. Sending isActive rebuilds even when the value is unchanged. runsRegenerated tells you whether it happened.
Rebuilding deletes every run due after now and generates the series again from the next occurrence. Runs already due are kept, so the schedule's history survives. The regenerated runs have new ids. A run you skipped comes back when the series is rebuilt.
A rebuild can create or remove checklists near the current time:
- The job creates checklists up to about fifteen minutes early, with a start at the run's due time. A checklist in that window counts as pending: the rebuild deletes it, and the job then creates it again from the regenerated run.
- On a
neverschedule whose one run has already happened, a change that rebuilds generates that run again with its time already passed. The job then creates a second checklist.
Delete a Schedule
Deletes a schedule, its pending runs and its parameter values. To stop a schedule without losing it, set isActive to false instead.
DELETE /v3/schedules/{id}
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer | Yes | The schedule id. |
Example
DELETE https://api.checkflow.io/v3/schedules/318
X-API-KEY: your-api-key-here
Responds 204 No Content with no body.
Responses
| Status | Code | When |
|---|---|---|
204 | — | The schedule was deleted. |
400 | VALIDATION_ERROR | field is id: not a positive integer. |
403 | FORBIDDEN | The key acts as a Member without Schedule.Creator. |
404 | SCHEDULE_NOT_FOUND | No schedule with that id in this workspace. |
Notes
Deleting a schedule cannot be undone. The recurrence, name pattern and parameter values are gone and must be rebuilt from scratch.
Checklists the schedule has already created are kept, including any created in the last few minutes for a run about to fall due. They are ordinary checklists and are not affected.
List a Schedule's Runs
Returns a schedule's runs — the checklists it has produced and the ones it is going to produce — oldest first.
GET /v3/schedules/{id}/runs
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer | Yes | The schedule id. |
status | query | string | No | pending, created or missed, in any case. Any other value is refused. |
from | query | string | No | Only runs due at or after this time. A wall-clock time in the schedule's zone, such as 2026-10-01T00:00:00, with no Z and no offset. |
to | query | string | No | Only runs due at or before this time, on the same terms as from. |
sort | query | string | No | field or field:asc / field:desc. One of scheduledDateTime, checklistName, startedDateTime, completedDateTime. Default scheduledDateTime:asc. There is no status sort. Ties are broken by id. |
after | query | string | No | The nextCursor from the previous page. Send the same filters, sort and pageSize with it. |
pageSize | query | integer | No | 1–100, default 50. A value that is not a number from 1 to 100 is replaced by 50, not refused. |
from and to follow the same rules as startDateTime: a value with a Z or an offset is refused, and so is a time inside a clocks-forward gap. A time that happens twice takes the standard-time reading. To avoid the zone entirely, leave them out and filter on scheduledDateTimeUtc yourself. See Pagination. total is always present.
Example
The runs either side of the October clock change, read on 20 October 2026:
GET https://api.checkflow.io/v3/schedules/318/runs?from=2026-10-19T00:00:00&to=2026-11-01T23:59:59
X-API-KEY: your-api-key-here
HTTP/1.1 200 OK
{
"items": [
{
"id": 52109,
"scheduleId": 318,
"status": "created",
"checklistName": "Invoice Review — 2026-10-19",
"scheduledDateTime": "2026-10-19T09:00:00",
"scheduledDateTimeUtc": "2026-10-19T08:00:00Z",
"timeZone": "Europe/London",
"checklist": {
"key": "5a90d3e7-c24b-4e81-9f36-0b7d1e4a2c59",
"name": "Invoice Review — 2026-10-19",
"url": "https://acme.checkflow.io/Checklist/Index?checklistKey=5a90d3e7-c24b-4e81-9f36-0b7d1e4a2c59"
},
"startedDateTime": "2026-10-19T08:00:00Z"
},
{
"id": 52110,
"scheduleId": 318,
"status": "pending",
"checklistName": "Invoice Review — 2026-10-26",
"scheduledDateTime": "2026-10-26T09:00:00",
"scheduledDateTimeUtc": "2026-10-26T09:00:00Z",
"timeZone": "Europe/London"
}
],
"hasMore": false,
"total": 2
}
Both runs are at 09:00 in London. The UTC instant moves by an hour because the clocks went back on 25 October.
Responses
| Status | Code | When |
|---|---|---|
200 | — | The page of runs. |
400 | VALIDATION_ERROR | field is id (not a positive integer), status (unknown), from or to (not a date, carries a Z or offset, inside a clocks-forward gap, or the schedule's stored zone is not recognised), to (before from), sort (unknown field) or after (malformed, or issued for a different query). |
403 | FORBIDDEN | The key acts as a Member with neither Schedule.Viewer nor Schedule.Creator. |
404 | SCHEDULE_NOT_FOUND | No schedule with that id in this workspace. |
Get a Run
Returns one run, with its checklist if it has one.
GET /v3/schedules/{id}/runs/{runId}
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer | Yes | The schedule id. |
runId | path | integer | Yes | The run id, from List a Schedule's Runs. |
Example
GET https://api.checkflow.io/v3/schedules/318/runs/52107
X-API-KEY: your-api-key-here
HTTP/1.1 200 OK
Returns a Run object, as in the example there. In that example checklist.name differs from checklistName because somebody renamed the checklist after it was created.
Responses
| Status | Code | When |
|---|---|---|
200 | — | The run. |
400 | VALIDATION_ERROR | field is id or runId: not a positive integer. |
403 | FORBIDDEN | The key acts as a Member with neither Schedule.Viewer nor Schedule.Creator. |
404 | SCHEDULE_NOT_FOUND | No schedule with that id in this workspace. |
404 | SCHEDULE_RUN_NOT_FOUND | The schedule exists but has no run with that id. |
Notes
A run is looked up within the schedule named in the path. A run id belonging to a different schedule is a 404. The two error codes tell you which id was wrong. Both ids are plain integers, so SCHEDULE_RUN_NOT_FOUND often means they were swapped.
Skip a Run
Deletes one run that has not produced a checklist, so the schedule skips that occurrence. This is the only way to skip a single occurrence of a repeating schedule. It also removes a missed run.
DELETE /v3/schedules/{id}/runs/{runId}
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer | Yes | The schedule id. |
runId | path | integer | Yes | The run id. |
Example
Skipping the Monday of Christmas week:
DELETE https://api.checkflow.io/v3/schedules/318/runs/52118
X-API-KEY: your-api-key-here
Responds 204 No Content with no body.
Responses
| Status | Code | When |
|---|---|---|
204 | — | The run was deleted. |
400 | VALIDATION_ERROR | field is id or runId: not a positive integer. |
403 | FORBIDDEN | The key acts as a Member without Schedule.Creator, or without permission to run checklists from the schedule's template. |
404 | SCHEDULE_NOT_FOUND | No schedule with that id in this workspace. |
404 | SCHEDULE_RUN_NOT_FOUND | The schedule has no run with that id. |
409 | CONFLICT | The run has already produced a checklist. The message names the checklist's key. |
Notes
- A skipped run stays skipped until the series is rebuilt. Any update with
runsRegenerated: truebrings it back. - A run that already has a checklist cannot be deleted this way. Deleting the run would leave the checklist with no record of where it came from. Delete the checklist with
DELETE /v3/checklists/{key}instead, and the run is removed with it.
Create a Run's Checklist Now
Creates the checklist for a run straight away, without waiting for the scheduled time. It is also the way to recover a missed run.
POST /v3/schedules/{id}/runs/{runId}/checklist
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer | Yes | The schedule id. |
runId | path | integer | Yes | The run id. |
Request Body
This endpoint takes no request body.
Example
Creating the 26 October checklist on 22 October:
POST https://api.checkflow.io/v3/schedules/318/runs/52110/checklist
X-API-KEY: your-api-key-here
HTTP/1.1 201 Created
{
"id": 52110,
"scheduleId": 318,
"status": "created",
"checklistName": "Invoice Review — 2026-10-26",
"scheduledDateTime": "2026-10-26T09:00:00",
"scheduledDateTimeUtc": "2026-10-26T09:00:00Z",
"timeZone": "Europe/London",
"checklist": {
"key": "e3b6f18c-9d42-4a7e-b5c0-2f8a6d91c437",
"name": "Invoice Review — 2026-10-26",
"url": "https://acme.checkflow.io/Checklist/Index?checklistKey=e3b6f18c-9d42-4a7e-b5c0-2f8a6d91c437"
},
"startedDateTime": "2026-10-22T10:15:42Z",
"checklistCreated": true
}
Sending the same request again returns 200 OK with the same run and "checklistCreated": false.
Responses
| Status | Code | When |
|---|---|---|
201 | — | This request created the checklist. checklistCreated is true. |
200 | — | The run already had a checklist, and nothing was done. checklistCreated is false. |
400 | VALIDATION_ERROR | field is id or runId: not a positive integer. |
403 | FORBIDDEN | The key acts as a Member without Schedule.Creator, or without permission to run checklists from the schedule's template. |
404 | SCHEDULE_NOT_FOUND | No schedule with that id in this workspace. |
404 | SCHEDULE_RUN_NOT_FOUND | The schedule has no run with that id. |
404 | TEMPLATE_NOT_FOUND | The schedule's template no longer exists. |
409 | CONFLICT | The schedule's template is archived. This is also why the schedule's runs are being missed. |
Notes
- The request is safe to repeat: it asks for the state "this run has a checklist", and nothing is ever created twice. If the scheduling job reaches the same run at the same moment, the request discards its own checklist and returns
200with the job's. - The checklist gets the run's
checklistNameand the schedule's parameter values, just as the job would give it. - The checklist starts now, and its scheduled time stays at the run's due time. So a checklist created a week early is not dated into the future.
startedDateTimeon the run shows the difference. - This works on an inactive schedule too.
- The new-checklist webhook fires. Unlike a checklist the job creates, no team notification is sent.
MCP Equivalents
Each route has an MCP tool, listed in Endpoints. The differences:
create_scheduleandupdate_scheduletakestartDateTimeas a string, with the same wall-clock rules.- In
update_schedule, an emptychecklistNameFormatresets the pattern to the template's name. Leaving an argument out leaves that field unchanged. create_scheduleaccepts anidempotencyKeyargument.
See Schedule Tools.
Related Pages
- Create a Schedule — the same schedules on the Scheduling screen.
- Managing Schedules — pausing, deleting and what happens when a template changes.
- Checklists — reading and deleting the checklists a schedule creates.
- Templates — finding a template's key and parameters.
- Webhooks — being told when a scheduled checklist is created.