Loopfour
API reference

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.

ScopeEndpoints
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

FieldTypeMeaning
idstring (UUID)Table ID
namestring1 to 255 characters
descriptionstring or null
columnsobject[]Column definitions, see below
schemaobjectStored schema, including columns
metadataobjectFree-form
maxRowsintegerRow limit. Default 10,000, maximum 100,000
rowCountintegerRows in the table
createdAt, updatedAtstring (ISO 8601)

Columns

FieldTypeRequiredMeaning
idstringNoStorage key, up to 255 characters. Generated when left out. Row data is keyed by column id
namestringYesDisplay name
typestringYestext, number, boolean, date, select, url, email or json
requiredbooleanNoDefault false
optionsstring[]NoChoices for a select column

List tables

GET /api/v1/tables

Returns every table of your company in data. There is no pagination.

Create a table

POST /api/v1/tables
FieldTypeRequiredMeaning
namestringYes1 to 255 characters
descriptionstringNo
columnsobject[]NoColumn definitions
schemaobjectNoSchema metadata. Its columns are used when columns is left out
metadataobjectNo
maxRowsintegerNo1 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/import

Takes 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/query

Both 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.

OptionTypeDefaultMeaning
limitinteger501 to 100
offsetinteger0Rows to skip
filterobjectFilter, see below. On GET, URL-encoded JSON
sortstringColumn ID to sort by
orderstringascasc 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:

KeyMeaning
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" }
valuesKeep rows whose value is in this list (up to 500 values)
excludedValuesDrop 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}
FieldTypeRequiredMeaning
dataobjectYesRow values keyed by column ID. Keys that are not columns are kept
mergebooleanNoUpdate 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/query

Returns 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

StatusCodeCause
400BAD_REQUESTRow limit reached, too many import rows, invalid filter or column
400ZodError in error.nameBody or query does not match the schema
401UNAUTHORIZEDMissing or invalid API key
403FORBIDDENKey lacks the scope
404NOT_FOUNDTable or row not found
409CONFLICTFolder name already used
429RATE_LIMITEDRate limit exceeded

On this page