Serenities AI™
Back to API Overview

Base & Tables API

Manage bases, tables, fields, and records. This router provides full CRUD operations for your database infrastructure.

47 endpoints

Endpoints

POST
/api/v1/tables/createDatabase

Create a new database (base). Params: name (required), description, icon, color (hex like "#3B82F6"). Returns the created database with its ID.

Parameters

namestringrequired

Name for the new database/base

descriptionstring

Optional description of the database

iconstring

Icon name (e.g., "database", "folder")

colorstring

Hex color code (e.g., "#3B82F6")

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/createDatabase \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "example",
  "description": "example",
  "icon": "example"
}'
POST
/api/v1/tables/listDatabases

List all databases (bases) for the current user. Returns array with id, name, description, tables[]. Use this first to get database IDs.

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/listDatabases \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{}'
POST
/api/v1/tables/getDatabaseById

Get a specific database by ID. Param "id" is a database ID from listDatabases. Returns database with its tables.

Parameters

idstringrequired

Database ID from listDatabases

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/getDatabaseById \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "id": "example"
}'
POST
/api/v1/tables/updateDatabase

Update a database. Params: id (database ID), name, description, icon, color. Only provided fields are updated.

Parameters

idstringrequired

Database ID from listDatabases

namestring

New name for the database

descriptionstring

New description

iconstring

New icon name

colorstring

New hex color code

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/updateDatabase \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "id": "example",
  "name": "example",
  "description": "example"
}'
POST
/api/v1/tables/deleteDatabase

Delete a database and all its tables. Param "id" is the database ID. WARNING: This is permanent.

Parameters

idstringrequired

Database ID to delete. WARNING: This permanently deletes all tables and data.

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/deleteDatabase \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "id": "example"
}'
POST
/api/v1/tables/createTable

Create a new table. Params: databaseId (required, from listDatabases), name (required), description, icon, color. A table a site's pages use goes in that site's linked base (site_getProject → linkedBase.id); a site reads another base's table only through an account function (functions.invokeAccount; automations_createFunction + grants.bases + accessRules.execute). Recreating tables in another base, or copying or moving rows between bases, is work beyond the request: ask the person first. A base another site links: offer the account-function route (no copy, recommended) or the move. A base no site links (no account function reaches it): offer the copy into this site's base (tables_duplicateTable with includeRows: true; recommended, the only way the site can show it) or leaving it.

Parameters

databaseIdstringrequired

Database ID from listDatabases where the table will be created

namestringrequired

Name for the new table

descriptionstring

Optional description of the table

iconstring

Icon name (e.g., "table", "users")

colorstring

Hex color code (e.g., "#10B981")

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/createTable \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "databaseId": "example",
  "name": "example",
  "description": "example"
}'
POST
/api/v1/tables/listTables

List all tables across all databases. Returns array with id, name, databaseId. Use getTableById for full details.

Parameters

databaseIdstring

Limit to one base — omit for all tables across all bases

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/listTables \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "databaseId": "example"
}'
POST
/api/v1/tables/getViewsByTableId

Alias for getViews. Param "tableId".

Parameters

tableIdstringrequired

Table ID from listTables

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/getViewsByTableId \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "tableId": "example"
}'
POST
/api/v1/tables/getFieldsByTableId

Get all fields/columns for a table. Param "tableId" from listTables. Returns array with field id, name, fieldType, options.

Parameters

tableIdstringrequired

Table ID from listTables

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/getFieldsByTableId \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "tableId": "example"
}'
POST
/api/v1/tables/getTableById

Get table with fields and views. Param "id" is a table ID from listTables. Returns table with fields[], views[], database info.

Parameters

idstringrequired

Table ID from listTables

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/getTableById \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "id": "example"
}'
POST
/api/v1/tables/updateTable

Update table properties. Params: id (table ID), name, description, icon, color. Renaming a table a function with Full data access reads by name (or renaming another into that name) is prepared as a card a person taps: the answer carries pendingChange and nothing changes until they approve. A function with Full data access whose code reaches the tables counts as reading every table of its base.

Parameters

idstringrequired

Table ID from listTables

namestring

New name for the table

descriptionstring

New description

iconstring

New icon name

colorstring

New hex color code

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/updateTable \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "id": "example",
  "name": "example",
  "description": "example"
}'
POST
/api/v1/tables/deleteTable

Delete a table and all its rows. Param "id" is the table ID. WARNING: This is permanent. Deleting a table a function with Full data access reads by name is prepared as a card a person taps (pendingChange; it can't be undone once applied).

Parameters

idstringrequired

Table ID to delete. WARNING: This permanently deletes all rows and fields.

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/deleteTable \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "id": "example"
}'
POST
/api/v1/tables/createField

