Data Sets
A Data Set is a table of reference data that template controls read from — a Dropdown offering your suppliers, a Sub-Tasks list built from your sites. It is made of fields (the columns), records (the rows) and views (saved filters, sorts and hidden columns). A template control is linked to a view rather than to the whole Data Set, so that it can offer a filtered slice of the table.
The routes on this page manage the workspace's own Data Sets and read the ten system Data Sets CheckFlow provides to every workspace. How a template control is linked to a Data Set is part of the template document — see Template Documents. How a linked control is answered on a checklist is covered in Task Fields and Files.
Every write on this page accepts an Idempotency-Key header (see Idempotency) and every request is charged to the standard rate limit budget.
"Field" on this page means a column of a Data Set. It is unrelated to the fields of a checklist task, which are the task's input controls. A missing Data Set column is reported as DATA_SET_FIELD_NOT_FOUND, never as FIELD_NOT_FOUND.
Endpoints
| Method | Path | Description | MCP tool |
|---|---|---|---|
GET | /v3/data-sets | List Data Sets | list_data_sets |
POST | /v3/data-sets | Create a Data Set | create_data_set |
POST | /v3/data-sets/import | Create a Data Set from CSV | create_data_set_from_csv |
GET | /v3/data-sets/{dataSetKey} | Get a Data Set | get_data_set |
PATCH | /v3/data-sets/{dataSetKey} | Update a Data Set | update_data_set |
DELETE | /v3/data-sets/{dataSetKey} | Delete a Data Set | delete_data_set |
GET | /v3/data-sets/{dataSetKey}/connections | List a Data Set's Connections | list_data_set_connections |
GET | /v3/data-sets/{dataSetKey}/export | Export a Data Set as CSV | export_data_set_csv |
POST | /v3/data-sets/{dataSetKey}/import | Replace Records from CSV | replace_data_set_records_from_csv |
GET | /v3/data-sets/{dataSetKey}/fields | List Fields | — |
POST | /v3/data-sets/{dataSetKey}/fields | Add a Field | create_data_set_field |
PATCH | /v3/data-sets/{dataSetKey}/fields/{fieldKey} | Update a Field | update_data_set_field |
DELETE | /v3/data-sets/{dataSetKey}/fields/{fieldKey} | Delete a Field | delete_data_set_field |
GET | /v3/data-sets/{dataSetKey}/records | List Records | list_data_set_records |
POST | /v3/data-sets/{dataSetKey}/records | Add a Record | create_data_set_record |
GET | /v3/data-sets/{dataSetKey}/records/{recordKey} | Get a Record | get_data_set_record |
PATCH | /v3/data-sets/{dataSetKey}/records/{recordKey} | Update a Record | update_data_set_record |
DELETE | /v3/data-sets/{dataSetKey}/records/{recordKey} | Delete a Record | — |
POST | /v3/data-sets/{dataSetKey}/records/bulk | Add or Replace Records in Bulk | create_data_set_records |
DELETE | /v3/data-sets/{dataSetKey}/records/bulk | Delete Records in Bulk | delete_data_set_records |
GET | /v3/data-sets/{dataSetKey}/views | List Views | — |
POST | /v3/data-sets/{dataSetKey}/views | Create a View | create_data_set_view |
GET | /v3/data-sets/{dataSetKey}/views/{viewKey} | Get a View | — |
PATCH | /v3/data-sets/{dataSetKey}/views/{viewKey} | Update a View | update_data_set_view |
DELETE | /v3/data-sets/{dataSetKey}/views/{viewKey} | Delete a View | delete_data_set_view |
The MCP server has no separate tools for the fields list, the views list or a single view, because get_data_set already returns every field and view in full. A single record is deleted with delete_data_set_records and one key — the REST route does exactly the same thing. See Data Set Tools.
Addressing a Data Set
dataSetKey is either the Data Set's key (a GUID) or, for a system Data Set, its slug such as system-countries. Both reach the same object. A value that matches nothing — a GUID from another workspace, a misspelt slug — is a 404 with DATA_SET_NOT_FOUND, never a 400, because only a lookup can say whether a slug names something.
Fields, records and views answer to their GUID key only. A value that is not a GUID is a 400 VALIDATION_ERROR naming the parameter (fieldKey, recordKey, viewKey or the view query parameter). A well-formed key that names nothing in this Data Set is a 404 with its own code — DATA_SET_FIELD_NOT_FOUND, DATA_SET_RECORD_NOT_FOUND or DATA_SET_VIEW_NOT_FOUND — so you can tell which of two keys was wrong. A record or view that belongs to a different Data Set from the one in the path is a 404 too.
Permissions
The Data Set routes apply the Library permissions the app uses. What a key may do depends on who it acts as:
- An Administrator can read, change and delete every Data Set in the workspace.
- The workspace can do everything an Administrator can, deleting included. None of these routes is about a person, so a key that acts as the workspace works on all of them.
- A Member needs the permissions below.
| Operation | What a Member needs |
|---|---|
| List Data Sets | One of Library.Creator, Library.Viewer, Template.Creator or Template.Viewer. The list holds only the Data Sets the Member could read one at a time. |
| Read a Data Set — get it, list its fields, records and views, get a record or view, list its connections, export it as CSV | One of the same four permissions, and View or Edit on the Library folder the Data Set is filed in. |
| Create a Data Set, directly or from CSV | Library.Creator. |
| Change a Data Set — rename it, change its description, add, change or delete fields, records (singly, in bulk or from CSV) and views | Library.Creator, and Edit on the Library folder the Data Set is filed in. Edit on the folder without Library.Creator is not enough. |
| Delete a Data Set | Cannot. Deleting a Data Set is limited to Administrators, whatever the folder allows. |
A folder that denies the Member, or that gives them no permission at all, refuses them. A Data Set in no folder — Unfiled, or never filed, as every Data Set the v2 API created is — has no folder to check, so no folder grant is needed: one of the four permissions above is enough to read it, and Library.Creator to change it. The same goes for reading the system Data Sets. A Member with none of the four permissions cannot read any Data Set, the list included.
A refusal is 403 FORBIDDEN, and its message names the missing permission or says that the Data Set's folder is the reason. The workspace check comes first: another workspace's Data Set is a 404 DATA_SET_NOT_FOUND, never a 403.
Writes to a system Data Set are refused for everybody, Administrators and the workspace included, with 403 FORBIDDEN — see System Data Sets.
A Data Set created through the API (directly or from CSV) is credited to the member the key acts as, or to the workspace's anonymous member for a key that acts as the workspace. It is filed in the Library as Unfiled, as the app files a Data Set created without a folder. Move it to a folder in the app to put it under that folder's permissions.
System Data Sets
Every workspace can read ten built-in system Data Sets. They have "source": "system", are the same in every workspace and region, and can be addressed by slug:
| Slug | Name | Fields |
|---|---|---|
system-countries | Countries | Name, ISO2 Code, ISO3 Code, Dial Code |
system-currencies | Currencies | Name, Code, Symbol |
system-months-of-year | Months of Year | Name, Short Name, Number |
system-days-of-week | Days of Week | Name, Short Name, Number |
system-us-states | US States | Name, Abbreviation |
system-timezones | Timezones | Name, UTC Offset, Region |
system-language-codes | Language Codes | Name, ISO 639-1 Code |
system-priority-levels | Priority Levels | Label, Value |
system-yes-no-na | Yes / No / N/A | Label, Value |
system-quarters-of-year | Quarters of Year | Name, Short Name, Number |
Every system Data Set has an All Records view. Currencies also has a Major Currencies view, which filters the Code field to twenty common currency codes.
Their keys, field keys, view keys and record keys are fixed: they are the same in every workspace, and a record's key is derived from its first cell (the country name, for example), so it does not change when CheckFlow adds rows to a list.
What you can and cannot do with a system Data Set:
| Operation | Allowed |
|---|---|
| List, get, list fields, list and get views | Yes |
| List and get records, including through a view | Yes |
| List connections | Yes |
| Export as CSV | Yes |
| Rename, change the description, delete | No — 403 FORBIDDEN |
| Add, change or delete fields | No — 403 FORBIDDEN |
| Add, change or delete records, in bulk or singly, or replace them from CSV | No — 403 FORBIDDEN |
| Create, change or delete views | No — 403 FORBIDDEN |
On a system Data Set, url, createdDateTime and updatedDateTime are absent — the ten are built by CheckFlow and have no page of their own in the app. System Data Sets do not count towards the workspace's limit of 100.
A write to a system Data Set is checked after the request body. A body that is invalid in its own right — a cell naming a column the Data Set does not have, say — is answered 400 before the 403.
Limits
| Limit | Value | When exceeded |
|---|---|---|
| Data Sets per workspace (system Data Sets not counted) | 100 | 400 on create or CSV import |
| Fields per Data Set | 50 | 400 on create, add field or CSV import |
| Records per Data Set | 10,000 | 400 on add record, bulk create or CSV import |
| Views per Data Set | 20 | 400 on create view |
| Cell text in one record | 5 KB (5,120 bytes of UTF-8, all cells together) | 400 on any record write |
| Records in one bulk create or bulk delete | 1,000 | 400 naming records or recordKeys |
| CSV text in one import or export | 5 MB (5,242,880 bytes of UTF-8) | 400 |
| Data Set, field and view names | 200 characters | 400 |
A limit is enforced when a request would cross it; nothing is written. Names are trimmed before they are stored. Field names must be unique within a Data Set, compared without regard to case.
The Data Set Object
GET /v3/data-sets/{dataSetKey}, create, update and both CSV imports return the full object. GET /v3/data-sets returns each Data Set without fields and views.
| Field | Type | Description |
|---|---|---|
key | string (GUID) | The Data Set's key. |
slug | string | The slug a system Data Set also answers to, such as system-countries. Absent for the workspace's own Data Sets. |
name | string | The Data Set's name. |
description | string | Absent when there is none. |
source | string | organisation for a Data Set the workspace made, system for a system Data Set. |
fieldCount | integer | How many fields it has. |
recordCount | integer | How many records it holds. |
url | string | Where to open it in the app. Absent for a system Data Set. |
createdDateTime | string (date-time) | When it was created. Absent for a system Data Set. |
updatedDateTime | string (date-time) | When it was last updated. Absent for a system Data Set. |
fields | array of Field | Its fields, in field order. Full object only. |
views | array of View | Its views, the default view first and the rest in the order they were created. Full object only. |
{
"key": "3f2b8c1e-7a4d-4e5b-9c61-2d8f0a7b6e14",
"name": "Suppliers",
"description": "Approved suppliers for invoice review",
"source": "organisation",
"fieldCount": 5,
"recordCount": 3,
"url": "https://app.checkflow.io/DataSet/Index?key=3f2b8c1e-7a4d-4e5b-9c61-2d8f0a7b6e14",
"createdDateTime": "2026-03-04T09:15:22Z",
"updatedDateTime": "2026-06-18T14:02:47Z",
"fields": [
{ "key": "a1c4e7f0-2b5d-4c8e-9f13-6d0a3b7e2c51", "name": "Supplier", "type": "text", "description": "Trading name", "position": 0 },
{ "key": "b72d9e14-5f3a-4b6c-8d20-1e9f4a7c3b68", "name": "Contact", "type": "email", "position": 1 },
{ "key": "c9e3f158-6a2b-4d7e-a041-3f8b2d6e9c74", "name": "Payment Terms", "type": "number", "description": "Days", "position": 2 },
{ "key": "d4f86a29-7b3c-4e1f-b852-5a9c0e3d7f16", "name": "Contract Renews", "type": "date", "position": 3 },
{ "key": "e5a97b3a-8c4d-4f20-9c63-7b1d2f4e8a27", "name": "Active", "type": "boolean", "position": 4 }
],
"views": [
{
"key": "6b2e4d8f-1a3c-4e5b-8d7f-0c9a2e4b6d13",
"name": "All Records",
"isDefault": true,
"filters": [],
"sorts": [],
"hiddenFieldKeys": [],
"hiddenFieldNames": []
},
{
"key": "7c3f5e9a-2b4d-4f6c-9e8a-1d0b3f5c7e24",
"name": "Active Suppliers",
"isDefault": false,
"filters": [
{ "fieldKey": "e5a97b3a-8c4d-4f20-9c63-7b1d2f4e8a27", "fieldName": "Active", "operator": "eq", "value": "true" }
],
"sorts": [
{ "fieldKey": "a1c4e7f0-2b5d-4c8e-9f13-6d0a3b7e2c51", "fieldName": "Supplier", "direction": "asc", "position": 0 }
],
"hiddenFieldKeys": ["c9e3f158-6a2b-4d7e-a041-3f8b2d6e9c74"],
"hiddenFieldNames": ["Payment Terms"]
}
]
}
The url uses your workspace's own address, so it may be a custom subdomain or eu.checkflow.io rather than app.checkflow.io.
The Field Object
A field is one column. See Fields for how field types behave in the app.
| Field | Type | Description |
|---|---|---|
key | string (GUID) | The field's key. |
name | string | Its name, unique within the Data Set regardless of case. Records are keyed by this name. |
type | string | text, number, date, email, url or boolean. See Record Values. |
description | string | Absent when there is none. |
position | integer | Where the field sits, counting from zero. Set when the field is created; nothing changes it. |
Fields are always listed by position, then by name, then by key. Positions are not renumbered when a field is deleted, so they can have gaps. There is no way to reorder fields, in the API or in the app.
The View Object
A view is a saved way of looking at the records: which of them, in what order and with which fields hidden. See Views.
| Field | Type | Description |
|---|---|---|
key | string (GUID) | The view's key. This is what a template control links to, and what ?view= takes. |
name | string | The view's name. |
isDefault | boolean | true for the All Records view every Data Set is created with. Exactly one view is the default, and it cannot be renamed, deleted or replaced as the default. |
filters | array of Filter | The conditions a record must meet. Empty for none. |
sorts | array of Sort | The sort levels, in the order they apply. Empty for the Data Set's own order. |
hiddenFieldKeys | array of string | The keys of the fields this view hides. |
hiddenFieldNames | array of string | The names of those fields, index for index. An entry is null when the view names a field that has since been deleted. |
A filter or sort in a response has this shape:
| Field | Type | Description |
|---|---|---|
fieldKey | string (GUID) | The field it applies to. |
fieldName | string | That field's name. Absent when the field has since been deleted. |
operator | string | Filters only. One of the filter operators. |
value | string | Filters only. What the field is compared against. Absent for is_empty and is_not_empty. |
direction | string | Sorts only. asc or desc. |
position | integer | Sorts only. The order the sort levels apply in, counting from zero. |
Deleting a field does not change any view. A filter, sort or hidden field that names a deleted field shows with no fieldName (or a null in hiddenFieldNames), has no effect on which records the view returns, and is dropped the next time the view is updated.
The Record Object
A record is one row. Its values are keyed by field name, not by field key, so a record reads as it would in a spreadsheet.
| Field | Type | Description |
|---|---|---|
key | string (GUID) | The record's key. |
position | integer | Where the record sits in the Data Set, counting from zero. Set when the record is created and never changed. Positions are not renumbered when records are deleted, so they can have gaps — they are an order, not a count. When you read through a view, this is still the record's position in the Data Set, not its place in the view. |
values | object | One entry per field of the Data Set, in field order, keyed by field name. Every value is a string, or null for an empty cell. |
{
"key": "8d4a6f0b-3c5e-4a7d-b9f1-2e1c4a6d8f35",
"position": 0,
"values": {
"Supplier": "Acme Office Supplies Ltd",
"Contact": "accounts@acme-office.example",
"Payment Terms": "30",
"Contract Renews": "2026-11-30",
"Active": "true"
}
}
Every field appears in values, including empty ones and fields a view hides, so every record of a Data Set has the same keys as GET /v3/data-sets/{dataSetKey}/fields reports. Unlike other properties in the API, an empty cell is sent as null rather than left out.
Record Values
When you write a record, each entry in values names a field by its name (matched without regard to case) or by its key. An entry that is neither is refused with 400, naming the cell — values.Suplier — and nothing is written. Naming the same field twice, once by name and once by key, is also a 400.
Each value is a JSON string, number or boolean. An array or object is refused, because a cell holds one value. null, an empty string and a string of only spaces all mean an empty cell. Values are trimmed. Every value is stored as text and read back as a string, whatever the field's type:
| Type | Accepts | Stored and read back as |
|---|---|---|
text | Anything. | As sent. |
number | A number, as a JSON number or a string, written with . as the decimal point — for example 30, -2.5 or "1,250.00". | As sent — 30 reads back as "30", and nothing is reformatted. |
date | A date or date-time. yyyy-MM-dd, yyyy-MM-ddTHH:mm:ss and yyyy-MM-ddTHH:mm:ssZ are always accepted; other formats are read with US conventions, so 03/01/2026 is 1 March. Use yyyy-MM-dd. | As sent. |
email | An address with exactly one @, something before it, a . after it (the first . after the @ must not come straight after it) and no spaces. | As sent. |
url | An absolute http or https URL. | As sent. |
boolean | true or false as JSON booleans, or the strings true, false, yes, no, 1 or 0 in any case. | "true" or "false". |
A value the field's type does not accept is a 400 naming the cell, with a message such as 'n/a' is not a valid number value for field 'Payment Terms'. Because a record write returns the record as stored, the response is not always what you sent: a boolean written as "yes" reads back as "true".
The Connection Object
A connection is one template control that reads from the Data Set. It is what stands between a Data Set and deleting it.
| Field | Type | Description |
|---|---|---|
templateKey | string (GUID) | The template the control is in. |
templateName | string | That template's name. |
templateVersion | integer | The template version the control is in — always the latest version. |
templateUrl | string | Where to open that template in the app. |
controlKey | string (GUID) | The control's key within that version. |
controlLabel | string | The control's label. |
controlType | string | dropDown, multiChoice, subTasks or table. |
viewKey | string (GUID) | The view the control reads. |
viewName | string | That view's name. Absent when the control points at a view that has since been deleted. |
Filters and Sorts
A view's filters decide which records it returns and its sorts decide their order. You write them when you create or update a view. Records are only ever filtered and sorted through a saved view — List Records has no filter or sort parameters of its own.
Writing a Filter
{ "field": "Payment Terms", "operator": "lte", "value": "30" }
| Field | Type | Required | Description |
|---|---|---|---|
field | string | Yes | The field, by name (matched without regard to case) or by key. |
operator | string | Yes | One of the operators below. |
value | string | For all but is_empty and is_not_empty | What to compare against, always as a string. is_empty and is_not_empty refuse a value, even an empty one. |
A view's filters are combined with AND — a record must meet every one. There is no OR. You can filter the same field twice, which is how you write a range.
| Operator | Matches a record when the cell… |
|---|---|
eq | equals value. |
neq | does not equal value. |
contains | contains value. |
not_contains | does not contain value. |
starts_with | starts with value. |
ends_with | ends with value. |
gt | is greater than value. |
gte | is greater than or equal to value. |
lt | is less than value. |
lte | is less than or equal to value. |
is_empty | is empty. |
is_not_empty | is not empty. |
in | equals one of the items in value, a comma-separated list such as "GBP,EUR,USD". Items are trimmed. |
How values are compared:
- Text comparisons ignore case. An empty cell compares as an empty string, so
neqandnot_containsmatch empty cells. gt,gte,ltandltecompare numerically on anumberfield and chronologically on adatefield when both the cell andvaluecan be read that way. Otherwise — on other field types, or when either side cannot be read as a number or date — they compare as text, ignoring case.- On a
booleanfield the filter's value is normalised the way a record's is, so"yes"and"1"matchtrue. Forin, each item is normalised separately.
Writing a Sort
{ "field": "Contract Renews", "direction": "desc" }
| Field | Type | Required | Description |
|---|---|---|---|
field | string | Yes | The field, by name or by key. A field may appear in sorts only once. |
direction | string | No | asc (the default) or desc. Anything else is refused rather than read as asc. |
Sorts apply in the order you write them; the array is the order, and there is no position to send. A number field sorts numerically and a date field chronologically, with empty and unreadable cells after every other value when ascending (and before them when descending). Other fields sort as text. Records that tie keep the Data Set's own order, and a view with no sorts returns records in the Data Set's own order.
Hidden Fields
hiddenFields is an array of field names or keys that the view leaves out of the grid and out of a CSV export. Naming a field twice is harmless. A view must show at least one field, so hiding every field is refused.
Hiding a field is a display instruction, not a security boundary: List Records still returns hidden fields through the view.
CSV Format
Both CSV imports take the file as text in a JSON string, csv, rather than as a file upload, so the same call works from a script and from an MCP tool. Export returns the file the same way. These rules apply to both imports:
- The first line names the columns. Every column must have a name; names are trimmed, must be unique without regard to case and must be 200 characters or fewer. At most 50 columns.
- Quoting follows RFC 4180: a value containing a comma, a double quote or a line break is wrapped in double quotes, and a double quote inside it is doubled. A quote that is never closed is refused.
- Lines may end in CRLF, LF or CR. A leading byte-order mark is ignored.
- Every line after the first is a record. A line whose cells are all blank is skipped and not counted. At most 10,000 records.
- A line with more cells than the header is refused, and the message lists the line numbers (up to 20). A line with fewer cells is padded with empty cells.
- Each record may hold at most 5 KB of cell text.
- Values are trimmed; a blank cell is an empty cell.
- A value that starts with an apostrophe followed by
=,+,-or@has the apostrophe removed. This reverses the formula protection export adds, so an exported file imports unchanged. - The whole text may be at most 5 MB, measured in UTF-8 bytes.
A file that breaks one of these rules is refused with 400 VALIDATION_ERROR and a message describing the problem, such as Column names in the first row must be unique. Nothing is written.
Column Type Detection
When an import creates a field, its type is detected from the non-empty values in that column, trying each type in this order:
| Every non-empty value is… | Type |
|---|---|
a date written exactly yyyy-MM-dd | date |
| a number | number |
true, false, yes, no, 1 or 0 | boolean |
| an email address | email |
an absolute http or https URL | url |
| anything else, or the column is empty | text |
Because numbers are tried before booleans, a column of 1 and 0 becomes a number field. There is no way to choose types when importing; change a field's type afterwards with Update a Field, which succeeds as long as every existing value suits the new type.
Webhook Events
Record changes raise these webhook events:
| Event | Raised by |
|---|---|
data_set.record.created | Add a Record. |
data_set.record.updated | Update a Record, when at least one cell actually changed. |
data_set.record.deleted | Delete a Record, and Delete Records in Bulk once per record. |
Add or Replace Records in Bulk, Create a Data Set from CSV and Replace Records from CSV raise no events — not for the records they write, and not for the records a replace removes. A 10,000-row import would otherwise send 10,000 events, or 20,000 for a replace. If something downstream relies on these events, use the single-record routes, or read the Data Set again with List Records after a bulk write.
List Data Sets
Returns every Data Set the key can read: the workspace's own, followed by the ten system Data Sets. For a key that acts as a Member, the workspace's own are only those the Member could read — see Permissions. Each item is a Data Set object without fields and views — use Get a Data Set for those.
GET /v3/data-sets
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
includeSystem | query | boolean | No | false leaves the system Data Sets out. Default true. Any value other than true or false is a 400. |
The list is not paged: the whole list comes back in one response, with hasMore always false and total equal to the number of items. The workspace's own Data Sets come first, by name; the system Data Sets follow in a fixed order.
Example
GET https://api.checkflow.io/v3/data-sets
X-API-KEY: your-api-key-here
HTTP/1.1 200 OK
{
"items": [
{
"key": "3f2b8c1e-7a4d-4e5b-9c61-2d8f0a7b6e14",
"name": "Suppliers",
"description": "Approved suppliers for invoice review",
"source": "organisation",
"fieldCount": 5,
"recordCount": 3,
"url": "https://app.checkflow.io/DataSet/Index?key=3f2b8c1e-7a4d-4e5b-9c61-2d8f0a7b6e14",
"createdDateTime": "2026-03-04T09:15:22Z",
"updatedDateTime": "2026-06-18T14:02:47Z"
},
{
"key": "2c7e9a41-5d3b-4f8e-a162-9b0d4e7f3a58",
"slug": "system-priority-levels",
"name": "Priority Levels",
"description": "Standard priority levels",
"source": "system",
"fieldCount": 2,
"recordCount": 4
}
],
"hasMore": false,
"total": 2
}
The example is shortened; a real response lists all ten system Data Sets.
Responses
| Status | Code | When |
|---|---|---|
200 OK | — | The Data Sets. |
400 Bad Request | VALIDATION_ERROR | includeSystem is neither true nor false (field: includeSystem). |
403 Forbidden | FORBIDDEN | The key acts as a Member with none of Library.Creator, Library.Viewer, Template.Creator and Template.Viewer. |
Create a Data Set
Creates a Data Set with the fields you name. An All Records view is created with it. To create a Data Set and fill it from a spreadsheet in one call, use Create a Data Set from CSV instead.
POST /v3/data-sets
Parameters
This endpoint takes no parameters.
Request Body
{
"name": "Suppliers",
"description": "Approved suppliers for invoice review",
"fields": [
{ "name": "Supplier", "type": "text", "description": "Trading name" },
{ "name": "Contact", "type": "email" },
{ "name": "Payment Terms", "type": "number", "description": "Days" },
{ "name": "Contract Renews", "type": "date" },
{ "name": "Active", "type": "boolean" }
]
}
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Up to 200 characters. |
description | string | No | A description of the Data Set. |
fields | array | No | The fields to create, in order. Leave it out, or send [], and the Data Set is created with one text field called Name. At most 50. |
fields[].name | string | Yes | Up to 200 characters, unique within the Data Set regardless of case. |
fields[].type | string | No | text, number, date, email, url or boolean. Default text. |
fields[].description | string | No | A description of the field. |
Example
POST https://api.checkflow.io/v3/data-sets
X-API-KEY: your-api-key-here
Content-Type: application/json
{
"name": "Suppliers",
"description": "Approved suppliers for invoice review",
"fields": [
{ "name": "Supplier", "type": "text", "description": "Trading name" },
{ "name": "Contact", "type": "email" },
{ "name": "Payment Terms", "type": "number", "description": "Days" },
{ "name": "Contract Renews", "type": "date" },
{ "name": "Active", "type": "boolean" }
]
}
HTTP/1.1 201 Created
{
"key": "3f2b8c1e-7a4d-4e5b-9c61-2d8f0a7b6e14",
"name": "Suppliers",
"description": "Approved suppliers for invoice review",
"source": "organisation",
"fieldCount": 5,
"recordCount": 0,
"url": "https://app.checkflow.io/DataSet/Index?key=3f2b8c1e-7a4d-4e5b-9c61-2d8f0a7b6e14",
"createdDateTime": "2026-03-04T09:15:22Z",
"updatedDateTime": "2026-03-04T09:15:22Z",
"fields": [
{ "key": "a1c4e7f0-2b5d-4c8e-9f13-6d0a3b7e2c51", "name": "Supplier", "type": "text", "description": "Trading name", "position": 0 },
{ "key": "b72d9e14-5f3a-4b6c-8d20-1e9f4a7c3b68", "name": "Contact", "type": "email", "position": 1 },
{ "key": "c9e3f158-6a2b-4d7e-a041-3f8b2d6e9c74", "name": "Payment Terms", "type": "number", "description": "Days", "position": 2 },
{ "key": "d4f86a29-7b3c-4e1f-b852-5a9c0e3d7f16", "name": "Contract Renews", "type": "date", "position": 3 },
{ "key": "e5a97b3a-8c4d-4f20-9c63-7b1d2f4e8a27", "name": "Active", "type": "boolean", "position": 4 }
],
"views": [
{
"key": "6b2e4d8f-1a3c-4e5b-8d7f-0c9a2e4b6d13",
"name": "All Records",
"isDefault": true,
"filters": [],
"sorts": [],
"hiddenFieldKeys": [],
"hiddenFieldNames": []
}
]
}
Responses
| Status | Code | When |
|---|---|---|
201 Created | — | The Data Set, with its fields and its All Records view. |
400 Bad Request | VALIDATION_ERROR | name or a field name is missing or over 200 characters; two fields share a name; more than 50 fields; the workspace already has 100 Data Sets; or a field type is not one of the six (field: fields). |
403 Forbidden | FORBIDDEN | The key acts as a Member without Library.Creator. |
Create a Data Set from CSV
Creates a Data Set from CSV text: the first line becomes the fields, every later line a record. Field types are detected from the values. To load an existing Data Set, use Replace Records from CSV.
POST /v3/data-sets/import
Parameters
This endpoint takes no parameters.
Request Body
{
"name": "Suppliers",
"description": "Imported from the finance system",
"csv": "Supplier,Contact,Payment Terms,Active\nAcme Office Supplies Ltd,accounts@acme-office.example,30,true\n\"Harbour Print Co, Leeds\",,14,false\n"
}
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Up to 200 characters. There is no file name to fall back on. |
description | string | No | A description of the Data Set. |
csv | string | Yes | The file's text, following the CSV format. |
Example
POST https://api.checkflow.io/v3/data-sets/import
X-API-KEY: your-api-key-here
Content-Type: application/json
{
"name": "Suppliers",
"description": "Imported from the finance system",
"csv": "Supplier,Contact,Payment Terms,Active\nAcme Office Supplies Ltd,accounts@acme-office.example,30,true\n\"Harbour Print Co, Leeds\",,14,false\n"
}
HTTP/1.1 201 Created
{
"dataSet": {
"key": "3f2b8c1e-7a4d-4e5b-9c61-2d8f0a7b6e14",
"name": "Suppliers",
"description": "Imported from the finance system",
"source": "organisation",
"fieldCount": 4,
"recordCount": 2,
"url": "https://app.checkflow.io/DataSet/Index?key=3f2b8c1e-7a4d-4e5b-9c61-2d8f0a7b6e14",
"createdDateTime": "2026-03-04T09:15:22Z",
"updatedDateTime": "2026-03-04T09:15:22Z",
"fields": [
{ "key": "a1c4e7f0-2b5d-4c8e-9f13-6d0a3b7e2c51", "name": "Supplier", "type": "text", "position": 0 },
{ "key": "b72d9e14-5f3a-4b6c-8d20-1e9f4a7c3b68", "name": "Contact", "type": "email", "position": 1 },
{ "key": "c9e3f158-6a2b-4d7e-a041-3f8b2d6e9c74", "name": "Payment Terms", "type": "number", "position": 2 },
{ "key": "e5a97b3a-8c4d-4f20-9c63-7b1d2f4e8a27", "name": "Active", "type": "boolean", "position": 3 }
],
"views": [
{
"key": "6b2e4d8f-1a3c-4e5b-8d7f-0c9a2e4b6d13",
"name": "All Records",
"isDefault": true,
"filters": [],
"sorts": [],
"hiddenFieldKeys": [],
"hiddenFieldNames": []
}
]
},
"recordsImported": 2,
"fieldsAdded": ["Supplier", "Contact", "Payment Terms", "Active"]
}
| Field | Type | Description |
|---|---|---|
dataSet | Data Set | The new Data Set, with its fields and views. |
recordsImported | integer | How many records the file created. Skipped blank lines are not counted. |
fieldsAdded | array of string | The fields created, in column order — here, every column. |
Responses
| Status | Code | When |
|---|---|---|
201 Created | — | The Data Set was created and filled. |
400 Bad Request | VALIDATION_ERROR | csv is missing, blank or over 5 MB (field: csv); the file breaks a CSV format rule; name is missing or over 200 characters; or the workspace already has 100 Data Sets. |
403 Forbidden | FORBIDDEN | The key acts as a Member without Library.Creator. |
Notes
- No
data_set.record.createdwebhook events are raised for the records the file creates.
Get a Data Set
Returns one Data Set with its fields and views, but not its records — use List Records for those. Read this before writing records (for the field names) or linking a template control (for the view keys).
GET /v3/data-sets/{dataSetKey}
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
dataSetKey | path | string | Yes | The Data Set's key, or a system Data Set's slug. |
Example
GET https://api.checkflow.io/v3/data-sets/3f2b8c1e-7a4d-4e5b-9c61-2d8f0a7b6e14
X-API-KEY: your-api-key-here
HTTP/1.1 200 OK
The body is a Data Set object, as in the example there.