Templates
A Template is the reusable definition a checklist is started from: its tasks, the fields on each task, the conditions that show and hide them, due date rules, assignments and custom notifications. Fields are the task's input controls — see Control Types for what each kind does.
Templates are versioned. Every change to a template publishes a new version under the same key, and the key names the template rather than any one version of it: reading a template by key gives you the latest version unless you ask for another. A checklist keeps the version it was started from, so publishing a version never changes work in progress — upgrades are how running checklists are moved on. See Template Versioning for the concept.
There is no route that edits part of a template, renames one or deletes one. You change a template by publishing a new version of the whole thing (a template's name and description belong to a version too), and you retire one by archiving it, which is reversible.
Every request acts as the member the API key acts as — see Authentication. What a key may do depends on who that is:
- A key acting as an Administrator may do everything on this page, for any template.
- A key acting as the workspace may do the same, archiving and unarchiving included.
- A key acting as a Member needs the permissions below. They combine the Member's Template.Creator permission with their permission on the Library folder the template is filed in (see Template Permissions).
| Operation | What a Member needs |
|---|---|
| List templates | Nothing. The list is not filtered by folder permission: it names every template in the workspace. |
Read one template — get it, list its versions, export it, list its permissions, validate a document forTemplate it | Any folder permission except Deny. |
| Create a template | Template.Creator. |
| Create a version, set its permissions | Template.Creator, and Edit on the template's folder. |
| Copy a template | Template.Creator, and Edit on the template's folder, as the app's Duplicate needs. |
| List checklist upgrades, upgrade checklists | Edit on the template's folder. |
| Archive or unarchive a template | Cannot. Archiving is limited to Administrators. |
A template in no folder has no folder rule to fail, and neither does an archived one: where the table asks for Edit or refuses Deny, such a template passes. Likewise a folder that has no rule naming the Member counts as passing, as it does in the app.
A refusal is 403 FORBIDDEN, and its message says what is missing. It comes after the template is looked up, so another workspace's template is still 404 TEMPLATE_NOT_FOUND. A write refused for permission is refused before the document is read, so a Member who may not save a template gets the 403 rather than a 422.
Every write on this page accepts an Idempotency-Key header (see Idempotency), and every route on this page is charged to the standard rate limit budget.
Authoring a Template
A template is written as a template document: one JSON object holding the whole template, in which tasks, fields, conditions and notifications carry a ref — a name you choose — that other parts of the document point at. Members, groups, tags, linked templates and Data Sets are named rather than numbered and are resolved against your workspace. The format is described in full on Template Documents, and the JSON Schema, the per-field-type schemas and the authoring guide are served by the schema routes.
There are two ways to write one:
- In one request. Send the finished document to
POST /v3/templates. Nothing is saved unless all of it can be; a document with any error is refused whole with422 TEMPLATE_INVALIDand a list of every problem. - As a draft. Build the document up over several requests in a draft, which is stored but not yet a template, then commit it. A draft can start empty or from an existing template, and can be validated at any point. Use a draft when the document is too large to send at once, or is being assembled step by step.
Whichever you choose, the working loop is the same:
- Compose the document, looking up each field type's properties with
GET /v3/schema/content-types/{type}. - Send it to
POST /v3/templates/validate, fix what it reports, and validate again. Validation is the only place warnings are reported — the routes that write a template accept a document with warnings and do not mention them. - Create the template.
To change a template afterwards:
- Export it as a document.
- Change the part that is wrong, keeping every
idthe export wrote. - Validate it with
forTemplateset to the template's key. - Publish it as a new version.
- If running checklists should follow, upgrade them.
To roll a template back, export the older version and publish it as a new version. To duplicate a template, copy it — posting an exported document to POST /v3/templates is refused, because the ids in it describe a template that already exists.
Endpoints
| Method | Path | Description | MCP tool |
|---|---|---|---|
GET | /v3/templates | List templates | list_templates |
GET | /v3/templates/{key} | Get a template | get_template |
POST | /v3/templates | Create a template | create_template |
POST | /v3/templates/validate | Validate a template document | validate_template |
GET | /v3/templates/{key}/document | Export a template document | export_template |
POST | /v3/templates/{key}/versions | Create a template version | create_template_version |
GET | /v3/templates/{key}/versions | List template versions | list_template_versions |
POST | /v3/templates/{key}/copy | Copy a template | copy_template |
POST | /v3/templates/{key}/archive | Archive a template | set_template_archived |
DELETE | /v3/templates/{key}/archive | Unarchive a template | set_template_archived |
GET | /v3/templates/{key}/permissions | List template permissions | list_template_permissions |
PUT | /v3/templates/{key}/permissions | Set template permissions | set_template_permissions |
GET | /v3/templates/{key}/upgrades | List checklist upgrades | list_checklist_upgrades |
POST | /v3/templates/{key}/upgrades | Upgrade checklists | upgrade_checklists |
The Template Object
One row of the template list. It describes one version of a template — the latest, unless you asked for every version — and carries no tasks.
| Field | Type | Description |
|---|---|---|
key | string | The template's key (GUID). Every version of a template has the same key. |
name | string | The template's name, as of this version. |
description | string | The template's description, as of this version. Absent when it has none. |
version | integer | The version this row describes. The first version is 1. |
isArchived | boolean | Whether the template is archived. Always false unless you asked for archived templates. |
url | string | The template in the CheckFlow app. |
createdDateTime | string | When this version was published. |
createdBy | object | Who published this version: name and email. Absent when that person has left the workspace. |
{
"key": "3f2b8c1e-7a4d-4e5b-9c61-2d8f0a7b6e14",
"name": "Invoice Review",
"description": "Check, approve and pay a supplier invoice.",
"version": 1,
"isArchived": false,
"url": "https://app.checkflow.io/Template/Index?templateKey=3f2b8c1e-7a4d-4e5b-9c61-2d8f0a7b6e14",
"createdDateTime": "2026-09-14T09:12:44.107Z",
"createdBy": {
"name": "Sarah Chen",
"email": "sarah.chen@acme.example"
}
}
The Template Detail Object
A template with its tasks and fields, as Get a Template and every route that creates or changes a template return it. This is the shape a checklist is started against — the task and field keys here are the ones Checklist Tasks and Task Fields take. It is not the shape a template is authored in: a field's full configuration (its drop-down items, file limits, conditions and so on) is only in the exported document.
| Field | Type | Description |
|---|---|---|
key | string | The template's key (GUID). |
name | string | The template's name, as of this version. |
description | string | The template's description. Absent when it has none. |
version | integer | The version this object describes. |
latestVersion | integer | The template's highest version number — what the key on its own resolves to. |
isLatest | boolean | Whether version is the latest version. |
isArchived | boolean | Whether the template is archived. |
url | string | This version in the CheckFlow app. The latest version is linked by key and an older one by its own id, so an older version's link opens that version. |
createdDateTime | string | When this version was published. |
createdBy | object | Who published this version: name and email. Absent when that person has left the workspace. |
tasks | array | The tasks and headings, in order. See Task. |
Task
| Field | Type | Description |
|---|---|---|
key | string | The task's key (GUID). |
name | string | The task's name. |
isHeading | boolean | true for a heading — a divider that is never completed. |
order | integer | The task's position, starting at 1. |
fields | array | The task's fields, in order. See Field. |
Field
| Field | Type | Description |
|---|---|---|
key | string | The field's key (GUID). Keys survive a new version when the version document keeps the field's id. |
name | string | The field's label. A field with no label is named after its type and position, for example Text Content at Position 1. |
type | string | The content type, for example ShortText, DropDown, Date or Members. The full list is on Template Documents. |
isRequired | boolean | Whether the task cannot be completed until the field is answered. |
mode | string | Date fields only: date, time or date-and-time — what the picker asks for. |
regex | string | ShortText fields with a pattern only. Answers that do not match are refused. Reported exactly as the template stores it, which may include the slashes the editor wraps a pattern in; the API trims them before applying the pattern, and you should too. |
regexMessage | string | The template's message for an answer that does not match regex. Absent when there is no pattern or no message. |
selectionMode | string | Members fields only: single or multiple. A single field refuses a value naming two people. |
dataSet | object | DropDown, MultiChoice, SubTasks and Table fields that read from a Data Set only. See Data Set Link. |
Data Set Link
A field linked to a Data Set is answered with a record of the linked view rather than with an option's text, so the link is reported on the field.
| Field | Type | Description |
|---|---|---|
key | string | The Data Set's key. |
name | string | The Data Set's name. |
viewKey | string | The view whose records are the choices. |
viewName | string | The view's name. |
displayFieldKey | string | The Data Set field whose value each choice shows. Absent on a Table. |
displayFieldName | string | That field's name. |
prePopulateRows | boolean | Table only: whether a checklist starts with one row per record. Always present, and false on the other three types. |
columns | array | Table only: fieldKey, fieldName and column (the table column the field fills). |
{
"key": "8d70632a-e08f-4afc-8939-6fd6f30f81cf",
"name": "Supplier",
"type": "DropDown",
"isRequired": true,
"dataSet": {
"key": "7abef6d5-0ab6-4b97-b868-e9126993a8b2",
"name": "Suppliers",
"viewKey": "92d9a6c9-0481-469c-9802-2b815219d6e8",
"viewName": "Active suppliers",
"displayFieldKey": "18a00c98-128f-450a-84fa-8b5b29a457e0",
"displayFieldName": "Supplier name",
"prePopulateRows": false
}
}
Example
{
"key": "3f2b8c1e-7a4d-4e5b-9c61-2d8f0a7b6e14",
"name": "Invoice Review",
"description": "Check, approve and pay a supplier invoice.",
"version": 1,
"latestVersion": 1,
"isLatest": true,
"isArchived": false,
"url": "https://app.checkflow.io/Template/Index?templateKey=3f2b8c1e-7a4d-4e5b-9c61-2d8f0a7b6e14",
"createdDateTime": "2026-09-14T09:12:44.107Z",
"createdBy": {
"name": "Sarah Chen",
"email": "sarah.chen@acme.example"
},
"tasks": [
{
"key": "3c99f258-45bb-4997-9d81-f97468e5ae9f",
"name": "Check the invoice",
"isHeading": false,
"order": 1,
"fields": [
{
"key": "9b71b832-a195-4d33-877e-3e1017fcd406",
"name": "Invoice number",
"type": "ShortText",
"isRequired": true,
"regex": "^INV-[0-9]{4}$",
"regexMessage": "An invoice number is INV- followed by four digits."
},
{
"key": "c1715bff-2066-4b2c-af25-3c37749df4b5",
"name": "Invoice amount",
"type": "DropDown",
"isRequired": true
}
]
},
{
"key": "c1cb7d14-0fa9-4a5e-8e08-77e93d29102c",
"name": "Get director approval",
"isHeading": false,
"order": 2,
"fields": []
},
{
"key": "f997d478-cb52-4efc-bfc2-3e318decadf2",
"name": "Pay the invoice",
"isHeading": false,
"order": 3,
"fields": []
}
]
}
The Template Version Object
One row of a template's version history.
| Field | Type | Description |
|---|---|---|
key | string | The template's key — the same on every row. |
version | integer | The version number. Pass it as version to the read routes. |
isLatest | boolean | Whether this is the latest version. |
name | string | The template's name in this version. |
description | string | The template's description in this version. Absent when it had none. |
createdDateTime | string | When this version was published. |
createdBy | object | Who published it: name and email. Absent when that person has left the workspace. |
url | string | This version in the CheckFlow app. |
{
"key": "ae72d986-884f-4c87-abeb-669e0543b59c",
"version": 2,
"isLatest": true,
"name": "Supplier Onboarding",
"description": "Set up a new supplier before their first invoice is paid.",
"createdDateTime": "2026-09-21T14:05:31.62Z",
"createdBy": {
"name": "James Okafor",
"email": "james.okafor@acme.example"
},
"url": "https://app.checkflow.io/Template/Index?templateId=48214"
}
The Template Permission Object
One rule about who can see and run the checklists made from a template. In the app these are the template's Checklist Permissions. They are about checklists, not about the template: none of them lets anyone edit the template.
| Field | Type | Description |
|---|---|---|
assigneeType | string | AllUsers (the whole workspace), TeamMember or Group. |
assigneeId | integer | The member's or group's id. Absent for AllUsers. |
name | string | The member's or group's name, or All Users. Ignored when you send a rule. |
permission | string | RunAndView, ViewOnly, RunAndViewAssigned or ViewAssignedOnly. The two Assigned values limit the rule to checklists in which the assignee has at least one task, and one such task opens the whole checklist to them. |
{
"assigneeType": "Group",
"assigneeId": 87,
"name": "Finance Team",
"permission": "RunAndView"
}
The Validation Result Object
What Validate a Template Document returns.
| Field | Type | Description |
|---|---|---|
valid | boolean | Whether the document would be accepted. Warnings do not make it false. |
errorCount | integer | How many violations would refuse the document. |
warningCount | integer | How many violations are worth knowing about and would not refuse it. |
violations | array | Every violation found, errors and warnings together, in the order the document was read. See Violation. |
Violation
The same object appears in the violations array of a 422 TEMPLATE_INVALID error.
| Field | Type | Description |
|---|---|---|
path | string | A JSONPath into the document as you sent it, for example $.tasks[0].fields[1].regex. |
code | string | What kind of problem it is, for example unknown_ref or assignee_not_found. Branch on this rather than on message, which may be reworded. The codes are listed on Template Documents. |
message | string | What is wrong, in a sentence. |
hint | string | What was probably meant, or what to do about it. Absent when there is nothing useful to suggest. |
severity | string | error refuses the document; warning does not. |
Warnings describe documents that can be stored but probably do not do what you meant — for example never_shown (something starts hidden and no rule ever shows it), never_restored (a rule shows or hides something and nothing ever puts it back), never_sent (an email with no recipient), circular_reference (a template that starts a copy of itself automatically) and data_set_link_removed (a field that read from a Data Set in the version before and does not in this one).
{
"path": "$.conditions[1].when.fieldRef",
"code": "unknown_ref",
"message": "No field with ref 'amount_bnad' exists in this document.",
"hint": "Did you mean 'amount_band'?",
"severity": "error"
}
The Upgrade Candidate Object
A running checklist that is not on the version an upgrade would move it to.
| Field | Type | Description |
|---|---|---|
key | string | The checklist's key. |
name | string | The checklist's name. |
url | string | The checklist in the CheckFlow app. |
status | string | Scheduled, InProgress or Complete. |
startDateTime | string | When the checklist started, or is due to start. Absent when it has no start date. |
version | integer | The version of the template the checklist is on now. |
targetVersion | integer | The version an upgrade would move it to. |
{
"key": "9db5bb2e-020c-480f-999a-f5ad1426583f",
"name": "Supplier Onboarding — Northwind Supplies",
"url": "https://app.checkflow.io/Checklist/Index?checklistKey=9db5bb2e-020c-480f-999a-f5ad1426583f",
"status": "InProgress",
"startDateTime": "2026-09-18T08:00:00Z",
"version": 1,
"targetVersion": 2
}
The Upgrade Result Object
What Upgrade Checklists returns: what was handed to the background worker, and what was left where it was.
| Field | Type | Description |
|---|---|---|
templateKey | string | The template's key. |
templateName | string | The template's name in the target version. |
version | integer | The version the queued checklists are being moved to. |
queued | integer | How many checklists were queued. |
checklistKeys | array | The keys of the queued checklists. Empty when none were queued. |
skipped | array | The checklists you named that were not queued, each with checklistKey and reason. Empty when every named checklist was queued, and always empty when you sent all: true. |
{
"templateKey": "ae72d986-884f-4c87-abeb-669e0543b59c",
"templateName": "Supplier Onboarding",
"version": 2,
"queued": 1,
"checklistKeys": [
"9db5bb2e-020c-480f-999a-f5ad1426583f"
],
"skipped": [
{
"checklistKey": "e7d714ea-f3f4-4cd7-bf56-fdcc5be6f56c",
"reason": "already on version 2"
}
]
}
List Templates
Returns the workspace's templates, one row per template at its latest version, leaving archived templates out. Use it to find a template's key before starting a checklist or schedule from it.
The list is not filtered by the acting member's permissions. A key that acts as a Member lists every template, including one in a Library folder where the Member has Deny — though reading that template by its key is refused with 403.
GET /v3/templates
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
includeAllVersions | query | boolean | No | true returns a row for every version of every template instead of one per template. Default false. For one template's history, List Template Versions is the better call. |
includeArchived | query | boolean | No | true includes archived templates, marked isArchived: true. Default false. This is how you find an archived template's key to unarchive it. |
Only the value true (in any letter case) switches either option on; any other value is read as false.
This list is not paged: every template comes back in one response, with hasMore always false and total the number of rows. It takes no pageSize, after or sort.
Example
GET https://api.checkflow.io/v3/templates
X-API-KEY: your-api-key-here
HTTP/1.1 200 OK
{
"items": [
{
"key": "7279c5ac-30a3-40be-babd-b0e3566e7d01",
"name": "Employee Onboarding",
"description": "Everything a new starter needs in their first week.",
"version": 4,
"isArchived": false,
"url": "https://app.checkflow.io/Template/Index?templateKey=7279c5ac-30a3-40be-babd-b0e3566e7d01",
"createdDateTime": "2026-06-02T11:47:09.3Z"
},
{
"key": "3f2b8c1e-7a4d-4e5b-9c61-2d8f0a7b6e14",
"name": "Invoice Review",
"description": "Check, approve and pay a supplier invoice.",
"version": 1,
"isArchived": false,
"url": "https://app.checkflow.io/Template/Index?templateKey=3f2b8c1e-7a4d-4e5b-9c61-2d8f0a7b6e14",
"createdDateTime": "2026-09-14T09:12:44.107Z",
"createdBy": {
"name": "Sarah Chen",
"email": "sarah.chen@acme.example"
}
},
{
"key": "ae72d986-884f-4c87-abeb-669e0543b59c",
"name": "Supplier Onboarding",
"description": "Set up a new supplier before their first invoice is paid.",
"version": 2,
"isArchived": false,
"url": "https://app.checkflow.io/Template/Index?templateKey=ae72d986-884f-4c87-abeb-669e0543b59c",
"createdDateTime": "2026-09-21T14:05:31.62Z",
"createdBy": {
"name": "James Okafor",
"email": "james.okafor@acme.example"
}
}
],
"hasMore": false,
"total": 3
}
The first row has no createdBy because the member who published that version has since left the workspace. The template is still listed.
Responses
| Status | Code | When |
|---|---|---|
200 | — | The templates. |
Get a Template
Returns one template with its tasks and fields, at the latest version or at the version you name. Use it to find the task and field keys a checklist will have; use Export a Template Document when you need the template's full configuration.
GET /v3/templates/{key}
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
key | path | string | Yes | The template's key. |
version | query | integer | No | The version to read, 1 or greater. Omit for the latest. Version numbers come from List Template Versions. |
A version that is not a whole number, or is 0 or less, is refused rather than read as "the latest".
Example
GET https://api.checkflow.io/v3/templates/ae72d986-884f-4c87-abeb-669e0543b59c?version=1
X-API-KEY: your-api-key-here
HTTP/1.1 200 OK
{
"key": "ae72d986-884f-4c87-abeb-669e0543b59c",
"name": "Supplier Onboarding",
"description": "Set up a new supplier before their first invoice is paid.",
"version": 1,
"latestVersion": 2,
"isLatest": false,
"isArchived": false,
"url": "https://app.checkflow.io/Template/Index?templateId=48213",
"createdDateTime": "2026-08-03T10:20:15.8Z",
"createdBy": {
"name": "Sarah Chen",
"email": "sarah.chen@acme.example"
},
"tasks": [
{
"key": "9ecc4a77-4f3d-4cda-af7e-c16b9c967cc9",
"name": "Collect supplier details",
"isHeading": false,
"order": 1,
"fields": [
{
"key": "37a08a2f-33de-4465-ae20-c94b07050e5c",
"name": "Supplier name",
"type": "ShortText",
"isRequired": true
}
]
}
]
}
Returns a Template Detail object. isLatest: false and latestVersion: 2 say that a newer version exists.
Responses
| Status | Code | When |
|---|---|---|
200 | — | The template. |
400 | VALIDATION_ERROR | key is not a GUID (field: key), or version is not a whole number of 1 or more (field: version). |
403 | FORBIDDEN | The key acts as a Member whose permission on the Library folder the template is in is Deny. |
404 | TEMPLATE_NOT_FOUND | No template in this workspace has that key, or the template has no such version. The message says which. |
Notes
- An archived template is still returned, with
isArchived: true.
Create a Template
Creates a template from a template document — the whole template, tasks, fields, conditions, due dates and custom notifications included — in one request. The new template is at version 1.
POST /v3/templates
Parameters
This endpoint takes no parameters.
Request Body
The body is a template document. Only name is required; a document with nothing but a name creates an empty template. The document below creates a three-task invoice review: the invoice number must match a pattern, a director's approval task is shown only for large invoices, and payment falls due two days after the invoice is checked.
{
"name": "Invoice Review",
"description": "Check, approve and pay a supplier invoice.",
"tags": ["q3-audit"],
"permissions": [
{ "type": "Group", "name": "Finance Team", "permission": "RunAndView" }
],
"tasks": [
{
"ref": "check_invoice",
"name": "Check the invoice",
"assignTo": [{ "name": "James Okafor" }],
"fields": [
{
"ref": "invoice_number",
"type": "ShortText",
"label": "Invoice number",
"isRequired": true,
"regex": "^INV-[0-9]{4}$",
"regexMessage": "An invoice number is INV- followed by four digits."
},
{
"ref": "amount_band",
"type": "DropDown",
"label": "Invoice amount",
"isRequired": true,
"items": [
{ "ref": "under_5k", "text": "Under 5,000" },
{ "ref": "over_5k", "text": "5,000 or more" }
]
}
]
},
{
"ref": "director_approval",
"name": "Get director approval",
"hiddenByDefault": true,
"fields": []
},
{
"ref": "pay_invoice",
"name": "Pay the invoice",
"halt": "task",
"dueDate": {
"rule": "task-completed",
"anchorRef": "check_invoice",
"offset": { "days": 2 }
},
"fields": []
}
],
"conditions": [
{
"ref": "reset_approval",
"when": { "fieldRef": "amount_band", "operator": "has-any-value" },
"then": { "effect": "hide", "tasks": ["director_approval"] }
},
{
"ref": "large_invoice",
"when": { "fieldRef": "amount_band", "operator": "is", "value": "over_5k" },
"then": { "effect": "show", "tasks": ["director_approval"] }
}
]
}
The first condition is a reset: whenever the amount is answered it hides the approval task, and the rule after it shows the task again only for a large invoice. Without it, changing the answer from 5,000 or more back to Under 5,000 would leave the task showing, and validation would warn never_restored.
The top-level properties are summarised below. Tasks, fields, conditions, due dates and notifications are described on Template Documents, and each field type's own properties (label, regex, items and the rest) by GET /v3/schema/content-types/{type}.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | The template's name, at most 100 characters. |
description | string | No | The template's description. |
settings | object | No | Time zone, date and time formats, the feature switches and the Library folder. Every setting has the default a new template gets in the app. |
tags | array | No | The template's tags, by name. A tag that does not exist yet is created. |
permissions | array | No | Who can see and run the template's checklists: type (AllUsers, TeamMember or Group) with an id or a name, and a permission. |
parameters | array | No | Values asked for when a checklist is started. |
tasks | array | No | The tasks and headings, in display order, each with its fields. |
conditions | array | No | Rules that show and hide tasks and fields. |
notifications | array | No | Custom notifications. |
Leave every id out. An id on a task, field, parameter or condition is refused with the violation code id_not_allowed: ids only come from an export, and a document carrying them describes a template that already exists. Copy that template instead, or publish the document as a new version of it.
Example
POST https://api.checkflow.io/v3/templates
X-API-KEY: your-api-key-here
Content-Type: application/json
{
"name": "Invoice Review",
"description": "Check, approve and pay a supplier invoice.",
"tags": ["q3-audit"],
"permissions": [
{ "type": "Group", "name": "Finance Team", "permission": "RunAndView" }
],
"tasks": [
{
"ref": "check_invoice",
"name": "Check the invoice",
"assignTo": [{ "name": "James Okafor" }],
"fields": [
{
"ref": "invoice_number",
"type": "ShortText",
"label": "Invoice number",
"isRequired": true,
"regex": "^INV-[0-9]{4}$",
"regexMessage": "An invoice number is INV- followed by four digits."
},
{
"ref": "amount_band",
"type": "DropDown",
"label": "Invoice amount",
"isRequired": true,
"items": [
{ "ref": "under_5k", "text": "Under 5,000" },
{ "ref": "over_5k", "text": "5,000 or more" }
]
}
]
},
{
"ref": "director_approval",
"name": "Get director approval",
"hiddenByDefault": true,
"fields": []
},
{
"ref": "pay_invoice",
"name": "Pay the invoice",
"halt": "task",
"dueDate": {
"rule": "task-completed",
"anchorRef": "check_invoice",
"offset": { "days": 2 }
},
"fields": []
}
],
"conditions": [
{
"ref": "reset_approval",
"when": { "fieldRef": "amount_band", "operator": "has-any-value" },
"then": { "effect": "hide", "tasks": ["director_approval"] }
},
{
"ref": "large_invoice",
"when": { "fieldRef": "amount_band", "operator": "is", "value": "over_5k" },
"then": { "effect": "show", "tasks": ["director_approval"] }
}
]
}
HTTP/1.1 201 Created
{
"key": "3f2b8c1e-7a4d-4e5b-9c61-2d8f0a7b6e14",
"name": "Invoice Review",
"description": "Check, approve and pay a supplier invoice.",
"version": 1,
"latestVersion": 1,
"isLatest": true,
"isArchived": false,
"url": "https://app.checkflow.io/Template/Index?templateKey=3f2b8c1e-7a4d-4e5b-9c61-2d8f0a7b6e14",
"createdDateTime": "2026-09-14T09:12:44.107Z",
"createdBy": {
"name": "Sarah Chen",
"email": "sarah.chen@acme.example"
},
"tasks": [
{
"key": "3c99f258-45bb-4997-9d81-f97468e5ae9f",
"name": "Check the invoice",
"isHeading": false,
"order": 1,
"fields": [
{
"key": "9b71b832-a195-4d33-877e-3e1017fcd406",
"name": "Invoice number",
"type": "ShortText",
"isRequired": true,
"regex": "^INV-[0-9]{4}$",
"regexMessage": "An invoice number is INV- followed by four digits."
},
{
"key": "c1715bff-2066-4b2c-af25-3c37749df4b5",
"name": "Invoice amount",
"type": "DropDown",
"isRequired": true
}
]
},
{
"key": "c1cb7d14-0fa9-4a5e-8e08-77e93d29102c",
"name": "Get director approval",
"isHeading": false,
"order": 2,
"fields": []
},
{
"key": "f997d478-cb52-4efc-bfc2-3e318decadf2",
"name": "Pay the invoice",
"isHeading": false,
"order": 3,
"fields": []
}
]
}
Returns a Template Detail object describing the template as it was stored. The keys in it are the ones checklists started from this version will have.
Responses
| Status | Code | When |
|---|---|---|
201 | — | The template was created at version 1. |
400 | VALIDATION_ERROR | The body is not valid JSON, or is empty. |
403 | FORBIDDEN | The key acts as a Member without Template.Creator. |
422 | TEMPLATE_INVALID | The document was read and cannot be stored. violations lists every problem. Nothing was written, not even new tags. |
Refused Documents
A document with any error is refused whole with 422 and the code TEMPLATE_INVALID. The error body carries a violations array in addition to the usual fields, listing every problem found in one pass — errors and any warnings — so one round trip is enough to see everything that needs fixing. The message counts only the errors.
If the second condition above named the field amount_bnad by mistake, the answer would be:
HTTP/1.1 422 Unprocessable Entity
{
"error": {
"code": "TEMPLATE_INVALID",
"message": "The template document has 1 problem.",
"requestId": "req_4b8e2f1a9c3d47e6b0a5d9c2e7f18a36",
"violations": [
{
"path": "$.conditions[1].when.fieldRef",
"code": "unknown_ref",
"message": "No field with ref 'amount_bnad' exists in this document.",
"hint": "Did you mean 'amount_band'?",
"severity": "error"
},
{
"path": "$.tasks[1].hiddenByDefault",
"code": "never_shown",
"message": "The task 'director_approval' starts hidden and no rule shows it, so it will never appear.",
"hint": "Add a rule with an effect of 'show' naming 'director_approval' in 'then.tasks', or take 'hiddenByDefault' off.",
"severity": "warning"
}
]
}
}
See The Violation Object and Errors.
Notes
- Everyone in the workspace except you is notified that you created the template, in the same words the template editor uses.
- A document that is accepted with warnings is created without mentioning them. Run it through validation first to see them.
- Array order is display order, for tasks and for the fields within a task.
Validate a Template Document
Reads a template document and reports everything wrong with it, without creating or changing anything. It applies the same rules as creating a template and publishing a version, so a document it calls valid is one those routes will accept.
POST /v3/templates/validate
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
forTemplate | query | string | No | The key of the template this document will become a new version of. Omit it to check the document as a new template. |
forTemplate changes the answer, so send it whenever the document is headed for Create a Template Version. With it, the id values in the document are read against that template, and a field that has dropped its dataSet since that version is warned about with data_set_link_removed. The answer can differ: an Image field needs a stored picture that a document cannot carry, so it is accepted when it keeps its id in a version of the template it came from, and refused as part of a new template.
Request Body
A template document, the same shape Create a Template takes.
Example
POST https://api.checkflow.io/v3/templates/validate
X-API-KEY: your-api-key-here
Content-Type: application/json
{
"name": "Invoice Review",
"tasks": [
{
"ref": "check_invoice",
"name": "Check the invoice",
"fields": [
{
"ref": "amount_band",
"type": "DropDown",
"label": "Invoice amount",
"isRequired": true,
"items": [
{ "ref": "under_5k", "text": "Under 5,000" },
{ "ref": "over_5k", "text": "5,000 or more" }
]
}
]
},
{ "ref": "director_approval", "name": "Get director approval", "hiddenByDefault": true, "fields": [] }
],
"conditions": [
{
"ref": "large_invoice",
"when": { "fieldRef": "amount_band", "operator": "is", "value": "over_5k" },
"then": { "effect": "show", "tasks": ["director_approval"] }
}
]
}
HTTP/1.1 200 OK
{
"valid": true,
"errorCount": 0,
"warningCount": 1,
"violations": [
{
"path": "$.conditions[0].then",
"code": "never_restored",
"message": "This rule will show 'director_approval', and no rule on 'amount_band' will hide it again. The checklist only applies the rules that fire when an answer changes and never goes back to 'hiddenByDefault' by itself, so changing the answer afterwards leaves 'director_approval' as this rule left it.",
"hint": "Make the first rule on 'amount_band' a reset -- when 'has-any-value', hide 'director_approval' -- so every change starts from how the checklist began and the rules after it decide. If the answer can also be cleared, add the same reset for 'has-no-value'.",
"severity": "warning"
}
]
}
Returns a Validation Result object.
Responses
| Status | Code | When |
|---|---|---|
200 | — | The document was read. Check valid — a document with errors is still a 200 here. |
400 | VALIDATION_ERROR | forTemplate is not a GUID (field: forTemplate), or the body is not valid JSON or is empty. |
403 | FORBIDDEN | forTemplate names a template in a Library folder where the Member the key acts as has Deny. |
404 | TEMPLATE_NOT_FOUND | forTemplate names no template in this workspace. |
Notes
- Validation writes nothing — not even the tags a document names.
- Validating is the only way to see warnings on a document that has no errors. Creating a template or publishing a version accepts it and does not report them.
Export a Template Document
Returns a template as a template document: the shape Create a Template and Create a Template Version accept. Use it to see a template's full configuration, and as the starting point for every new version.
GET /v3/templates/{key}/document
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
key | path | string | Yes | The template's key. |
version | query | integer | No | The version to export, 1 or greater. Omit for the latest. |
Example
GET https://api.checkflow.io/v3/templates/ae72d986-884f-4c87-abeb-669e0543b59c/document
X-API-KEY: your-api-key-here
HTTP/1.1 200 OK
{
"name": "Supplier Onboarding",
"description": "Set up a new supplier before their first invoice is paid.",
"settings": {
"dateFormatId": 1,
"timeFormatId": 1,
"displayComments": true,
"displayTaskTags": true,
"isNotApplicableEnabled": true,
"isShareEmbedEnabled": true,
"isAttachedTasksEnabled": true
},
"tags": ["q3-audit"],
"permissions": [
{ "type": "Group", "id": 87, "name": "Finance Team", "permission": "RunAndView" }
],
"parameters": [],
"tasks": [
{
"ref": "collect_supplier_details",
"id": "9ecc4a77-4f3d-4cda-af7e-c16b9c967cc9",
"name": "Collect supplier details",
"isHeading": false,
"halt": "none",
"hiddenByDefault": false,
"assignTo": [
{ "type": "TeamMember", "id": 1051, "name": "Priya Patel" }
],
"assignFrom": [],
"assignedExclusively": false,
"tags": [],
"fields": [
{
"ref": "supplier_name",
"id": "37a08a2f-33de-4465-ae20-c94b07050e5c",
"type": "ShortText",
"hiddenByDefault": false,
"label": "Supplier name",
"isRequired": true
}
]
}
],
"conditions": [],
"notifications": []
}
Responses
| Status | Code | When |
|---|---|---|
200 | — | The template as a document. |
400 | VALIDATION_ERROR | key is not a GUID (field: key), or version is not a whole number of 1 or more (field: version). |
403 | FORBIDDEN | The key acts as a Member whose permission on the Library folder the template is in is Deny. |
404 | TEMPLATE_NOT_FOUND | No template in this workspace has that key, or the template has no such version. |
Notes
- Refs are invented from names. A template stores no refs, so the export makes one for every task, field and choice from its name or label (
Collect supplier detailsbecomescollect_supplier_details), numbering duplicates. Conditions and notifications have no name, so they getcondition,condition_2,notification,notification_2and so on. - Every node carries its
id. Keep them when you publish the document as a new version: the ids are what keep answers already given on running checklists attached to the same tasks and fields. Remove them, or use Copy a Template, to make a separate template. - Members, groups and linked templates are written with their names beside their ids, and a field linked to a Data Set is written with a
dataSetnaming the Data Set, view and display field, and noitems. - The document carries no key and no version — it describes a template rather than identifying one.
- An
Image,Videoor read-onlyFilefield is exported without the stored file behind it. It keeps that file only when the document goes back to the same template as a new version.
Create a Template Version
Publishes a new version of an existing template from a template document. The new version becomes the latest and is what new checklists are started from; running checklists stay on the version they were started from until you upgrade them.
POST /v3/templates/{key}/versions
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
key | path | string | Yes | The template's key. The new version is added under it. |
Request Body
The template document, describing the whole template as it should be from this version on. Only name is required. Start from an export and keep its id values: a task or field that keeps its id is the same task or field as before, and one without an id is new.
A document is the whole template, not a change to it. A template's permissions and tags belong to the template rather than to one version, so a version document that leaves out permissions or tags removes them from the template altogether — including from the versions before it. Likewise a field that keeps its id and leaves out its dataSet goes back to being a fixed list. Export the template and edit what comes back, and all of these are kept.
The version number is assigned by CheckFlow — the next one after the current latest — and cannot be chosen. Read it from the response.
Example
This adds a VAT number field to the exported document above. The existing task and field keep their id; the new field has none.
POST https://api.checkflow.io/v3/templates/ae72d986-884f-4c87-abeb-669e0543b59c/versions
X-API-KEY: your-api-key-here
Content-Type: application/json
{
"name": "Supplier Onboarding",
"description": "Set up a new supplier before their first invoice is paid.",
"tags": ["q3-audit"],
"permissions": [
{ "type": "Group", "id": 87, "name": "Finance Team", "permission": "RunAndView" }
],
"tasks": [
{
"ref": "collect_supplier_details",
"id": "9ecc4a77-4f3d-4cda-af7e-c16b9c967cc9",
"name": "Collect supplier details",
"assignTo": [
{ "type": "TeamMember", "id": 1051, "name": "Priya Patel" }
],
"fields": [
{
"ref": "supplier_name",
"id": "37a08a2f-33de-4465-ae20-c94b07050e5c",
"type": "ShortText",
"label": "Supplier name",
"isRequired": true
},
{
"ref": "vat_number",
"type": "ShortText",
"label": "VAT number",
"isRequired": true,
"regex": "^GB[0-9]{9}$",
"regexMessage": "A UK VAT number is GB followed by nine digits."
}
]
}
]
}
HTTP/1.1 201 Created
{
"key": "ae72d986-884f-4c87-abeb-669e0543b59c",
"name": "Supplier Onboarding",
"description": "Set up a new supplier before their first invoice is paid.",
"version": 2,
"latestVersion": 2,
"isLatest": true,
"isArchived": false,
"url": "https://app.checkflow.io/Template/Index?templateKey=ae72d986-884f-4c87-abeb-669e0543b59c",
"createdDateTime": "2026-09-21T14:05:31.62Z",
"createdBy": {
"name": "James Okafor",
"email": "james.okafor@acme.example"
},
"tasks": [
{
"key": "9ecc4a77-4f3d-4cda-af7e-c16b9c967cc9",
"name": "Collect supplier details",
"isHeading": false,
"order": 1,
"fields": [
{
"key": "37a08a2f-33de-4465-ae20-c94b07050e5c",
"name": "Supplier name",
"type": "ShortText",
"isRequired": true
},
{
"key": "e12b7d01-7423-45b8-b293-54221f667ed2",
"name": "VAT number",
"type": "ShortText",
"isRequired": true,
"regex": "^GB[0-9]{9}$",
"regexMessage": "A UK VAT number is GB followed by nine digits."
}
]
}
]
}
Returns a Template Detail object at the new version.
Responses
| Status | Code | When |
|---|---|---|
201 | — | The version was published. |
400 | VALIDATION_ERROR | key is not a GUID (field: key), or the body is not valid JSON or is empty. |
403 | FORBIDDEN | The key acts as a Member without Template.Creator, or whose permission on the Library folder the template is in is only View or Deny. |
404 | TEMPLATE_NOT_FOUND | No template in this workspace has that key. |
409 | CONFLICT | The template is archived. Unarchive it first. |
422 | TEMPLATE_INVALID | The document cannot be stored. violations lists every problem, and the template stays at its current version. See Refused Documents. |
Notes
- Everyone in the workspace except you is notified that you modified the template.
- The response does not report warnings, including
data_set_link_removed. Validate the document withforTemplatefirst to see them. - To rename a template, publish a version with the new
name.
List Template Versions
Returns every version of a template, newest first, with who published each. This is the only route that shows which versions exist.
GET /v3/templates/{key}/versions
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
key | path | string | Yes | The template's key. |
The list is not paged: every version comes back in one response, with hasMore always false.
Example
GET https://api.checkflow.io/v3/templates/ae72d986-884f-4c87-abeb-669e0543b59c/versions
X-API-KEY: your-api-key-here
HTTP/1.1 200 OK
{
"items": [
{
"key": "ae72d986-884f-4c87-abeb-669e0543b59c",
"version": 2,
"isLatest": true,
"name": "Supplier Onboarding",
"description": "Set up a new supplier before their first invoice is paid.",
"createdDateTime": "2026-09-21T14:05:31.62Z",
"createdBy": {
"name": "James Okafor",
"email": "james.okafor@acme.example"
},
"url": "https://app.checkflow.io/Template/Index?templateId=48214"
},
{
"key": "ae72d986-884f-4c87-abeb-669e0543b59c",
"version": 1,
"isLatest": false,
"name": "Supplier Onboarding",
"description": "Set up a new supplier before their first invoice is paid.",
"createdDateTime": "2026-08-03T10:20:15.8Z",
"createdBy": {
"name": "Sarah Chen",
"email": "sarah.chen@acme.example"
},
"url": "https://app.checkflow.io/Template/Index?templateId=48213"
}
],
"hasMore": false,
"total": 2
}
Each item is a Template Version object.
Responses
| Status | Code | When |
|---|---|---|
200 | — | The versions, newest first. |
400 | VALIDATION_ERROR | key is not a GUID (field: key). |
403 | FORBIDDEN | The key acts as a Member whose permission on the Library folder the template is in is Deny. |
404 | TEMPLATE_NOT_FOUND | No template in this workspace has that key. |
Notes
- An archived template's versions are still listed.
Copy a Template
Copies a template, and everything in it, into a new template of its own at version 1. This is the way to duplicate a template — it is the same copy the app's Duplicate button makes.
POST /v3/templates/{key}/copy
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
key | path | string | Yes | The key of the template to copy. The copy is made from its latest version. |
Request Body
{
"name": "Supplier Onboarding — EU",
"description": "Set up a new EU supplier before their first invoice is paid."
}
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | The copy's name, at most 100 characters. It may be the same as the original's. |
description | string | No | The copy's description. Omit it to keep the original's; send "" to give the copy none. |
Example
POST https://api.checkflow.io/v3/templates/ae72d986-884f-4c87-abeb-669e0543b59c/copy
X-API-KEY: your-api-key-here
Content-Type: application/json
{
"name": "Supplier Onboarding — EU",
"description": "Set up a new EU supplier before their first invoice is paid."
}
HTTP/1.1 201 Created
{
"key": "99d0227d-d5a9-42da-9317-ddda5cdf74eb",
"name": "Supplier Onboarding — EU",
"description": "Set up a new EU supplier before their first invoice is paid.",
"version": 1,
"latestVersion": 1,
"isLatest": true,
"isArchived": false,
"url": "https://app.checkflow.io/Template/Index?templateKey=99d0227d-d5a9-42da-9317-ddda5cdf74eb",
"createdDateTime": "2026-09-22T08:41:02.513Z",
"createdBy": {
"name": "Sarah Chen",
"email": "sarah.chen@acme.example"
},
"tasks": [
{
"key": "54489054-80f5-4127-b5bb-7990c5fa291e",
"name": "Collect supplier details",
"isHeading": false,
"order": 1,
"fields": [
{
"key": "9bd1844f-b751-43a7-a95a-9602fae94f7d",
"name": "Supplier name",
"type": "ShortText",
"isRequired": true
},
{
"key": "49be1345-d50b-4df4-9d5b-06792b5a15cc",
"name": "VAT number",
"type": "ShortText",
"isRequired": true,
"regex": "^GB[0-9]{9}$",
"regexMessage": "A UK VAT number is GB followed by nine digits."
}
]
}
]
}
Returns a Template Detail object for the copy.
Responses
| Status | Code | When |
|---|---|---|
201 | — | The copy was created. |
400 | VALIDATION_ERROR | key is not a GUID (field: key), or name is missing, blank or longer than 100 characters (field: name). |
403 | FORBIDDEN | The key acts as a Member without Template.Creator, or whose permission on the Library folder the template is in is only View or Deny. |
404 | TEMPLATE_NOT_FOUND | No template in this workspace has that key. |
Notes
- The copy has its own key, and so does every task, field, parameter and rule in it. Everything inside that pointed at one of those — conditions, due date anchors, task tags, dynamic assignments, parameter bindings, Data Set links,
valueFrombindings,SendEmailattachments and{{content.x}}tokens — is repointed at the copy's own, so nothing in the copy reaches back into the original. - Images, videos and read-only files are shared with the original rather than duplicated. This is what exporting a document and creating a template from it cannot do, because a document carries no files.
- An archived template can be copied, and the copy is not archived.
Archive a Template
Moves a template into the Library's archive. An archived template is left out of List Templates unless you ask for archived templates, and cannot be used to start new checklists; checklists already running from it are unaffected. Archiving applies to every version and is reversible — there is no route that deletes a template.
POST /v3/templates/{key}/archive
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
key | path | string | Yes | The template's key. |
Request Body
This endpoint takes no request body.
Example
POST https://api.checkflow.io/v3/templates/ae72d986-884f-4c87-abeb-669e0543b59c/archive
X-API-KEY: your-api-key-here
HTTP/1.1 200 OK
{
"key": "ae72d986-884f-4c87-abeb-669e0543b59c",
"name": "Supplier Onboarding",
"description": "Set up a new supplier before their first invoice is paid.",
"version": 2,
"latestVersion": 2,
"isLatest": true,
"isArchived": true,
"url": "https://app.checkflow.io/Template/Index?templateKey=ae72d986-884f-4c87-abeb-669e0543b59c",
"createdDateTime": "2026-09-21T14:05:31.62Z",
"createdBy": {
"name": "James Okafor",
"email": "james.okafor@acme.example"
},
"tasks": [
{
"key": "9ecc4a77-4f3d-4cda-af7e-c16b9c967cc9",
"name": "Collect supplier details",
"isHeading": false,
"order": 1,
"fields": [
{
"key": "37a08a2f-33de-4465-ae20-c94b07050e5c",
"name": "Supplier name",
"type": "ShortText",
"isRequired": true
},
{
"key": "e12b7d01-7423-45b8-b293-54221f667ed2",
"name": "VAT number",
"type": "ShortText",
"isRequired": true,
"regex": "^GB[0-9]{9}$",
"regexMessage": "A UK VAT number is GB followed by nine digits."
}
]
}
]
}
Returns the Template Detail object at its latest version, with isArchived: true.
Responses
| Status | Code | When |
|---|---|---|
200 | — | The template is archived. Archiving a template that is already archived also answers 200 and changes nothing. |
400 | VALIDATION_ERROR | key is not a GUID (field: key). |
403 | FORBIDDEN | The key acts as a Member. Archiving and unarchiving are limited to Administrators. |
404 | TEMPLATE_NOT_FOUND | No template in this workspace has that key. |
Notes
- An archived template can still be read, exported, listed by version and copied, but a new version cannot be published until it is unarchived.
Unarchive a Template
Takes a template back out of the Library's archive, restoring every version of it.
DELETE /v3/templates/{key}/archive
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
key | path | string | Yes | The template's key. To find an archived template's key, list templates with includeArchived=true. |
Request Body
This endpoint takes no request body.
Example
DELETE https://api.checkflow.io/v3/templates/ae72d986-884f-4c87-abeb-669e0543b59c/archive
X-API-KEY: your-api-key-here
HTTP/1.1 200 OK
{
"key": "ae72d986-884f-4c87-abeb-669e0543b59c",
"name": "Supplier Onboarding",
"description": "Set up a new supplier before their first invoice is paid.",
"version": 2,
"latestVersion": 2,
"isLatest": true,
"isArchived": false,
"url": "https://app.checkflow.io/Template/Index?templateKey=ae72d986-884f-4c87-abeb-669e0543b59c",
"createdDateTime": "2026-09-21T14:05:31.62Z",
"createdBy": {
"name": "James Okafor",
"email": "james.okafor@acme.example"
},
"tasks": [
{
"key": "9ecc4a77-4f3d-4cda-af7e-c16b9c967cc9",
"name": "Collect supplier details",
"isHeading": false,
"order": 1,
"fields": [
{
"key": "37a08a2f-33de-4465-ae20-c94b07050e5c",
"name": "Supplier name",
"type": "ShortText",
"isRequired": true
},
{
"key": "e12b7d01-7423-45b8-b293-54221f667ed2",
"name": "VAT number",
"type": "ShortText",
"isRequired": true,
"regex": "^GB[0-9]{9}$",
"regexMessage": "A UK VAT number is GB followed by nine digits."
}
]
}
]
}
Returns the Template Detail object at its latest version, with isArchived: false.
Responses
| Status | Code | When |
|---|---|---|
200 | — | The template is live. Unarchiving a template that is not archived also answers 200 and changes nothing. |
400 | VALIDATION_ERROR | key is not a GUID (field: key). |
403 | FORBIDDEN | The key acts as a Member. Archiving and unarchiving are limited to Administrators. |
404 | TEMPLATE_NOT_FOUND | No template in this workspace has that key. |
List Template Permissions
Returns who can see and run the checklists made from a template — the template's Checklist Permissions in the app.
GET /v3/templates/{key}/permissions
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
key | path | string | Yes | The template's key. |
The list is not paged.
Example
GET https://api.checkflow.io/v3/templates/3f2b8c1e-7a4d-4e5b-9c61-2d8f0a7b6e14/permissions
X-API-KEY: your-api-key-here
HTTP/1.1 200 OK
{
"items": [
{
"assigneeType": "Group",
"assigneeId": 87,
"name": "Finance Team",
"permission": "RunAndView"
}
],
"hasMore": false,
"total": 1
}
Each item is a Template Permission object.
Responses
| Status | Code | When |
|---|---|---|
200 | — | The template's permissions. |
400 | VALIDATION_ERROR | key is not a GUID (field: key). |
403 | FORBIDDEN | The key acts as a Member whose permission on the Library folder the template is in is Deny. |
404 | TEMPLATE_NOT_FOUND | No template in this workspace has that key. |
Notes
- An empty list is a real answer: only Administrators can see the template's checklists. A member named by no rule can still see the tasks they are assigned.
Set Template Permissions
Replaces a template's whole set of checklist permissions. A permission has no identity of its own, so there is nothing to change one at a time: send every rule you want the template to have, including the ones you are keeping.
PUT /v3/templates/{key}/permissions
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
key | path | string | Yes | The template's key. |
Request Body
{
"permissions": [
{ "assigneeType": "Group", "assigneeId": 87, "permission": "RunAndView" },
{ "assigneeType": "TeamMember", "assigneeId": 1051, "permission": "ViewAssignedOnly" }
]
}
| Field | Type | Required | Description |
|---|---|---|---|
permissions | array | Yes | The complete set of rules. An empty array removes every rule, leaving the checklists visible to Administrators only. |
permissions[].assigneeType | string | Yes | AllUsers, TeamMember or Group. |
permissions[].assigneeId | integer | For TeamMember and Group | The member's or group's id, from Team. Not allowed with AllUsers. |
permissions[].permission | string | Yes | RunAndView, ViewOnly, RunAndViewAssigned or ViewAssignedOnly. |
permissions[].name | string | No | Ignored. The rule is about the id. |
This route names assignees with assigneeType and assigneeId. The permissions array in a template document names them with type and id (or name) instead.
Example
PUT https://api.checkflow.io/v3/templates/3f2b8c1e-7a4d-4e5b-9c61-2d8f0a7b6e14/permissions
X-API-KEY: your-api-key-here
Content-Type: application/json
{
"permissions": [
{ "assigneeType": "Group", "assigneeId": 87, "permission": "RunAndView" },
{ "assigneeType": "TeamMember", "assigneeId": 1051, "permission": "ViewAssignedOnly" }
]
}
HTTP/1.1 200 OK
{
"items": [
{
"assigneeType": "Group",
"assigneeId": 87,
"name": "Finance Team",
"permission": "RunAndView"
},
{
"assigneeType": "TeamMember",
"assigneeId": 1051,
"name": "Priya Patel",
"permission": "ViewAssignedOnly"
}
],
"hasMore": false,
"total": 2
}
Returns the permissions as installed.
Responses
| Status | Code | When |
|---|---|---|
200 | — | The permissions were replaced. |
400 | VALIDATION_ERROR | key is not a GUID; permissions is missing (field: permissions); or a rule is invalid — an unknown assigneeType or permission, a missing assigneeId, an assigneeId sent with AllUsers, an id that is not a member or group of this workspace or the same assignee named twice. field points at the rule, for example permissions[1].assigneeId. |
403 | FORBIDDEN | The key acts as a Member without Template.Creator, or whose permission on the Library folder the template is in is only View or Deny. |
404 | TEMPLATE_NOT_FOUND | No template in this workspace has that key. Nothing is written. |
Notes
- Every rule is checked before anything is written, so a request with one bad rule changes nothing.
- Permissions apply to the template as a whole, not to one version, and changing them does not publish a version.
List Checklist Upgrades
Returns the running checklists of a template that are not on a given version — the latest, unless you name another — newest first, each with the version it is on beside the target. These are the checklists Upgrade Checklists would move, and the list is also how you watch an upgrade finish: a checklist leaves it as soon as its rebuild is done.
GET /v3/templates/{key}/upgrades
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
key | path | string | Yes | The template's key. |
version | query | integer | No | The target version, 1 or greater. Omit for the latest. Name an older version to see what a rollback would touch. |
pageSize | query | integer | No | How many to return, 1 to 100. Default 50; a value outside that range, or not a number, is also read as 50. |
after | query | string | No | The nextCursor from the previous page. A cursor is only valid with the same version and pageSize it was issued under. |
The order is fixed — newest start date first — and there is no sort parameter. total is the number of candidates in all. See Pagination.
Example
GET https://api.checkflow.io/v3/templates/ae72d986-884f-4c87-abeb-669e0543b59c/upgrades?pageSize=2
X-API-KEY: your-api-key-here
HTTP/1.1 200 OK
{
"items": [
{
"key": "9db5bb2e-020c-480f-999a-f5ad1426583f",
"name": "Supplier Onboarding — Northwind Supplies",
"url": "https://app.checkflow.io/Checklist/Index?checklistKey=9db5bb2e-020c-480f-999a-f5ad1426583f",
"status": "InProgress",
"startDateTime": "2026-09-18T08:00:00Z",
"version": 1,
"targetVersion": 2
},
{
"key": "9584a35a-1f92-43da-b54e-94db1c245d85",
"name": "Supplier Onboarding — Harbour Freight",
"url": "https://app.checkflow.io/Checklist/Index?checklistKey=9584a35a-1f92-43da-b54e-94db1c245d85",
"status": "Complete",
"startDateTime": "2026-09-02T09:30:00Z",
"version": 1,
"targetVersion": 2
}
],
"nextCursor": "Q2hLdTdQeWZ4b0x3N2ZtVjJ6UXNL",
"hasMore": true,
"total": 3
}
Each item is an Upgrade Candidate object.
Responses
| Status | Code | When |
|---|---|---|
200 | — | The candidates. |
400 | VALIDATION_ERROR | key is not a GUID (field: key); version is not a whole number of 1 or more (field: version); or after is not a valid cursor, or was issued for a different version or pageSize (field: after). |
403 | FORBIDDEN | The key acts as a Member whose permission on the Library folder the template is in is only View or Deny. |
404 | TEMPLATE_NOT_FOUND | No template in this workspace has that key, or the template has no such version. |
Notes
- Candidates are compared for difference, not order: a checklist on a newer version than the target is a candidate too, which is how a rollback is found.
- Archived checklists are left out. So is a checklist the background worker is part-way through rebuilding, which is on neither version until it finishes.
- Completed checklists are included. They are running checklists that have not been archived, and an upgrade with
all: truerebuilds them too. - If you publish another version while paging, the cursor no longer matches the latest version; start again from the first page.
Upgrade Checklists
Queues running checklists to be rebuilt on a version of their template — the latest, unless you name another — carrying across every answer that still has somewhere to go. This is the same queue and background worker as the app's option to update open checklists when a template is saved. The worker can take minutes for a template with many checklists, so the route answers 202 Accepted with what it queued, and List Checklist Upgrades is how you watch it land.
POST /v3/templates/{key}/upgrades
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
key | path | string | Yes | The template's key. |
Request Body
{
"checklistKeys": [
"9db5bb2e-020c-480f-999a-f5ad1426583f",
"e7d714ea-f3f4-4cd7-bf56-fdcc5be6f56c"
]
}
| Field | Type | Required | Description |
|---|---|---|---|
checklistKeys | array | One of the two | The checklists to upgrade, by key — at most 500. Duplicates are ignored. |
all | boolean | One of the two | true to upgrade every checklist List Checklist Upgrades would return for the target version. |
version | integer | No | The version to move them to, 1 or greater. Omit for the latest. An older version is accepted, which is how a rollout is undone. |
Send checklistKeys or all: true, not both.
Example
POST https://api.checkflow.io/v3/templates/ae72d986-884f-4c87-abeb-669e0543b59c/upgrades
X-API-KEY: your-api-key-here
Content-Type: application/json
{
"checklistKeys": [
"9db5bb2e-020c-480f-999a-f5ad1426583f",
"e7d714ea-f3f4-4cd7-bf56-fdcc5be6f56c"
]
}
HTTP/1.1 202 Accepted
Location: /v3/templates/ae72d986-884f-4c87-abeb-669e0543b59c/upgrades
Retry-After: 60
{
"templateKey": "ae72d986-884f-4c87-abeb-669e0543b59c",
"templateName": "Supplier Onboarding",
"version": 2,
"queued": 1,
"checklistKeys": [
"9db5bb2e-020c-480f-999a-f5ad1426583f"
],
"skipped": [
{
"checklistKey": "e7d714ea-f3f4-4cd7-bf56-fdcc5be6f56c",
"reason": "already on version 2"
}
]
}
Returns an Upgrade Result object. Location is the candidates list and Retry-After is how many seconds to give the worker before reading it.
Responses
| Status | Code | When |
|---|---|---|
202 | — | At least one checklist was queued. |
200 | — | Nothing was queued: every named checklist was skipped, or there were no candidates. The body has the same shape, with queued: 0. |
400 | VALIDATION_ERROR | See What Is Refused. |
403 | FORBIDDEN | The key acts as a Member whose permission on the Library folder the template is in is only View or Deny. |
404 | TEMPLATE_NOT_FOUND | No template in this workspace has that key, or the template has no such version. |
404 | CHECKLIST_NOT_FOUND | A named checklist is not in this workspace. |
What Is Refused
A refusal refuses the whole request: nothing is queued, even for the checklists that were valid.
| Problem | Answer | field |
|---|---|---|
| No body | 400 VALIDATION_ERROR | — |
Both checklistKeys and all: true | 400 VALIDATION_ERROR | all |
Neither checklistKeys nor all: true | 400 VALIDATION_ERROR | checklistKeys |
| More than 500 keys | 400 VALIDATION_ERROR | checklistKeys |
| A key that is not a GUID | 400 VALIDATION_ERROR | checklistKeys[n] |
version below 1 | 400 VALIDATION_ERROR | version |
| A template or version that does not exist | 404 TEMPLATE_NOT_FOUND | — |
| A key without permission to edit the template | 403 FORBIDDEN | — |
| A named checklist that is not in this workspace | 404 CHECKLIST_NOT_FOUND | — |
| A named checklist started from a different template | 400 VALIDATION_ERROR | checklistKeys |
What Is Skipped
A named checklist in one of these states is left where it is and reported under skipped, and the rest of the request goes ahead:
| State | reason |
|---|---|
| Archived | archived, and an archived checklist is not running |
| Already on the target version | already on version N, with the version number |
What an Upgrade Carries Across
The worker builds each checklist afresh from the target version and moves the old checklist's work onto it. Tasks are matched by task key and fields by field key — which is why a version document must keep the id of every task and field that is meant to stay the same. The rebuilt checklist keeps its key, name and start date, and replaces the old one.
| What | Carried across |
|---|---|
| Short Text, Long Text, E-Mail, Website, Video, Embed | The value. |
| Date & Time | The date, when one was given. |
| Dropdown, Multi-Choice | The selection, matched by option key. When the field's Data Set link has changed — newly linked, pointed at a different view or unlinked — the selection is matched by the words the option showed. |
| Sub-Tasks | Which items were ticked, matched the same way. |
| Members | Selections that the new version still offers and does not exclude. |
| File Upload | The uploaded files. |
| Table | Entered cell values and added rows, adjusted for columns the new version adds or removes. |
| Linked Checklist | The link to the child checklist, when the field still links to the same template. |
| Task status | Completed and not-applicable tasks keep their status, who set it and when. A completed task that has an unanswered required field in the new version is made incomplete. |
| Comments | All task comments. |
| Assignees | Assignees added to or removed from a task on the checklist, relative to the template. |
| Due dates | A due date changed on the checklist is kept when the task's due date rule has not changed. |
| Custom notifications | Notifications added to the checklist itself, rather than inherited from the template. |
A Dropdown, Multi-Choice or Sub-Tasks choice that the new version no longer offers is recorded in the checklist's activity feed as an answer that could no longer be selected. An answer to a task or field that the new version does not have at all is not carried across and is not recorded. A field that the new version fills from a new or changed valueFrom binding is filled from that binding, replacing the carried-across value.
Notes
- A checklist whose rebuild fails is left as it was, on its old version, and stays on the candidates list. Nothing in the response reports the failure — check the candidates list after the
Retry-Afterinterval. - The worker handles one template at a time. A second upgrade of the same template waits until the first has finished.
- Upgrading cannot be undone except by another upgrade. Read List Checklist Upgrades before sending
all: true. - The app offers the upgrade to Administrators only. The API also lets a key acting as a Member upgrade, when the Member's permission on the template's Library folder is not View or Deny.
Related Pages
- Template Documents — the full format of the document these routes take and return, and every violation code.
- Drafts — building a template document over several requests before committing it.
- Schema and Authoring Guide — the JSON Schema and per-field-type properties for composing a document.
- Template Versioning — how versions and running checklists relate in the app.
- Templates MCP Tools — the same operations for AI clients.