Create a new field/column. Params: tableId (required), name (required), fieldType (required: "text", "longtext", "number", "email", "date", "datetime", "checkbox", "select", "multiselect", "url", "phone", "currency", "percent", "rating", "duration", "file", "link", "lookup", "rollup", "formula", "button", "status", "slug", "hash", "color", "json", "secret", "created_time", "updated_time" — note: file uploads use "file", there is no "attachment" type; "secret" = encrypted at rest and MASKED in every read (grid, API, exports, even function reads); nobody reads it back, the owner included, it can only be replaced — right for sensitive data IN records, but code cannot use a secret field's value, so API keys belong in env/Secrets, not here). Optional: description, unique, defaultValue, options (for select/multiselect: a FLAT array [{value, label, color?}] — label is required, color is #RRGGBB; no "choices" wrapper and no "id"). Note: "required" is not supported — pass false or omit it.

Parameters

tableIdstringrequired

Table ID from listTables

namestringrequired

Name for the new field/column

fieldTypestringrequired

Field type: "text", "number", "select", "multiselect", "date", "datetime", "checkbox", "url", "email", "phone", "file", "formula", "rating", "currency", "percent", "duration", "status", "button", "link", "lookup", "rollup". READ-ONLY computed types (writes to them are dropped): "hash" = Hash ID, a read-only stable per-row identifier — it does NOT hash a value you supply (to store a hashed value, compute it yourself and use text/secret); "created_time"/"updated_time" = system timestamps; "formula"/"lookup"/"rollup" = computed. Numbers are exact only up to 2^53 — unsafe integers are rejected with guidance; store long IDs, account numbers, and IMEIs as text.

descriptionstring

Optional description of the field

requiredboolean

Must be false or omitted. Required fields are not supported - UI creates blank rows first.

uniqueboolean

Whether values must be unique (default: false)

defaultValueany

Default value for new rows

optionsarray

For select/multiselect: [{value: "opt1", label: "Option 1", color: "#3b82f6"}]

statusLabelsarray

For status: [{value: "todo", label: "To Do", color: "#6b7280"}]

formulaConfigobject

For formula. Values of a formula without NOW()/TODAY() are stored on the row, recalculated when inputs change (also through links) and filterable everywhere; with NOW()/TODAY() they are display-only and not filterable

buttonConfigobject

For button: {actionType: "flow", flowId: "xxx"}

numberFormatConfigobject

For number: {decimalPlaces: 2, useThousandSeparator: true}

currencyFormatConfigobject

For currency: {currencySymbol: "$", symbolPosition: "before"}

percentFormatConfigobject

For percent: {decimalPlaces: 1}

lookupConfigobject

For lookup. Its COUNT counts NON-EMPTY VALUES (a rollup COUNT counts linked records). Scalar (non-chained) aggregations are filterable everywhere (view filters, filterRows, entities.filter())

rollupConfigobject

For rollup. COUNT counts LINKED RECORDS, empty values included (COUNT_FILLED = lookup COUNT). Scalar (non-chained) aggregations are filterable everywhere (view filters, filterRows, entities.filter())

linkedTableIdstring

For link field: target table ID

linkTypestring

Link relationship type

displayOrderstring

Fractional order string (e.g., "a0", "b0")

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/createField \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "tableId": "example",
  "name": "example",
  "fieldType": "example"
}'
POST
/api/v1/tables/updateField

Update field properties. Params: id (field ID from getFieldsByTableId), name, fieldType, description, unique, options (flat [{value, label, color?}]), etc. Same field-type set as createField; "required" must be false or omitted. Renaming, retyping, or changing what it links to or looks up, a field of a table a function with Full data access reads by name is prepared as a card a person taps (pendingChange): send that change on its own.

Parameters

idstringrequired

Field ID from getFieldsByTableId

namestring

New name for the field

fieldTypestring

Field type

descriptionstring

New description

requiredboolean

Must be false or omitted. Required fields are not supported - UI creates blank rows first.

uniqueboolean

Whether values must be unique

defaultValueany

Default value for new rows

optionsarray

For select/multiselect: [{value: "opt1", label: "Option 1", color: "#3b82f6"}]

statusLabelsarray

For status: [{value: "todo", label: "To Do", color: "#6b7280"}]

formulaConfigobject

For formula. Values of a formula without NOW()/TODAY() are stored on the row, recalculated when inputs change (also through links) and filterable everywhere; with NOW()/TODAY() they are display-only and not filterable

linkedTableIdstring

For link field: target table ID

linkTypestring

Link relationship type

linkDisplayModestring

How to display linked records

linkDisplayFieldIdstring

Which field to show from linked records

allowMultipleLinksboolean

Whether multiple links are allowed

linkOnDeletestring

For link fields: what happens to linking rows when a linked record is deleted. RESTRICT blocks the delete; CASCADE deletes the linking rows too (chains, capped at 500 rows per delete); SET_NULL (default) just removes the connection.

buttonConfigobject

For button: {actionType: "flow", flowId: "xxx"}

numberFormatConfigobject

For number: {decimalPlaces: 2, useThousandSeparator: true}

currencyFormatConfigobject

