Tables API
Create tables, define columns and read or write rows with the Tables API, with the parameters, request bodies and responses of each endpoint.
A table holds rows of working data in Loopfour, such as an open-items list or a vendor master. Workflows read and write tables with the Data Table block, and dashboards chart them. Use these endpoints to manage tables and rows from your own systems.
| Scope | Endpoints |
|---|---|
tables:read (or legacy workflows:read) | List and get tables, list and query rows, column values, list folders |
tables:write (or legacy workflows:write) | Create, import, update and delete tables, write rows, folders |
Table object
| Field | Type | Meaning |
|---|---|---|
id | string (UUID) | Table ID |
name | string | 1 to 255 characters |
description | string or null | |
columns | object[] | Column definitions, see below |
schema | object | Stored schema, including columns |
metadata | object | Free-form |
maxRows | integer | Row limit. Default 10,000, maximum 100,000 |
rowCount | integer | Rows in the table |
createdAt, updatedAt | string (ISO 8601) |
Columns
| Field | Type | Required | Meaning |
|---|---|---|---|
id | string | No | Storage key, up to 255 characters. Generated when left out. Row data is keyed by column id |
name | string | Yes | Display name |
type | string | Yes | text, number, boolean, date, select, url, email or json |
required | boolean | No | Default false |
options | string[] | No | Choices for a select column |
List tables
GET /api/v1/tablesReturns every table of your company in data. There is no pagination.
Create a table
POST /api/v1/tables| Field | Type | Required | Meaning |
|---|---|---|---|
name | string | Yes | 1 to 255 characters |
description | string | No | |
columns | object[] | No | Column definitions |
schema | object | No | Schema metadata. Its columns are used when columns is left out |
metadata | object | No | |
maxRows | integer | No | 1 to 100,000. Default 10,000 |
curl -X POST 'https://workflow.loopfour.ai/api/v1/tables' \
-H 'x-api-key: wfk_live_your_api_key' \
-H 'Content-Type: application/json' \
-d '{
"name": "Open AR items",
"description": "Invoices past due, refreshed each morning",
"columns": [
{ "id": "invoice_number", "name": "Invoice number", "type": "text", "required": true },
{ "id": "customer", "name": "Customer", "type": "text" },
{ "id": "amount_due", "name": "Amount due", "type": "number" },
{ "id": "due_date", "name": "Due date", "type": "date" },
{ "id": "stage", "name": "Stage", "type": "select", "options": ["Reminder sent", "Escalated", "Paid"] }
]
}'The response is 201 with the table object.
Import a table with rows
POST /api/v1/tables/importTakes the create fields, with columns and rows required. rows is an array of objects keyed by column ID. More rows than maxRows returns 400. The table and its rows are created together; the response is 201 with the table.
Get, update and delete a table
GET /api/v1/tables/{id}
PATCH /api/v1/tables/{id}
DELETE /api/v1/tables/{id}Update takes any of name, description, columns, schema, metadata and maxRows, and returns the table. Delete removes the table and its rows and returns { "deleted": true }. An unknown table returns 404.
Read rows
GET /api/v1/tables/{id}/rows
POST /api/v1/tables/{id}/rows/queryBoth take the same options: as query parameters on GET, and as a JSON body on POST /rows/query. Use the POST form for long filters.
| Option | Type | Default | Meaning |
|---|---|---|---|
limit | integer | 50 | 1 to 100 |
offset | integer | 0 | Rows to skip |
filter | object | Filter, see below. On GET, URL-encoded JSON | |
sort | string | Column ID to sort by | |
order | string | asc | asc or desc |
curl -X POST 'https://workflow.loopfour.ai/api/v1/tables/5b9c1e2a-7d4f-4a3b-8e61-0c2d9f7a1b34/rows/query' \
-H 'x-api-key: wfk_live_your_api_key' \
-H 'Content-Type: application/json' \
-d '{
"filter": {
"amount_due": { "condition": { "operator": "greater_than", "value": 10000 } },
"stage": { "excludedValues": ["Paid"] }
},
"sort": "due_date",
"limit": 50
}'{
"success": true,
"data": [
{
"id": "e2a7c4d1-3b5f-4c8e-9a10-6f2b8d4e1c73",
"data": {
"invoice_number": "INV-1042",
"customer": "Acme Supplies",
"amount_due": 12400,
"due_date": "2026-01-15",
"stage": "Escalated"
},
"position": 0,
"createdAt": "2026-02-01T07:00:00.000Z",
"updatedAt": "2026-02-10T07:00:00.000Z"
}
],
"meta": { "total": 1, "limit": 50, "offset": 0, "hasMore": false }
}Filters
A filter is an object keyed by column ID. Each clause has at least one of:
| Key | Meaning |
|---|---|
condition | { "operator": "...", "value": ... }. Operators: contains, not_contains, equals, not_equals, starts_with, ends_with, greater_than, greater_than_or_equal, less_than, less_than_or_equal, between, is_empty, is_not_empty. between takes { "start": "YYYY-MM-DD", "end": "YYYY-MM-DD" } |
values | Keep rows whose value is in this list (up to 500 values) |
excludedValues | Drop rows whose value is in this list. Cannot be combined with values |
All clauses must match. A filter on an unknown column, or a serialized filter over 50,000 characters, returns 400.
Write rows
POST /api/v1/tables/{id}/rows
PATCH /api/v1/tables/{id}/rows/{rowId}
DELETE /api/v1/tables/{id}/rows/{rowId}| Field | Type | Required | Meaning |
|---|---|---|---|
data | object | Yes | Row values keyed by column ID. Keys that are not columns are kept |
merge | boolean | No | Update only. true merges into the stored values; false (default) replaces them |
Create returns 201 with the row. A table at its maxRows limit returns 400 (Table has reached its maximum row limit of 10000). Update returns the row. Delete returns { "deleted": true }. An unknown row returns 404.
Column values
GET /api/v1/tables/{id}/columns/{columnId}/values
POST /api/v1/tables/{id}/columns/{columnId}/values/queryReturns the distinct values of one column with their counts, for building pickers and filters. Options are filter, search (up to 255 characters) and limit (default 100, maximum 500). The response is { "values": [{ "value": ..., "label": "...", "count": 3 }], "truncated": false }.
Folders
GET /api/v1/table-folders
POST /api/v1/table-folders
PATCH /api/v1/table-folders/tables/{tableId}List returns every folder with id, name, tableIds, createdAt and updatedAt. Create takes name (1 to 255 characters) and returns 201; a name already in use returns 409. Move takes folderId (a folder UUID, or null for the top level) and returns { "tableId": "...", "folderId": "..." }.
Errors
| Status | Code | Cause |
|---|---|---|
400 | BAD_REQUEST | Row limit reached, too many import rows, invalid filter or column |
400 | ZodError in error.name | Body or query does not match the schema |
401 | UNAUTHORIZED | Missing or invalid API key |
403 | FORBIDDEN | Key lacks the scope |
404 | NOT_FOUND | Table or row not found |
409 | CONFLICT | Folder name already used |
429 | RATE_LIMITED | Rate limit exceeded |
Loopfour