Schema and Authoring Guide
The discovery routes describe the template document format to a program. They answer questions about the format rather than about your workspace's data: which field types exist and what each one takes, which operators a condition can use, which due date rules and scenarios there are, and — in prose — how to put a document together. A client, or an AI agent working through the MCP server, reads them before composing a document so that it gets the document right first time instead of learning the format from refusals.
Every answer is generated from the same declarations the document reader validates against, so a property or value these routes offer is one a document may use, and one they do not offer is one a document is refused for. None of them depends on the workspace: every API key gets the same answers. The one discovery read that does depend on the workspace — the member, group, tag and template names a document can use — is GET /v3/workspace.
All six routes are reads that change nothing, and all six are charged to the cheap rate limit budget rather than the standard one, so reading them freely costs almost nothing against your workspace's allowance. Responses carry X-RateLimit-Bucket: cheap.
Endpoints
| Method | Path | Description | MCP tool |
|---|---|---|---|
GET | /v3/schema/guide | Get the authoring guide | get_authoring_guide |
GET | /v3/schema/document | Get the document schema | get_template_schema |
GET | /v3/schema/content-types | List content types | list_content_types |
GET | /v3/schema/content-types/{type} | Describe a content type | describe_content_type |
GET | /v3/schema/condition-operators | List condition operators | list_condition_operators |
GET | /v3/schema/due-dates | List due date rules and scenarios | list_due_date_scenarios |
Each MCP tool returns the same object as its route, as the tool's structured content. The MCP tools take the same inputs: describe_content_type takes type, and list_condition_operators takes an optional contentType.
Composing a Document With These Routes
The routes are designed to be read in an order that keeps what you hold small: the envelope and a one-line index first, and one field type unfolded at a time, at the moment you choose it.
- Read the authoring guide once —
GET /v3/schema/guide. It covers what the schemas cannot say: that a task has no description and its instructions go in aTextfield, that array position is order, how to choose between the nineteen field types and how to write conditional logic that resets correctly. - Read the workspace —
GET /v3/workspace. Members, groups, tags and templates a document can name. Most refused documents are refused for a name that does not resolve. - Read the envelope —
GET /v3/schema/document. Settings, tags, permissions, parameters, tasks, conditions and notifications, with fields reduced to the properties every field has. - Choose field types —
GET /v3/schema/content-typeslists all nineteen with a summary and whether conditional logic can test them. If the template's logic depends on a field, choose one of the eight testable types. - Unfold each type as you use it —
GET /v3/schema/content-types/{type}returns that type's full JSON Schema and an example field to adapt. - Check the vocabularies you need —
GET /v3/schema/condition-operatorsfor rules andGET /v3/schema/due-datesfor due dates. - Validate, then publish. Send the document to
POST /v3/templates/validateuntilvalidistrue, then toPOST /v3/templatesor a draft. Each violation carries a path, a code and usually a hint. See Violations.
Because the answers are the same for every workspace and every key, a client can read them once per session and keep them. The workspace read is different: re-read it before publishing if time has passed, because people join, tags are created and templates are published.
For an AI agent, the MCP server's instructions already tell the model to read the guide before composing its first document. The guide is also offered as an MCP resource so that a person can attach it to a conversation instead — see Get the Authoring Guide.
Get the Authoring Guide
Returns the template authoring guide: prose, in Markdown, explaining how to compose a template document — the order to work in, what tasks and fields are, how to choose a field type, how conditions and due dates are written and how to publish. It ends with a complete worked example that validates in any workspace. Read it before writing a template for the first time.
GET /v3/schema/guide
Parameters
This endpoint takes no parameters.
Example
GET https://api.checkflow.io/v3/schema/guide
X-API-KEY: your-api-key-here
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-RateLimit-Bucket: cheap
The content string is shortened here; the real guide is around fifteen kilobytes.
{
"format": "markdown",
"content": "# Writing a CheckFlow template\n\nA template is a process written down once: a list of tasks, the fields on them, the rules that\ndecide which of them a particular run shows, and when each one falls due. ..."
}
| Field | Type | Description |
|---|---|---|
format | string | What content is written in. Always markdown. |
content | string | The whole guide. |
Responses
| Status | Code | When |
|---|---|---|
200 | — | Always, for a valid key. |
Notes
- The guide is JSON on the wire, like every other v3 response, rather than
text/markdown. Rendercontentas Markdown. - Over MCP the same text is available three ways: the
get_authoring_guidetool, which returns this object; thetemplate_authoring_guideresource atcheckflow://guides/template-authoring, with MIME typetext/markdown, which a client can offer a person to attach; and a pointer in the server's instructions. Thecheckflow://URI names the resource within the MCP server and is not a web address. - The worked example names no members, tags or templates, so it validates in any workspace. As soon as you add an assignee or a tag, the names have to come from
GET /v3/workspace.
Get the Document Schema
Returns the whole template document as a JSON Schema (draft 2020-12): the top-level properties, settings, tags, permissions, parameters, tasks, due dates, conditions and notifications. Use it to validate a document's structure client-side, to generate types, or to give an agent the envelope before it chooses any field types.
GET /v3/schema/document
Parameters
This endpoint takes no parameters.
Example
GET https://api.checkflow.io/v3/schema/document
X-API-KEY: your-api-key-here
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-RateLimit-Bucket: cheap
Abridged — the full response describes every property of the document:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Template document",
"description": "A whole CheckFlow template, written as one object. POST /v3/templates takes one, GET /v3/templates/{key}/document emits one, and a draft holds one while it is being written. ...",
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 100,
"description": "The template's name, as the library lists it."
},
"tasks": {
"type": "array",
"description": "The tasks, in the order they appear. Array position is the order -- there is no order property, and sending one is an unknown property.",
"items": {
"type": "object",
"properties": {
"halt": {
"type": "string",
"enum": ["none", "task", "task-and-preceding"],
"description": "Whether the checklist stops here until this task is done. Defaults to none."
},
"fields": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["Text", "ShortText", "LongText", "EmailInput", "Website", "FileUpload", "Date", "DropDown", "MultiChoice", "SubTasks", "Image", "Video", "File", "SendEmail", "Separator", "Embed", "Members", "Table", "LinkedChecklist"]
}
},
"additionalProperties": true,
"required": ["type"]
}
}
},
"additionalProperties": false,
"required": ["name"]
}
}
},
"required": ["name"],
"additionalProperties": false
}
Responses
| Status | Code | When |
|---|---|---|
200 | — | Always, for a valid key. |
Notes
- Fields are folded. Each field in
tasks[].fields[]is described only by the properties every field has —ref,id,type,hiddenByDefaultandtooltip— withtypeas an enum of the nineteen content types andadditionalPropertieslefttrue. A field's own properties are written flat beside those five, so a closed object would refuse every real field. Use Describe a Content Type for the type you are writing; that schema is closed. - Every other object is closed (
additionalProperties: false). Note that the server itself refuses unknown properties only on fields; see Rules That Apply Everywhere. - Enums are the accepted vocabularies.
halt, parametertype, permissiontypeandpermission, due daterule,directionandscenario, conditionoperatorandeffect, and notificationeventare all given as enums. - The schema cannot tell you what the workspace contains. A tag is a string here; in a document it has to be a real or intended tag name. Names come from
GET /v3/workspace. - The schema is rebuilt on every request, and its descriptions carry the same explanations as Template Documents.
List Content Types
Returns the nineteen field types a document can use, each with a one-line summary and whether a condition can test it. Read it before choosing a field's type: the type decides every other property the field may carry.
GET /v3/schema/content-types
Parameters
This endpoint takes no parameters.
Example
GET https://api.checkflow.io/v3/schema/content-types
X-API-KEY: your-api-key-here
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-RateLimit-Bucket: cheap
{
"types": [
{ "type": "Text", "summary": "A block of formatted prose the template shows and nobody answers -- an instruction, a warning, a paragraph of context before the questions start.", "isTestable": false },
{ "type": "ShortText", "summary": "One line of text the checklist's user types -- a name, a reference, a serial number.", "isTestable": true },
{ "type": "LongText", "summary": "Several lines of text the checklist's user types -- an explanation, a note, an account of what happened.", "isTestable": true },
{ "type": "EmailInput", "summary": "An email address the checklist's user types, checked for being one.", "isTestable": true },
{ "type": "Website", "summary": "A web address the checklist's user types. To show a page the author chose, rather than ask for one, use an Embed field instead.", "isTestable": true },
{ "type": "FileUpload", "summary": "Files the checklist's user uploads -- receipts, photographs, a signed form. To hand them files instead, use a File field.", "isTestable": false },
{ "type": "Date", "summary": "A date, a time, or both, picked from a calendar by the checklist's user.", "isTestable": true },
{ "type": "DropDown", "summary": "A list the checklist's user picks exactly one thing from, shown collapsed.", "isTestable": true },
{ "type": "MultiChoice", "summary": "A list of boxes the checklist's user may tick any number of, all shown at once.", "isTestable": true },
{ "type": "SubTasks", "summary": "A list of steps the checklist's user ticks off as they go -- a checklist inside the task, rather than a question with answers.", "isTestable": false },
{ "type": "Image", "summary": "A picture the template shows. The picture itself is uploaded in the editor and a document cannot carry one, so a new Image field has to be made there.", "isTestable": false },
{ "type": "Video", "summary": "A video the template shows, either from an address or uploaded in the editor. An address is the half a document can write.", "isTestable": false },
{ "type": "File", "summary": "Files the template hands the checklist's user -- a handbook, a form, a drawing. To ask them for files instead, use a FileUpload field.", "isTestable": false },
{ "type": "SendEmail", "summary": "An email the checklist sends, written when the template is. Every property is the author's -- there is nothing here for the checklist's user to answer.", "isTestable": false },
{ "type": "Separator", "summary": "A rule drawn across the task, to group what is above it apart from what is below. Nothing to configure and nothing to answer.", "isTestable": false },
{ "type": "Embed", "summary": "A page from somewhere else, shown inside the task -- a dashboard, a form, a video. To ask the checklist's user for an address instead, use a Website field.", "isTestable": false },
{ "type": "Members", "summary": "People picked from the workspace by the checklist's user -- who signs it off, who was present, who to hand it to next.", "isTestable": true },
{ "type": "Table", "summary": "A grid of cells -- readings, quantities, a row per item. Cells the author fills in and marks read-only are headings; the rest are answered in the checklist.", "isTestable": false },
{ "type": "LinkedChecklist", "summary": "A second checklist started from another template -- a sub-process run from inside this one.", "isTestable": false }
]
}
| Field | Type | Description |
|---|---|---|
types | object[] | Every content type, in a fixed order. Not paged. |
types[].type | string | The word a field's type is written with, spelled exactly. |
types[].summary | string | What the type is for, written to tell it apart from its neighbours. |
types[].isTestable | boolean | Whether a condition can be triggered by a field of this type. |
Responses
| Status | Code | When |
|---|---|---|
200 | — | Always, for a valid key. |
Notes
- The type names are the API's own and differ from the control names in the product in a few places:
EmailInputis the E-Mail control,Dateis Date & Time,DropDownis Dropdown andSendEmailis Mail-To. Field Types maps each one to its control page. - Eleven types cannot be tested by a condition. That is a fact about which controls the product supports conditions on, so a field that other parts of the template branch on has to be one of the eight with
isTestable: true.
Describe a Content Type
Returns one field type in full: its JSON Schema, the condition operators it supports and an example field. Call it when you choose a type — it is the only place a type's own properties are listed.
GET /v3/schema/content-types/{type}
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
type | path | string | Yes | The content type, spelled as List Content Types spells it. Matched without regard to case, so table finds Table. |
Example
GET https://api.checkflow.io/v3/schema/content-types/Date
X-API-KEY: your-api-key-here
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-RateLimit-Bucket: cheap
{
"type": "Date",
"summary": "A date, a time, or both, picked from a calendar by the checklist's user.",
"isTestable": true,
"operators": [
"is", "is-not", "has-no-value", "has-any-value",
"is-greater-than", "is-greater-than-or-equal-to", "is-less-than", "is-less-than-or-equal-to"
],
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Date",
"description": "A date, a time, or both, picked from a calendar by the checklist's user.",
"type": "object",
"properties": {
"ref": { "type": "string", "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$", "maxLength": 64, "description": "The author's own name for this field, which everything referring to it uses. Invented from the field's label when left out." },
"id": { "type": "string", "format": "uuid", "description": "The field's key. Leave it out to create a field; keep the one export gave you to carry an existing field, and the answers already given to it, into a new version." },
"type": { "const": "Date", "description": "A date, a time, or both, picked from a calendar by the checklist's user." },
"hiddenByDefault": { "type": "boolean", "description": "Whether the field starts hidden, to be shown by a condition." },
"label": { "type": "string", "description": "The question or prompt shown above the picker." },
"isRequired": { "type": "boolean", "description": "Whether the task cannot be completed until this field has been answered.", "default": false },
"mode": { "type": "string", "description": "What the picker asks for: a day, a time of day, or both.", "enum": ["date", "time", "date-and-time"], "default": "date" },
"valueFrom": {
"type": "object",
"description": "Where the field's value comes from when it is filled in for the user: a parameter, another field, a field of the record another field selected from a Data Set, or a global dynamic value. One of the three, and at most one binding per field.",
"properties": {
"parameter": { "type": "string", "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$", "maxLength": 64, "description": "The ref of a parameter whose value fills this field when a checklist is started. A string parameter fills any field but a Date, a datetime parameter fills a Date, and a member_or_group parameter fills a Members field." },
"field": { "type": "string", "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$", "maxLength": 64, "description": "The ref of a field whose answer is copied into this one whenever it changes -- a field of the same kind, or any answerable field for a ShortText or LongText. With 'dataSetField', the field whose selected record is read instead." },
"dataSetField": { "type": "string", "description": "A field of the record 'field' selected from its Data Set, by name or by key. Only with a 'field' that carries a dataSet." },
"dynamicValue": { "type": "string", "description": "A global dynamic value, as its token: {{checklist.name}}, {{current_user.email}}, {{current_date}}. Which ones a field takes depends on its type; a Date takes the date-valued ones and a DropDown takes none." }
},
"additionalProperties": false,
"anyOf": [
{ "required": ["parameter"] },
{ "required": ["field"] },
{ "required": ["dynamicValue"] }
]
},
"tooltip": { "type": "string", "description": "Help text shown against the field, behind the question mark beside its label.", "maxLength": 1200 }
},
"required": ["type", "label"],
"additionalProperties": false
},
"example": {
"ref": "installed_on",
"type": "Date",
"hiddenByDefault": false,
"label": "When was it installed",
"isRequired": true,
"mode": "date-and-time"
}
}
| Field | Type | Description |
|---|---|---|
type | string | The content type's name. |
summary | string | The same summary List Content Types gives. |
isTestable | boolean | Whether a condition can be triggered by a field of this type. |
operators | string[] | The condition operators a field of this type supports. Empty for an untestable type; four for most testable types; eight for Date. |
schema | object | The type's JSON Schema, complete on its own: every property with its type, description, range, enum, default and whether it is required. additionalProperties is false. A property marked readOnly is written by export and ignored on the way in. |
example | object | A realistic field of this type, ready to put in a task's fields array once you change its ref and label. |
Responses
| Status | Code | When |
|---|---|---|
200 | — | The type exists. |
400 | VALIDATION_ERROR | type is not a content type. field is type. The message names the nearest real type when one is close, such as "'ShortTxt' is not a content type. Did you mean 'ShortText'?". |
Notes
- The keys of
schema.propertiesare the complete list of properties a field of this type may carry. There is no separate property list. - The
exampleis the small, realistic case rather than a field with every property set. - The
ImageandFileexamples cannot be used in a new template as they stand: a newImageorFilefield needs a file, which a document cannot carry, and is refused withbinary_required. See Image, Video and File.
List Condition Operators
Returns the tests a condition can make, which of them need a value, which apply only to dates, the two effects and the eight field types a condition can test. Pass contentType to ask about one type.
GET /v3/schema/condition-operators
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
contentType | query | string | No | Narrow the answer to one field type, matched without regard to case. Omit for every operator. |
Example
GET https://api.checkflow.io/v3/schema/condition-operators
X-API-KEY: your-api-key-here
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-RateLimit-Bucket: cheap
{
"operators": [
{ "operator": "is", "needsValue": true, "datesOnly": false },
{ "operator": "is-not", "needsValue": true, "datesOnly": false },
{ "operator": "has-no-value", "needsValue": false, "datesOnly": false },
{ "operator": "has-any-value", "needsValue": false, "datesOnly": false },
{ "operator": "is-greater-than", "needsValue": true, "datesOnly": true },
{ "operator": "is-greater-than-or-equal-to", "needsValue": true, "datesOnly": true },
{ "operator": "is-less-than", "needsValue": true, "datesOnly": true },
{ "operator": "is-less-than-or-equal-to", "needsValue": true, "datesOnly": true }
],
"testableTypes": ["Date", "DropDown", "EmailInput", "LongText", "MultiChoice", "ShortText", "Website", "Members"],
"effects": ["show", "hide"]
}
Asked about a type that cannot be tested, the answer is not an error:
GET https://api.checkflow.io/v3/schema/condition-operators?contentType=FileUpload
X-API-KEY: your-api-key-here
{
"type": "FileUpload",
"isTestable": false,
"operators": [],
"testableTypes": ["Date", "DropDown", "EmailInput", "LongText", "MultiChoice", "ShortText", "Website", "Members"],
"effects": ["show", "hide"]
}
| Field | Type | Description |
|---|---|---|
type | string | The type asked about. Absent when contentType was not sent. |
isTestable | boolean | Whether that type can be tested. Absent when contentType was not sent. |
operators | object[] | Every operator, or those the type supports, or none. |
operators[].operator | string | The word a condition's when.operator is written with. |
operators[].needsValue | boolean | Whether the operator compares against a when.value. A value sent with an operator that takes none is refused. |
operators[].datesOnly | boolean | Whether only a Date field supports the operator. |
testableTypes | string[] | The eight types a condition can test, always included so that an untestable answer says what to use instead. |
effects | string[] | What a rule can do: show or hide. |
Responses
| Status | Code | When |
|---|---|---|
200 | — | No contentType, or a contentType that names a type — testable or not. |
400 | VALIDATION_ERROR | contentType names no type at all, such as Drop Down. field is contentType, and the message names the nearest type when one is close. |
Notes
- A condition's rules on one field run in order each time its answer changes, and nothing returns to
hiddenByDefaulton its own. A show or hide rule needs a reset rule ahead of it on the same field. See Order and Resets. - What
when.valuelooks like for each type is on Template Documents.
List Due Date Rules and Scenarios
Returns everything a task's dueDate can be written with: the rules an offset is counted from, what each rule's anchorRef names, which rules accept before, the two directions and the twenty calendar scenarios.
GET /v3/schema/due-dates
Parameters
This endpoint takes no parameters.
Example
GET https://api.checkflow.io/v3/schema/due-dates
X-API-KEY: your-api-key-here
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-RateLimit-Bucket: cheap
{
"rules": [
{ "rule": "checklist-start-date", "takesDirection": false },
{ "rule": "previous-task-completed", "takesDirection": false },
{ "rule": "date-control", "anchor": "field", "takesDirection": true },
{ "rule": "task-completed", "anchor": "task", "takesDirection": false },
{ "rule": "parameter", "anchor": "parameter", "takesDirection": true }
],
"directions": ["before", "after"],
"scenarios": [
"next-weekday", "next-monday", "next-tuesday", "next-wednesday", "next-thursday",
"next-friday", "next-saturday", "next-sunday",
"first-day-of-next-month", "first-weekday-of-next-month", "last-day-of-month", "last-weekday-of-month",
"last-day-of-quarter", "last-weekday-of-quarter", "first-day-of-next-quarter", "first-weekday-of-next-quarter",
"last-day-of-current-year", "last-weekday-of-current-year", "first-day-of-next-year", "first-weekday-of-next-year"
]
}
| Field | Type | Description |
|---|---|---|
rules | object[] | The five rules a due date's rule can take. |
rules[].rule | string | The rule's word. |
rules[].anchor | string | What anchorRef must name under this rule: task, field (a Date field) or parameter (a datetime parameter). Absent when the rule counts from something the checklist already knows and takes no anchorRef. |
rules[].takesDirection | boolean | Whether direction: "before" is accepted. true for date-control and parameter, whose anchors are dates; the other rules count forward from something that has just happened. |
directions | string[] | before and after. |
scenarios | string[] | The twenty recurring calendar moments a due date's scenario can take, in the order the template editor lists them. |
Responses
| Status | Code | When |
|---|---|---|
200 | — | Always, for a valid key. |
Notes
- A due date is a
rulewith an optionaloffset, or ascenariowith an optionalscenarioTime— never both. Every combination, with examples, is on Due Dates. - A scenario outside this list is refused rather than stored, because it would never compute a due date.
Related Pages
- Template Documents — the full reference for the format these routes describe.
- Workspace — the member, group, tag and template names a document can use.
- Templates — validating, creating and versioning templates from documents.
- Rate Limits — the
cheapandstandardbudgets. - MCP Template Tools — the same discovery reads as MCP tools.