For currency: {currencySymbol: "$", symbolPosition: "before"}

percentFormatConfigobject

For percent: {decimalPlaces: 1}

lookupConfigobject

For lookup. Its COUNT counts NON-EMPTY VALUES (a rollup COUNT counts linked records). Scalar (non-chained) aggregations are filterable everywhere (view filters, filterRows, entities.filter())

rollupConfigobject

For rollup. COUNT counts LINKED RECORDS, empty values included (COUNT_FILLED = lookup COUNT). Scalar (non-chained) aggregations are filterable everywhere (view filters, filterRows, entities.filter())

displayOrdernumber

Display order position

isVisibleboolean

Whether field is visible

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/updateField \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "id": "example",
  "name": "example",
  "fieldType": "example"
}'
POST
/api/v1/tables/deleteField

Delete a field/column. Param "id" is the field ID. WARNING: Deletes all data in this column. Deleting a field of a table a function with Full data access reads by name is prepared as a card a person taps (pendingChange; it can't be undone once applied).

Parameters

idstringrequired

Field ID to delete. WARNING: This permanently deletes all data in this column.

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/deleteField \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "id": "example"
}'
POST
/api/v1/tables/createRow

Create a new row. Params: tableId (required), data (object keyed by field ID from getFieldsByTableId — field NAMES are also accepted and auto-normalized, but IDs are safest since names can change). Example: {tableId: "xxx", data: {"fieldId1": "text value", "fieldId2": 123}}.

Parameters

tableIdstringrequired

Table ID from listTables

dataobjectrequired

Object where keys are FIELD IDs from getFieldsByTableId, values are cell values. Example: {"fieldId1": "text", "fieldId2": 123}

rowOrderstring

Fractional order string for positioning. Leave empty to append at end.

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/createRow \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "tableId": "example",
  "data": {},
  "rowOrder": "example"
}'
POST
/api/v1/tables/report

Answer a numbers question about a table in one call (counts, totals, averages per slice) — never write SQL. Params: tableId; measures (1-8: {as, fn: count|countDistinct|sum|avg|min|max|median|ratio, field: {fieldId}, filter?, numerator?/denominator? (ratio: the "as" of two other measures), decimals? 0-6}); filter? (which rows count — the view filter language {logic:"and"|"or", conditions:[{fieldId, operator, value}]}, up to 30 conditions, a nested group counting as one, 3 levels); groupBy? (up to 3: {as, field: {fieldId}, bucket?: day|week|month|quarter|year|hour|dayOfWeek for date fields}); having? ({measure, operator, value}); sort? ({by: an "as", direction}); limit? (1-1000, default 100) and offset? (pages, hasMore says more exist); percentOfTotal? (the "as" of a count or sum: each slice's share of ALL slices, 0-1); timeZone? (IANA, default UTC; week starts Monday). A measure with a filter counts only its rows (win rate = ratio of a filtered count over count). Empty values form one null slice. Returns {columns, rows: [{groups, measures, percent?}], hasMore, total, timeZone, truncatedAt?}. Not yet possible: grouping through a linked record, lookup/rollup fields, running totals, period-over-period in one report, cohorts, two-level aggregates (use two reports). Sign-in and secret fields can never be used. Example: {tableId, filter:{logic:"and",conditions:[{fieldId:"<Status id>",operator:"equals",value:"Paid"}]}, groupBy:[{as:"month",field:{fieldId:"<Created id>"},bucket:"month"}], measures:[{as:"orders",fn:"count"},{as:"revenue",fn:"sum",field:{fieldId:"<Amount id>"}}]}.

Parameters

filterany

Which rows count: {logic: "and"|"or", conditions: [{fieldId, operator, value}]} — the view filter language

groupByarray

Up to 3 slices

measuresarrayrequired

1 to 8 numbers to work out per slice

havingarray

Keep only slices whose measure passes (up to 4)

sortarray

Order by group or measure names (up to 3)

limitinteger

Slices per answer, 1-1000 (default 100)

offsetinteger

Slices to skip, for the next page (max 100000)

percentOfTotalstring

The `as` of a count or sum measure: adds each slice's share of the whole (0-1)

timeZonestring

IANA time zone for date slices, e.g. "Europe/Berlin" (default UTC)

tableIdstringrequired

Table ID from listTables

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/report \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "filter": {},
  "groupBy": {},
  "measures": {}
}'
POST
/api/v1/tables/getRowCount

Get total row count. Param "tableId". Returns {count: number}.

Parameters

tableIdstringrequired

Table ID from listTables

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/getRowCount \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "tableId": "example"
}'
POST
/api/v1/tables/getRows

Get rows from a table. Params: tableId (required), limit (default 100), offset (default 0). Returns {rows: [{id, data: {fieldId: value}}], total}. Data keys are field IDs, not names.

Parameters

tableIdstringrequired

Table ID from listTables

limitnumber

Rows to return, 1-1000 (default: 100). Values outside the range are REJECTED, not clamped — page with offset for more

offsetnumber

Number of rows to skip for pagination

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/getRows \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "tableId": "example",
  "limit": 0,
  "offset": 0
}'
POST
/api/v1/tables/updateRow

Update a row. Params: id (row ID from getRows), data (partial object keyed by field ID; exact field names are also accepted). Only provided fields are updated.

Parameters

idstringrequired

Row ID from getRows

dataobjectrequired

Object with field IDs as keys and new values. Only provided fields are updated.

metadataobject

Optional metadata to merge with existing

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/updateRow \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "id": "example",
  "data": {},
  "metadata": {}
}'
POST
/api/v1/tables/restoreRow

Undo a row delete: bring back a row this same person deleted (with deleteRow or bulkDeleteRows) in the last ten minutes, at its old id, exactly as it was (its values are the copy kept when it was deleted). Params: tableId, rowId (the deleted row's id), rowOrder? (a row position the grid made, e.g. one taken from getRows; anything else is refused; default: where it was). Each delete can be undone once. A row deleted by someone else, longer ago, or never deleted answers "Row not found". Returns { id, tableId, data, rowOrder, createdAt, updatedAt } (secret values masked).

Parameters

tableIdstringrequired

The table the row was deleted from

rowIdstringrequired

The deleted row's id

rowOrderstring

Where to put it back (a row position the grid made; a bad one is refused); default: where it was

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/restoreRow \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "tableId": "example",
  "rowId": "example",
  "rowOrder": "example"
}'
POST
/api/v1/tables/deleteRow

Delete a row. Param "id" is the row ID.

Parameters

idstringrequired

Row ID to delete

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/deleteRow \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "id": "example"
}'
POST
/api/v1/tables/bulkDeleteRows

Delete multiple rows. Params: tableId, rowIds (array of row IDs).

Parameters

tableIdstringrequired
rowIdsarrayrequired

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/bulkDeleteRows \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "tableId": "example",
  "rowIds": {}
}'
POST
/api/v1/tables/createView

Create a new view. Params: tableId (required), name (required), viewType (required: "grid", "kanban", "calendar", "gallery", "form"), description, filterConfig, sortConfig, groupConfig, visibleFields (array of field IDs).

Parameters

tableIdstringrequired
namestringrequired
viewTypestringrequired
descriptionstring
filterConfigany
sortConfigany
groupConfigany
visibleFieldsarray
columnWidthsany
isDefaultboolean

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/createView \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "tableId": "example",
  "name": "example",
  "viewType": "example"
}'
POST
/api/v1/tables/updateView

Update view settings. Params: id (view ID), name, filterConfig, sortConfig, groupConfig, visibleFields, etc.

Parameters

idstringrequired
namestring
descriptionstring
filterConfigany
sortConfigany
groupConfigany
visibleFieldsarray
columnWidthsany
isDefaultboolean
metadataany

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/updateView \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "id": "example",
  "name": "example",
  "description": "example"
}'
POST
/api/v1/tables/getViews

List all views for a table. Param "tableId". Returns array with id, name, viewType, filterConfig, sortConfig.

Parameters

tableIdstringrequired

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/getViews \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "tableId": "example"
}'
POST
/api/v1/tables/reorderView

Change view position. Params: viewId, newOrderIndex (string).

Parameters

viewIdstringrequired
newOrderIndexstringrequired

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/reorderView \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "viewId": "example",
  "newOrderIndex": "example"
}'
POST
/api/v1/tables/duplicateView

Copy a view. Params: id (view ID to copy), name (optional, for the copy).

Parameters

idstringrequired
namestring

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/duplicateView \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "id": "example",
  "name": "example"
}'
POST
/api/v1/tables/setDefaultView

Set which view opens by default. Params: id (view ID), tableId.

Parameters

idstringrequired
tableIdstringrequired

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/setDefaultView \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "id": "example",
  "tableId": "example"
}'
POST
/api/v1/tables/deleteView

Delete a view. Param "id" is the view ID.

Parameters

idstringrequired

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/deleteView \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "id": "example"
}'
POST
/api/v1/tables/updateFieldOrder

Change field position. Params: fieldId, newOrder (fractional string).

Parameters

fieldIdstringrequired
newOrderstringrequired

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/updateFieldOrder \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "fieldId": "example",
  "newOrder": "example"
}'
POST
/api/v1/tables/updateTableOrder

Change table position in database. Params: tableId, newOrder (string like "a0", "b0" for fractional ordering).

Parameters

tableIdstringrequired
newOrderstringrequired

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/updateTableOrder \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "tableId": "example",
  "newOrder": "example"
}'
POST
/api/v1/tables/updateFieldWidth

Set column width in pixels. Params: fieldId, width (number, e.g., 200).

Parameters

fieldIdstringrequired
widthnumberrequired

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/updateFieldWidth \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "fieldId": "example",
  "width": 0
}'
POST
/api/v1/tables/updateRowOrder

Change row position. Params: rowId, newOrder (fractional string).

Parameters

rowIdstringrequired

Row ID to reorder

newOrderstringrequired

New fractional order string

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/updateRowOrder \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "rowId": "example",
  "newOrder": "example"
}'
POST
/api/v1/tables/bulkUpdateRows

Update multiple rows in one call. Params: tableId, updates (array of {rowId, data}). Each update merges its data with the existing row — fields not included are preserved, identical semantics to updateRow. More efficient than calling updateRow in a loop.

Parameters

tableIdstringrequired

Table ID from listTables

updatesarrayrequired

Array of row updates

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/bulkUpdateRows \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "tableId": "example",
  "updates": {}
}'
POST
/api/v1/tables/exportTable

Export all table data. Param "tableId". Returns {tableName, fields[], rows[]} with human-readable field names as keys.

Parameters

tableIdstringrequired

Table ID to export

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/exportTable \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "tableId": "example"
}'
POST
/api/v1/tables/importRows

Bulk import rows. Params: tableId, rows (array of data objects), matchFieldsByName (default true - use field NAMES as keys instead of IDs), fieldDefinitions (optional, to create new fields). When matchFieldsByName=true, use {"Field Name": value} instead of {"fieldId": value}. Send about 5,000 rows a call, fewer for a wide table: a call takes at most 100,000 items in one list and 1,000,000 values in all. A file in several calls: call it with checkOnly=true and firstRowNumber for every part first (nothing is written), then import the parts, so a wrong row anywhere imports nothing, except values that clash between parts or a limit reached by the parts together. Copying tables or rows into another base is work beyond most requests: only when the person asked for the copy.

Parameters

tableIdstringrequired

Table ID from listTables

rowsarrayrequired

Array of row data. When matchFieldsByName=true, use field NAMES as keys. When false, use field IDs. LINKED-RECORD columns resolve per value: sibling "<Field> (id)" column → row IDs → JSON-array string (comma-safe) → exact label → unique case-insensitive label; unresolvable values are returned in unresolvedLinks (row still imports, link skipped — never guessed, never auto-created). Relations are written both directions.

matchFieldsByNameboolean

If true, match columns by field name. If false, use field IDs as keys (default: true)

fieldDefinitionsarray

Optional field definitions to create new fields during import

checkOnlyboolean

Check these rows exactly as an import would, writing nothing (no rows, no columns). To import a file in several calls, check every part first, then import them: a wrong row anywhere then imports nothing, except values that clash between parts or a limit reached by the parts together.

firstRowNumberinteger

The file's row number of rows[0] when a file goes in several calls (default 1), so an error names the row in the file.

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/importRows \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "tableId": "example",
  "rows": {},
  "matchFieldsByName": true
}'
POST
/api/v1/tables/exportBase

Export one whole base (all its tables, fields, rows and views) as one file in the format tables_importBase takes. Param "databaseId" (from listDatabases). Returns { formatVersion, exportedAt, base, tables: [{ name, fields, rows, views }] }, the same file the Export button saves. Each row carries its id (__rowId) so an import can put linked records back; a secret column comes back blank. The file is at most 256 KB: a bigger base is refused with its size, so read it one table at a time with tables_getRows instead. For one table only use tables_exportTable. Copying tables or rows into another base is work beyond most requests: only when the person asked for the copy.

Parameters

databaseIdstringrequired

The base to export (an id from listDatabases)

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/exportBase \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "databaseId": "example"
}'
POST
/api/v1/tables/importBase

Make a new base from a file tables_exportBase gave. Params: data (required: the whole file, exactly as exported), name (optional: the new base's name; default the file's own name). It always makes a new base with all-new ids in this workspace and never changes, overwrites or deletes an existing base, table or row; a base of the same name becomes a second base. It counts against the plan: the number of bases, the rows a table may hold on plans with a row limit, and the database storage; when a limit is reached nothing is made and the answer says which. Returns { databaseId, tablesCreated, linkedRecordsRestored, unresolvedLinks, message }; unresolvedLinks lists link cells that named a row the file does not hold (nothing is linked for them). Copying tables or rows into another base is work beyond most requests: only when the person asked for the copy.

Parameters

namestring

Name for the new base (default: the file's own name)

dataobjectrequired

The whole file tables_exportBase gave, exactly as exported

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/importBase \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "example",
  "data": {}
}'
POST
/api/v1/tables/filterRows

Filter rows by field values using the SDK operator syntax (postgres-side, scales to large tables — uses a GIN index on data jsonb). Returns { rows, total, tableId, tableName, limit, offset }. Params: - tableId (required) - filter (object): keys are field IDs OR field names (case-insensitive). Multiple keys are AND-ed. Each value is a plain primitive (equality) or an operator dict. - sort: rowOrder, createdAt, updatedAt or any field name/id, optionally prefixed with "-" for descending. Numbers sort numerically, text case-insensitively, blanks last. Default: rowOrder asc. - limit: default 100, max 1000 - offset: default 0 - includeReadable: include human-readable field-name keys in output (default false; turn on only when needed). Operators: $eq, $ne, $gt, $gte, $lt, $lte, $between, $contains, $startsWith, $endsWith, $regex, $in, $nin, $not, $isEmpty, $isNotEmpty. Combine whole filters with { "$or": [...] } and { "$and": [...] }. Compare two fields of a row with $eqField/$neField/$gtField/$gteField/$ltField/$lteField. Examples: - { "Status": "todo" } → equality - { "Priority": { "$gt": 5 } } → numeric comparison - { "Title": { "$contains": "urgent" } } → substring (case-insensitive) - { "Status": { "$in": ["todo", "in-progress"] } } → multi-value - { "DueDate": { "$isNotEmpty": true } } → presence check - { "Status": "done", "Assignee": "Alice" } → AND across keys Validation: unknown field keys or unknown operators throw 400 with the list of valid options — typos surface immediately rather than returning all rows. Caveat: $gt/$gte/$lt/$lte on text fields that store numeric-looking strings ("16", "8") compare LEXICOGRAPHICALLY, so "8" > "10" is true. Use these operators only on fields whose values are stored as proper numbers, or post-filter client-side for text-stored numerics. Same operator dict as the published-app runtime SDK's entities.<Table>.filter().

Parameters

tableIdstringrequired

Table ID to filter

filterobject

Filter object. Keys are field IDs OR field names (case-insensitive). Multiple keys are AND-ed; { "$or": [ {...}, {...} ] } and { "$and": [...] } group full filter objects and nest arbitrarily. Each value is either a plain primitive (equality), or an operator dict. Operators: $eq, $ne, $gt, $gte, $lt, $lte, $between ([min,max] inclusive), $contains, $startsWith, $endsWith, $regex (Postgres syntax, case-sensitive — prefix (?i) for insensitive), $in, $nin (blanks count as not-in-list), $not (complement of an inner operator dict), $isEmpty, $isNotEmpty. Field-to-field: $eqField/$neField/$gtField/$gteField/$ltField/$lteField compare against ANOTHER field of the same row by name/id — { "Spent": { "$gtField": "Budget" } } finds over-budget rows. Example: { "Status": "todo", "Priority": { "$between": [5, 9] }, "$or": [{ "Tags": { "$contains": "urgent" } }, { "Due": { "$isEmpty": true } }] } Validation: unknown field keys, unknown operators, and operator/value type mismatches throw 400 with the valid options — typos surface immediately instead of returning all (or wrong) rows. Semantics: $contains is case-insensitive and matches LITERALLY (%, _ and \ are escaped); $eq is exact and case-sensitive with NO type coercion ({Score: "50"} does not match the number 50); { field: null } means "is empty"; $in: [] matches NOTHING. Field types: multiselect filters by MEMBERSHIP ($contains value, $in = any-of, plain scalar = has that tag). created_time/updated_time compare as dates ($gte/$lt ranges; ISO strings). Link fields filter by linked ROW ID via the relation table ($contains id, $in [ids], $isEmpty/$isNotEmpty). Caveat: $gt/$gte/$lt/$lte on text fields that store numeric-looking strings ("16", "8") use LEXICOGRAPHIC comparison, so "8" > "10" is true. Use these operators only on fields whose values are stored as proper numbers, or filter then sort/compare client-side for text-stored numerics.

sortstring

Sort spec — rowOrder, createdAt, updatedAt, or ANY FIELD NAME/ID, optionally prefixed with "-" for descending (e.g. "-Total"). Defaults to rowOrder asc. Numbers sort numerically, text case-insensitively, blanks always last; scalar lookup/rollup and self-contained formulas sort server-side too. Link fields, volatile formulas and list aggregations are not sortable (clear 400). Unknown keys throw 400 — never silently ignored.

limitnumber

Max rows per page (default: 100, max: 1000)

offsetnumber

Skip results for pagination

includeReadableboolean

Include human-readable field-name keys in output (extra cost — set true only when you need it; default false)

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/filterRows \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "tableId": "example",
  "filter": {},
  "sort": "example"
}'
POST
/api/v1/tables/searchRows

Substring search across all field values in a table (postgres-side, no row cap). Params: tableId (required), query (required, search string), limit (default 50, max 500), offset, includeReadable (default true). Returns matching rows with optional human-readable field names (dataReadable). For typed field-level filters, use filterRows.

Parameters

tableIdstringrequired

Table ID to search in

querystringrequired

Search query string. Matches anywhere in the row's serialized data (field IDs and values).

limitnumber

Max results per page (default: 50, max: 500)

offsetnumber

Skip results for pagination

includeReadableboolean

Include human-readable field names in output

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/searchRows \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "tableId": "example",
  "query": "example",
  "limit": 0
}'
POST
/api/v1/tables/upsertRow

Create or update row based on match field. Params: tableId (required), matchFieldId (required, field ID to match on), matchValue (required), data (row data keyed by field ID; exact field names are also accepted), includeReadable (default true). Returns {id, action: "created"|"updated", data, dataReadable}.

Parameters

tableIdstringrequired

Table ID

matchFieldIdstringrequired

Field ID to match on (must be unique or will update first match)

matchValueany

Value to match against

dataobjectrequired

Row data with field IDs as keys

includeReadableboolean

Include human-readable output

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/upsertRow \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "tableId": "example",
  "matchFieldId": "example",
  "matchValue": {}
}'
POST
/api/v1/tables/getTableSchema

Get table structure/schema. Params: tableId (required), format ("json"|"array"|"markdown", default "json"). Returns field definitions useful for understanding table layout before creating/updating rows.

Parameters

tableIdstringrequired

Table ID

formatstring

Output format: json (full), array (simple), markdown (documentation)

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/getTableSchema \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "tableId": "example",
  "format": "example"
}'
POST
/api/v1/tables/duplicateTable

Clone a table with structure and optionally data. Params: tableId (source), newName (required), targetDatabaseId (optional, defaults to same database), includeRows (default false), includeViews (default true). Returns new table info. Copying tables or rows into another base is work beyond most requests: only when the person asked for the copy.

Parameters

tableIdstringrequired

Source table ID to duplicate

newNamestringrequired

Name for the new table

targetDatabaseIdstring

Target database ID (defaults to same database)

includeRowsboolean

Copy all rows from source table

includeViewsboolean

Copy views configuration

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/duplicateTable \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "tableId": "example",
  "newName": "example",
  "targetDatabaseId": "example"
}'
POST
/api/v1/tables/getHelp

Get focused help for Tables API. Call when a parameter shape is unclear (one call per topic). Topics: - "overview": Structure, quick start, key concepts - "fields": Field types and how to create them - "rows": Creating/updating rows (uses field IDs!) - "views": View types and creation - "linked": Linking tables together Example: getHelp({topic: "rows"}) before creating rows.

Parameters

topicstring

Topic: "overview", "fields", "rows", "views", "linked", "buttons"

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/getHelp \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "topic": "example"
}'
POST
/api/v1/tables/setAccessRules

Set access rules for a table in published apps. Controls who can create/read/update/delete rows. Params: - tableId: Table ID (required) - accessRules: Object with rules per operation (create, read, update, delete) - trackCreator: boolean (default true) - Track who creates each row Each operation's value is a CONDITION: a single rule, a flat array (OR), or a boolean group. This lets you express ANY combination. Rule types (leaves): - public: Anyone (anonymous users). Always say its level: on a table it has one meaning today, written { "type": "public", "scope": "everywhere" } (on a file or flow the default is "scope": "home" with its sites: see files_setAccessRules / flow_setAccessRules). Two newer levels: "Only through this site" ({ "type": "only_through_site" }: anyone, but only from this site's own pages) and "Anyone on the internet" ({ "type": "anyone_on_internet" }: anyone, anywhere). They can be saved only once Serenities switches them on (until then a save naming one is refused and says so); from then on a public leaf that says no level is refused naming both. When a person asks for "public", ask which one they mean; never pick for them: suggest "Only through this site", and write "Anyone on the internet" only when they ask for exactly that. - authenticated: Any logged-in app user - creator: Only the user who created the row - field_match: { type: "field_match", rowField, userField, op? } - row.data[rowField] OP user.profileData[userField] (op default eq). Compared by the FIELD's type: a number field compares numbers (the profile's "5" is 5; a row holding the text "5" is not), a text field compares text, a multiselect or link field is membership. A profile value that can't be the field's type, ordering (gt/lt…) on a non-number field, or a field the table doesn't have matches no row. - user_property: { type: "user_property", field, value?, op? } - user.profileData[field] OP value - has_role: { type: "has_role", roleId: "<uuid>" } - matches users assigned this AppBuilderRole. Get role IDs from listProjectRoles. Always send roleId: a { role: "name" } is saved with that role's id beside it (so renaming the role keeps the rule), and is refused once name rules are switched off; "user" / "admin" (the built-in classification) stay names. - email: { type: "email", email: "person@example.com" } - one specific user - has_entitlement: { type: "has_entitlement", key } - members holding that paid entitlement - function: { type: "function", functionId: "<id>" } - access decided by a backend function returning true/false (NOT allowed on pages). It must be a switched-on CODE function of this site: one that is off, a data function, or fails to run can't decide: it never lets anyone in, and a rule that needs its answer (all / not / deny) refuses. It may only read, with the visitor's rights: ctx.tables reads see exactly what the table's read and field rules give that visitor, ctx.files.list/getUrl only the files that visitor may read, plus ctx.settings.get; a function rule inside it can't decide (no gate runs inside a gate) — no writes, email, posting, agents, events or AI, and no network but the bridge (no outside sites); do that work in the function or flow the rule protects - relationship: { type: "relationship", linkFieldId: "<id>", match: { via: "creator" } } - ReBAC: access via a RELATED record. Follow the link field linkFieldId on THIS table to the linked table; allow if a related row "belongs to" the user. match is one of: { via:"creator" } (related row's creator is the user), { via:"field_match", rowField, userField, op? } (related.data[rowField] OP user.profileData[userField]), or { via:"user_property", field, value?, op? }. Get link field IDs from getFieldsByTableId (fields where linkType/linkedTableId is set). Tables only — NOT allowed on pages (needs a DB join the edge can't do). Use for "vendor sees only orders for products they own", "member reads their team's projects". - policy: { "policy": "<policyId>" } - reference a reusable AccessPolicy (see listAccessPolicies/createAccessPolicy); expanded at eval. Missing/cyclic ⇒ deny. Operators (op) for user_property/field_match: eq, ne, gt, gte, lt, lte, in, nin, contains, isEmpty, isNotEmpty. Use arrays for in/nin: { type:"user_property", field:"tier", op:"in", value:["pro","enterprise"] }. Groups (combine conditions): - { "any": [condA, condB] } → OR (a flat array is shorthand for this) - { "all": [condA, condB] } → AND (every condition must pass) - { "not": cond } → NOT - { "deny": cond } → DENY-WINS: if cond matches, the WHOLE check fails, overriding every allow (e.g. "everyone paid EXCEPT banned"). Use { all: [ <allows>, { deny: { type:"user_property", field:"banned", value:true } } ] }. Groups nest arbitrarily: { all: [ { any: [tierPro, tierEnt] }, { type:"has_role", roleId } ] }. IMPORTANT: App owner always has full access. No rules / empty = deny (secure by default). A read rule saved empty ([] — "only the owner reads") also gives site functions' ctx.tables 0 rows for members and visitors. With NO read rule, a site function's ctx.tables read for a member or visitor is refused (only the owner reads); set a "creator" read rule so members read the rows they created. Field rules (accessRules.fields[<fieldId>] = { read?, write? }): same conditions. A rule that compares a record (creator, field_match, relationship) is judged on EACH row — "only the creator sees Notes" hides Notes on others' rows; a write sets the field only where the rule passes on the row the write leaves (link/unlink of a link field too). has_entitlement, function and policy work there as well. Example - Contact form (anyone submits, only owner reads): { "tableId": "xxx", "accessRules": { "create": [{"type":"public","scope":"everywhere"}], "read": [], "update": [], "delete": [] } } Example - Personal data (users see only their own): { "tableId": "xxx", "accessRules": { "create": [{"type":"authenticated"}], "read": [{"type":"creator"}], "update": [{"type":"creator"}], "delete": [{"type":"creator"}] } } Example - "(Pro OR Enterprise) AND age >= 18" can read: { "tableId": "xxx", "accessRules": { "read": { "all": [ { "any": [ {"type":"user_property","field":"tier","value":"pro"}, {"type":"user_property","field":"tier","value":"enterprise"} ] }, {"type":"user_property","field":"age","op":"gte","value":18} ] } } } Example - Vendor sees only Orders for Products they own (ReBAC, "product" = a link field on Orders → Products): { "tableId": "<ordersTableId>", "accessRules": { "read": {"type":"relationship","linkFieldId":"<productLinkFieldId>","match":{"via":"creator"}} } } Example - Paying members EXCEPT banned users (deny-wins): { "tableId": "xxx", "accessRules": { "read": { "all": [ {"type":"user_property","field":"tier","value":"pro"}, { "deny": {"type":"user_property","field":"banned","value":true} } ] } } }

Parameters

tableIdstringrequired

Table ID from listTables

accessRulesobjectrequired

Access rules per operation (create/read/update/delete). Each is a Condition: a single rule, a legacy flat array (OR), or an { all | any | not } group. Rule types: public, authenticated, creator, field_match, user_property (with op: eq/ne/gt/gte/lt/lte/in/nin/contains/isEmpty/isNotEmpty), has_role, email, function. AND/OR/NOT groups + operators enable any combination.

trackCreatorboolean

Whether to track who creates each row. OMIT to keep the table's current setting — it used to default to true, so a call that only changed a read rule silently re-enabled creator tracking.

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/setAccessRules \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "tableId": "example",
  "accessRules": {},
  "trackCreator": true
}'
POST
/api/v1/tables/getAccessRules

Get current access rules for a table. Returns rules, trackCreator setting, and help with presets. Params: tableId (required) Returns: - tableId, tableName - accessRules: Current rules per operation - trackCreator: Whether row creators are tracked - help: Rule type explanations and preset examples

Parameters

tableIdstringrequired

Table ID from listTables

Example Request

curl -X POST https://app.serenitiesai.com/api/v1/tables/getAccessRules \
  -H "Authorization: Bearer mk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
  "tableId": "example"
}'