Tables API
Tables provide structured data storage for agents and users. Each table has typed columns, queryable rows with filtering and sorting, import/export support, automation triggers, chart visualizations, and full change history with rollback.
Quick Start
Create a table, add columns, insert rows, and query with filters — a typical integration flow:
// 1. Create a table with initial columns
const table: { id: string; name: string } = await fetch('/api/tables', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'Customer Feedback',
projectId: 'proj-8f3a-4b2c',
columns: [
{ name: 'customer', displayName: 'Customer', dataType: 'text', position: 0 },
{ name: 'rating', displayName: 'Rating', dataType: 'number', position: 1 },
{ name: 'feedback', displayName: 'Feedback', dataType: 'text', position: 2 },
],
}),
}).then((r) => r.json());
// → { id: "tbl-6c1d-9e7f", name: "Customer Feedback", ... }
// 2. Add a status column after creation
await fetch(`/api/tables/${table.id}/columns`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'status',
displayName: 'Status',
dataType: 'select',
config: { options: ['open', 'in_progress', 'closed'] },
}),
});
// 3. Insert rows in batch (up to 1000 per request)
await fetch(`/api/tables/${table.id}/rows`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
rows: [
{ data: { customer: 'Acme Corp', rating: 5, feedback: 'Excellent', status: 'closed' } },
{ data: { customer: 'Globex Inc', rating: 3, feedback: 'Average', status: 'open' } },
{ data: { customer: 'Initech', rating: 4, feedback: 'Good support', status: 'closed' } },
],
}),
});
// 4. Query with filters — find high-rated feedback
const filters: string = JSON.stringify([{ column: 'rating', operator: 'gte', value: 4 }]);
const rows: Array<{ data: Record<string, unknown> }> = await fetch(
`/api/tables/${table.id}/rows?filters=${encodeURIComponent(filters)}`
).then((r) => r.json());
console.log(`${rows.length} rows with rating >= 4`); Base URL
/api/tables
Endpoints
List Tables
/api/tables Retrieve all tables with optional filtering by project, source, or search term.
Query Parameters
| Param | Type | Req | Description |
|---|---|---|---|
| projectId | string | — | Filter by project ID |
| source | enum | — | manual, imported, agent, or template |
| search | string | — | Search by table name |
List all tables for a project — useful for building a data browser or selecting a table to query:
// List all tables in a project
const response: Response = await fetch('/api/tables?projectId=proj-8f3a-4b2c');
const tables: Array<{ id: string; name: string; source: string }> = await response.json();
tables.forEach((t) => console.log(`${t.name} (${t.source}) — ${t.id}`)); Example response:
[
{
"id": "tbl-6c1d-9e7f",
"name": "Customer Feedback",
"description": null,
"projectId": "proj-8f3a-4b2c",
"source": "manual",
"rowCount": 42,
"createdAt": "2026-04-03T10:00:00.000Z",
"updatedAt": "2026-04-03T11:30:00.000Z"
}
] Create Table
/api/tables Create a new table with optional column definitions. If a templateId is provided, the table is cloned from that template instead.
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| name | string | * | Table name (1-256 characters) |
| description | string | — | Table description (max 1024 characters) |
| projectId | string (UUID) | — | Link to a project |
| columns | ColumnDef[] | — | Initial column definitions (see Column Schema below) |
| source | enum | — | manual, imported, agent, or template |
| templateId | string (UUID) | — | Clone from an existing template |
Response 201 Created — New table object
Errors: 400 — Zod validation failure
Create a table with typed columns — columns define the schema that rows must follow:
// Create a table with typed columns
const response: Response = await fetch('/api/tables', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'Customer Feedback',
projectId: 'proj-8f3a-4b2c',
columns: [
{ name: 'customer', displayName: 'Customer', dataType: 'text', position: 0 },
{ name: 'rating', displayName: 'Rating', dataType: 'number', position: 1 },
{ name: 'feedback', displayName: 'Feedback', dataType: 'text', position: 2 },
],
}),
});
const table: { id: string } = await response.json();
console.log(table.id); // "tbl-6c1d-9e7f" Example response:
{
"id": "tbl-6c1d-9e7f",
"name": "Customer Feedback",
"projectId": "proj-8f3a-4b2c",
"source": "manual",
"columns": [
{ "id": "col-a1b2", "name": "customer", "displayName": "Customer", "dataType": "text", "position": 0 },
{ "id": "col-c3d4", "name": "rating", "displayName": "Rating", "dataType": "number", "position": 1 },
{ "id": "col-e5f6", "name": "feedback", "displayName": "Feedback", "dataType": "text", "position": 2 }
],
"createdAt": "2026-04-03T10:00:00.000Z"
} Clone from Template
/api/tables Create a table by cloning a template. Provide templateId in the request body to use this path.
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| templateId | string (UUID) | * | Template to clone |
| name | string | * | Name for the new table (1-256 characters) |
| projectId | string (UUID) | — | Link to a project |
| includeSampleData | boolean | — | Include sample rows from the template |
Clone a template to get a pre-configured table with columns and optional sample data:
// Clone a template with sample data included
const response: Response = await fetch('/api/tables', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
templateId: 'tpl-4a2b-8c9d',
name: 'Q3 Budget Tracker',
projectId: 'proj-8f3a-4b2c',
includeSampleData: true,
}),
});
const table: { id: string; name: string } = await response.json(); Get Table
/api/tables/{id} Retrieve a single table by ID including its column definitions.
Response 200 — Table object with columns array
// Fetch table metadata and column definitions
const table: { name: string; columns: Array<{ displayName: string; dataType: string }> } =
await fetch('/api/tables/tbl-6c1d-9e7f').then((r) => r.json());
console.log(`${table.name} — ${table.columns.length} columns`);
table.columns.forEach((c) => console.log(` ${c.displayName} (${c.dataType})`)); Errors: 404 — { "error": "Table not found" }
Update Table
/api/tables/{id} Update table name, description, or project association.
Request Body (all fields optional)
| Field | Type | Req | Description |
|---|---|---|---|
| name | string | — | Updated name (1-256 chars) |
| description | string | — | Updated description (max 1024 chars) |
| projectId | string (UUID) | null | — | Reassign or unlink project |
Response 200 — Updated table object
Errors: 400 — Validation failure, 404 — Not found
// Update table name
await fetch('/api/tables/tbl-6c1d-9e7f', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Customer Feedback v2' }),
}); Delete Table
/api/tables/{id} Permanently delete a table and all its rows, columns, triggers, charts, and history.
Response 204 No Content
Errors: 404 — Not found
// Delete a table and all associated data
await fetch('/api/tables/tbl-6c1d-9e7f', { method: 'DELETE' }); Rows
List Rows
/api/tables/{id}/rows Query rows with filtering, sorting, and pagination. Filters and sorts are passed as JSON-encoded query parameters.
Query Parameters
| Param | Type | Req | Description |
|---|---|---|---|
| limit | number | — | Max rows to return (1-1000) |
| offset | number | — | Number of rows to skip |
| filters | JSON string | — | Array of FilterSpec objects (see Filtering below) |
| sorts | JSON string | — | Array of SortSpec objects (see Sorting below) |
Query rows with filters and pagination — useful for building data views or feeding data to agents:
// Query with filters and sorting
const filters: string = JSON.stringify([
{ column: 'rating', operator: 'gte', value: 4 },
{ column: 'status', operator: 'eq', value: 'open' },
]);
const sorts: string = JSON.stringify([
{ column: 'rating', direction: 'desc' },
]);
const rows: Array<{ data: { customer: string; rating: number; feedback: string } }> = await fetch(
`/api/tables/tbl-6c1d-9e7f/rows?limit=50&filters=${encodeURIComponent(filters)}&sorts=${encodeURIComponent(sorts)}`
).then((r) => r.json());
rows.forEach((row) => {
console.log(`${row.data.customer}: ${row.data.rating}/5 — ${row.data.feedback}`);
}); Example response:
[
{
"id": "row-a1b2-c3d4",
"tableId": "tbl-6c1d-9e7f",
"data": {
"customer": "Acme Corp",
"rating": 5,
"feedback": "Excellent support and fast resolution",
"status": "open"
},
"createdBy": "claude-code",
"createdAt": "2026-04-03T10:30:00.000Z",
"updatedAt": "2026-04-03T10:30:00.000Z"
}
] Add Rows
/api/tables/{id}/rows Insert one or more rows into a table. Accepts a batch of up to 1000 rows per request.
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| rows | Array | * | Array of row objects (1-1000 items) |
| rows[].data | Record<string, unknown> | * | Column name to value mapping |
| rows[].createdBy | string | — | Creator identifier (e.g. agent name) |
Response 201 Created — Array of created rows
Insert a batch of rows — agents often use this to populate tables with structured results:
// Batch insert rows (up to 1000 per request)
const result: Array<{ id: string }> = await fetch('/api/tables/tbl-6c1d-9e7f/rows', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
rows: [
{ data: { customer: 'Acme Corp', rating: 5, feedback: 'Excellent' }, createdBy: 'claude-code' },
{ data: { customer: 'Globex Inc', rating: 3, feedback: 'Average' }, createdBy: 'claude-code' },
],
}),
}).then((r) => r.json());
console.log(`Inserted ${result.length} rows`); Errors: 400 — Validation failure, 404 — Table not found
Update Row
/api/tables/{id}/rows/{rowId} Update the data for a single row. The entire data object is replaced.
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| data | Record<string, unknown> | * | Updated column name to value mapping |
Response 200 — Updated row
Replace a row’s data — note that the entire data object is replaced, not merged:
// Update a row — full data replacement
await fetch('/api/tables/tbl-6c1d-9e7f/rows/row-a1b2-c3d4', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
data: { customer: 'Acme Corp', rating: 4, feedback: 'Good, improved response time', status: 'closed' },
}),
}); Errors: 400 — Validation failure, 404 — Row not found
Delete Row
/api/tables/{id}/rows/{rowId} Permanently delete a single row.
Response 204 No Content
// Delete a single row
await fetch('/api/tables/tbl-6c1d-9e7f/rows/row-a1b2-c3d4', { method: 'DELETE' }); Columns
Add Column
/api/tables/{id}/columns Add a new column to an existing table.
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| name | string | * | Column key (1-64 chars, unique per table) |
| displayName | string | * | Display label (1-128 chars) |
| dataType | enum | * | text, number, boolean, date, select, url, email, computed, or relation |
| required | boolean | — | Whether the column is required |
| defaultValue | string | null | — | Default value for new rows |
| config | ColumnConfig | — | Type-specific config (see Column Config below) |
Response 201 Created — New column object
Add a select column with predefined options — great for status tracking:
// Add a select column with options
const column: { id: string; name: string } = await fetch('/api/tables/tbl-6c1d-9e7f/columns', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'status',
displayName: 'Status',
dataType: 'select',
config: { options: ['open', 'in_progress', 'closed'] },
}),
}).then((r) => r.json()); Example response:
{
"id": "col-g7h8",
"tableId": "tbl-6c1d-9e7f",
"name": "status",
"displayName": "Status",
"dataType": "select",
"position": 3,
"config": { "options": ["open", "in_progress", "closed"] },
"createdAt": "2026-04-03T10:05:00.000Z"
} Reorder Columns
/api/tables/{id}/columns Reorder all columns by providing column IDs in the desired order.
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| columnIds | string[] | * | Column IDs in the desired display order |
Response 200 — Reordered columns array
// Move status column to the front
await fetch('/api/tables/tbl-6c1d-9e7f/columns', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
columnIds: ['col-g7h8', 'col-a1b2', 'col-c3d4', 'col-e5f6'],
}),
}); Import & Export
Import Data
/api/tables/{id}/import Import structured data from a document into the table. Supports a preview mode to inspect inferred columns and sample rows before committing. Column types are auto-inferred from the first 100 rows.
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| documentId | string (UUID) | * | Source document to import from |
| preview | boolean | — | Set true to preview without importing |
| columnMapping | Array | — | Override inferred column names, types, or skip columns |
| columnMapping[].name | string | * | Column key |
| columnMapping[].displayName | string | * | Display label |
| columnMapping[].dataType | string | * | Column data type |
| columnMapping[].skip | boolean | — | Set true to skip this column |
Preview Response
| Field | Type | Req | Description |
|---|---|---|---|
| headers | string[] | * | Detected column headers |
| sampleRows | Array | * | First 10 rows of data |
| totalRows | number | * | Total row count in source |
| inferredColumns | ColumnDef[] | * | Auto-inferred column definitions |
Import Response
| Field | Type | Req | Description |
|---|---|---|---|
| importId | string | * | Audit record ID |
| rowsImported | number | * | Successfully imported rows |
| rowsSkipped | number | * | Skipped rows (validation errors) |
| errors | Array | * | First 20 error details |
| columns | ColumnDef[] | * | Final column definitions used |
Preview an import before committing — inspect inferred column types and sample data, then execute:
// Preview first, then import
const preview: { totalRows: number; headers: string[] } = await fetch(
'/api/tables/tbl-6c1d-9e7f/import',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ documentId: 'doc-7b2e-3f9a', preview: true }),
}
).then((r) => r.json());
console.log(`${preview.totalRows} rows, ${preview.headers.length} columns detected`);
// Execute the import
const result: { rowsImported: number; rowsSkipped: number } = await fetch(
'/api/tables/tbl-6c1d-9e7f/import',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ documentId: 'doc-7b2e-3f9a' }),
}
).then((r) => r.json());
console.log(`Imported ${result.rowsImported}, skipped ${result.rowsSkipped}`); Example preview response:
{
"headers": ["customer", "rating", "feedback"],
"sampleRows": [
{ "customer": "Acme Corp", "rating": "5", "feedback": "Excellent" }
],
"totalRows": 150,
"inferredColumns": [
{ "name": "customer", "displayName": "customer", "dataType": "text" },
{ "name": "rating", "displayName": "rating", "dataType": "number" },
{ "name": "feedback", "displayName": "feedback", "dataType": "text" }
]
} Export Table
/api/tables/{id}/export Download the entire table as CSV, JSON, or XLSX. Returns up to 10,000 rows.
Query Parameters
| Param | Type | Req | Description |
|---|---|---|---|
| format | enum | — | csv (default), json, or xlsx |
Response 200 — File download with appropriate Content-Type and Content-Disposition headers
Export a table for external use — CSV for spreadsheets, JSON for programmatic access, XLSX for stakeholders:
// Download as CSV and trigger browser save
const res: Response = await fetch('/api/tables/tbl-6c1d-9e7f/export?format=csv');
const blob: Blob = await res.blob();
const url: string = URL.createObjectURL(blob);
const link: HTMLAnchorElement = document.createElement('a');
link.href = url;
link.download = 'customer-feedback.csv';
link.click(); Triggers
List Triggers
/api/tables/{id}/triggers List all automation triggers configured for a table.
Response Body (Array)
| Field | Type | Req | Description |
|---|---|---|---|
| id | string (UUID) | * | Trigger identifier |
| tableId | string (UUID) | * | Parent table ID |
| name | string | * | Trigger name |
| triggerEvent | enum | * | row_added, row_updated, or row_deleted |
| condition | JSON | null | — | Optional condition expression |
| actionType | enum | * | run_workflow or create_task |
| actionConfig | JSON | * | Action-specific configuration |
| status | enum | * | active or paused |
| fireCount | number | * | Times this trigger has fired |
List triggers to see what automations are active on a table:
// Check which triggers are active
const triggers: Array<{ name: string; status: string; triggerEvent: string; actionType: string; fireCount: number }> =
await fetch('/api/tables/tbl-6c1d-9e7f/triggers').then((r) => r.json());
triggers.forEach((t) => {
console.log(`${t.name} [${t.status}] — ${t.triggerEvent} → ${t.actionType} (fired ${t.fireCount}x)`);
}); Example response:
[
{
"id": "trg-5e4d-3c2b",
"tableId": "tbl-6c1d-9e7f",
"name": "Auto-triage feedback",
"triggerEvent": "row_added",
"condition": null,
"actionType": "run_workflow",
"actionConfig": { "workflowId": "wf-2a3b-4c5d" },
"status": "active",
"fireCount": 28
}
] Create Trigger
/api/tables/{id}/triggers Create a new automation trigger that fires when rows are added, updated, or deleted.
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| name | string | * | Trigger name |
| triggerEvent | enum | * | row_added, row_updated, or row_deleted |
| condition | object | — | Condition that must be met for the trigger to fire |
| actionType | enum | * | run_workflow or create_task |
| actionConfig | object | * | Action configuration (e.g. workflowId, task template) |
Response 201 Created — New trigger object
Create a trigger that automatically runs a workflow when new feedback is added:
// Auto-run a triage workflow when new rows are added
const trigger: { id: string; name: string } = await fetch('/api/tables/tbl-6c1d-9e7f/triggers', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'Auto-triage feedback',
triggerEvent: 'row_added',
actionType: 'run_workflow',
actionConfig: { workflowId: 'wf-2a3b-4c5d' },
}),
}).then((r) => r.json()); Update Trigger
/api/tables/{id}/triggers/{triggerId} Update a trigger's name, event, condition, action, or status.
Request Body (all fields optional)
| Field | Type | Req | Description |
|---|---|---|---|
| name | string | — | Updated name |
| triggerEvent | enum | — | row_added, row_updated, or row_deleted |
| status | enum | — | active or paused |
| condition | object | null | — | Updated condition (null to remove) |
| actionType | enum | — | run_workflow or create_task |
| actionConfig | object | — | Updated action configuration |
Response 200 — Updated trigger object
Errors: 404 — Trigger not found
Pause a trigger temporarily during maintenance:
// Pause a trigger during maintenance
await fetch('/api/tables/tbl-6c1d-9e7f/triggers/trg-5e4d-3c2b', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status: 'paused' }),
}); Delete Trigger
/api/tables/{id}/triggers/{triggerId} Permanently delete a trigger.
Response 204 No Content
Errors: 404 — Trigger not found
// Delete a trigger
await fetch('/api/tables/tbl-6c1d-9e7f/triggers/trg-5e4d-3c2b', { method: 'DELETE' }); Charts
List Charts
/api/tables/{id}/charts List all chart visualizations saved for a table.
Response Body (Array)
| Field | Type | Req | Description |
|---|---|---|---|
| id | string (UUID) | * | Chart view ID |
| name | string | * | Chart title |
| config | object | * | Chart configuration (type, xColumn, yColumn, aggregation) |
| createdAt | ISO 8601 | * | Creation timestamp |
// List all charts for a table
const charts: Array<{ name: string; config: { type: string } }> =
await fetch('/api/tables/tbl-6c1d-9e7f/charts').then((r) => r.json());
charts.forEach((c) => console.log(`${c.name} (${c.config.type})`)); Create Chart
/api/tables/{id}/charts Save a new chart visualization for a table.
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| type | string | * | Chart type (e.g. bar, line, pie, scatter) |
| title | string | * | Chart display title |
| xColumn | string | * | Column name for X axis |
| yColumn | string | — | Column name for Y axis |
| aggregation | string | — | Aggregation function (e.g. sum, count, avg) |
Response 201 Created — { "id": "...", "name": "..." }
Create a bar chart showing the distribution of customer ratings:
// Create a ratings distribution chart
const chart: { id: string; name: string } = await fetch('/api/tables/tbl-6c1d-9e7f/charts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: 'bar',
title: 'Ratings Distribution',
xColumn: 'rating',
aggregation: 'count',
}),
}).then((r) => r.json()); Update Chart
/api/tables/{id}/charts/{chartId} Update a chart's title, type, axes, or aggregation. Config fields are merged with the existing configuration.
Request Body (all fields optional)
| Field | Type | Req | Description |
|---|---|---|---|
| title | string | — | Updated chart title |
| type | string | — | Updated chart type |
| xColumn | string | — | Updated X axis column |
| yColumn | string | — | Updated Y axis column |
| aggregation | string | — | Updated aggregation function |
Response 200 — Updated chart object with parsed config
Errors: 404 — Chart not found
// Switch to pie chart
await fetch('/api/tables/tbl-6c1d-9e7f/charts/chart-a1b2-c3d4', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type: 'pie', title: 'Ratings Breakdown' }),
}); Delete Chart
/api/tables/{id}/charts/{chartId} Permanently delete a chart visualization.
Response 204 No Content
Errors: 404 — Chart not found
// Delete a chart
await fetch('/api/tables/tbl-6c1d-9e7f/charts/chart-a1b2-c3d4', { method: 'DELETE' }); History
Table History
/api/tables/{id}/history Retrieve the change history for a table (row additions, updates, deletions).
Query Parameters
| Param | Type | Req | Description |
|---|---|---|---|
| limit | number | — | Max entries to return (default 100, max 500) |
// Fetch recent change history
const history: Array<{ action: string; rowId: string; createdAt: string }> =
await fetch('/api/tables/tbl-6c1d-9e7f/history?limit=50').then((r) => r.json());
history.forEach((entry) => {
console.log(`[${entry.action}] row ${entry.rowId} at ${entry.createdAt}`);
}); Row History
/api/tables/{id}/rows/{rowId}/history Retrieve the change history for a specific row, showing each version of its data.
Query Parameters
| Param | Type | Req | Description |
|---|---|---|---|
| limit | number | — | Max entries to return (default 50, max 200) |
// View how a row changed over time
const history: Array<{ version: number; data: { rating: number }; createdAt: string }> =
await fetch('/api/tables/tbl-6c1d-9e7f/rows/row-a1b2-c3d4/history').then((r) => r.json());
history.forEach((entry) => {
console.log(`v${entry.version}: rating=${entry.data.rating} at ${entry.createdAt}`);
}); Rollback Row
/api/tables/{id}/rows/{rowId}/history Rollback a row to a previous version by providing a history entry ID.
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| historyEntryId | string (UUID) | * | History entry to rollback to |
Response 200 — { "ok": true }
Rollback a row to a previous version — useful for undoing accidental edits:
// Rollback a row to a previous version
await fetch('/api/tables/tbl-6c1d-9e7f/rows/row-a1b2-c3d4/history', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ historyEntryId: 'hist-9f8e-7d6c' }),
}); Errors: 400 — Missing historyEntryId, 404 — History entry not found
Templates
List Templates
/api/tables/templates List available table templates, optionally filtered by category or scope.
Query Parameters
| Param | Type | Req | Description |
|---|---|---|---|
| category | enum | — | business, personal, pm, finance, or content |
| scope | enum | — | system or user |
// List project management templates
const { templates }: { templates: Array<{ name: string; scope: string; description: string }> } =
await fetch('/api/tables/templates?category=pm').then((r) => r.json());
templates.forEach((t) => console.log(`${t.name} [${t.scope}] — ${t.description}`)); Save as Template
/api/tables/templates Save an existing table as a reusable user-scoped template. Optionally includes the first 5 rows as sample data.
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| tableId | string (UUID) | * | Source table to save as template |
| name | string | * | Template name |
| description | string | — | Template description (defaults to table description) |
| category | enum | — | business, personal, pm, finance, or content |
| includeSampleData | boolean | — | Include first 5 rows as sample data |
Response 201 Created — { "id": "...", "name": "..." }
Save a well-designed table as a reusable template for your team:
// Save a table as a reusable template
const template: { id: string } = await fetch('/api/tables/templates', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tableId: 'tbl-6c1d-9e7f',
name: 'Sprint Planning Board',
category: 'pm',
includeSampleData: true,
}),
}).then((r) => r.json());
console.log(`Template saved: ${template.id}`); Enrichment
AI-powered enrichment fills a target column by running an agent workflow for each eligible row. Use the plan endpoint to preview the strategy before committing, the execute endpoint to kick off the workflow, and the runs endpoint to check recent results.
Preview Enrichment Plan
/api/tables/{id}/enrich/plan Preview the enrichment strategy the agent will use before executing. Returns the inferred plan including strategy type, agent profile, step list, target column contract, and eligible row count.
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| targetColumn | string | * | Column name to populate (1-128 chars) |
| promptMode | enum | — | auto (default) or custom |
| prompt | string | — | Custom prompt (required when promptMode is custom, max 8192 chars) |
| filter | FilterSpec | — | Restrict enrichment to a subset of rows |
| agentProfile | string | — | Override the default agent profile (1-128 chars) |
| batchSize | number | — | Rows to process per workflow step (max 200) |
Response Body
| Field | Type | Req | Description |
|---|---|---|---|
| promptMode | enum | * | auto or custom |
| strategy | enum | * | single-pass-lookup, single-pass-classify, or research-and-synthesize |
| agentProfile | string | * | Agent profile that will execute the enrichment |
| reasoning | string | * | Explanation of why this strategy was chosen |
| steps | Array | * | Ordered list of enrichment steps with id, name, purpose, and prompt |
| targetContract | object | * | Expected output shape: columnName, columnLabel, dataType, allowedOptions |
| eligibleRowCount | number | * | Rows that will be processed (already-populated rows excluded) |
| sampleBindings | Array | * | Sample row data used to build the plan |
Preview the enrichment plan before committing — inspect the chosen strategy and eligible row count, then pass the plan directly to the execute endpoint:
// Preview enrichment plan for a "sentiment" column
const plan = await fetch('/api/tables/tbl-6c1d-9e7f/enrich/plan', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
targetColumn: 'sentiment',
promptMode: 'auto',
filter: { column: 'status', operator: 'eq', value: 'open' },
}),
}).then((r) => r.json());
console.log(`Strategy: ${plan.strategy}`);
console.log(`Eligible rows: ${plan.eligibleRowCount}`);
console.log(`Steps: ${plan.steps.length}`); Example response:
{
"promptMode": "auto",
"strategy": "single-pass-classify",
"agentProfile": "data-analyst",
"reasoning": "Classifying feedback sentiment is a straightforward single-pass task with a fixed set of output labels.",
"steps": [
{
"id": "step-1",
"name": "Classify Sentiment",
"purpose": "Assign a sentiment label to each feedback entry",
"prompt": "Classify the following customer feedback as positive, neutral, or negative."
}
],
"targetContract": {
"columnName": "sentiment",
"columnLabel": "Sentiment",
"dataType": "select",
"allowedOptions": ["positive", "neutral", "negative"]
},
"eligibleRowCount": 38,
"sampleBindings": [
{ "customer": "Acme Corp", "feedback": "Excellent support and fast resolution" }
]
}Errors: 400 — Validation failure or unknown column, 404 — Table not found
Execute Enrichment
/api/tables/{id}/enrich Start an AI enrichment workflow that fills a target column for all eligible rows. Returns 202 immediately — the workflow runs fire-and-forget in the background. Already-populated rows are skipped automatically for idempotency. Pass a plan from the /enrich/plan endpoint to skip re-planning.
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| targetColumn | string | * | Column name to populate (1-128 chars) |
| promptMode | enum | — | auto (default) or custom |
| prompt | string | — | Custom enrichment prompt (required when promptMode is custom) |
| filter | FilterSpec | — | Restrict enrichment to a row subset |
| agentProfile | string | — | Override the agent profile (1-128 chars) |
| batchSize | number | — | Rows per workflow step (max 200, clamped automatically) |
| projectId | string | null | — | Associate the enrichment workflow with a project |
| workflowName | string | — | Custom name for the enrichment workflow (1-256 chars) |
| plan | object | — | Pre-built plan from /enrich/plan — skips the planning phase |
Response Body (202)
| Field | Type | Req | Description |
|---|---|---|---|
| workflowId | string | * | ID of the created enrichment workflow |
| rowsQueued | number | * | Number of rows that will be processed |
Execute enrichment with a pre-built plan — preview first, then pass the plan to avoid re-planning latency:
// 1. Get a plan first (reuse from preview step)
const plan = await fetch('/api/tables/tbl-6c1d-9e7f/enrich/plan', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ targetColumn: 'sentiment', promptMode: 'auto' }),
}).then((r) => r.json());
// 2. Execute using the pre-built plan
const result: { workflowId: string; rowsQueued: number } = await fetch(
'/api/tables/tbl-6c1d-9e7f/enrich',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
targetColumn: 'sentiment',
promptMode: 'auto',
plan,
}),
}
).then((r) => r.json());
console.log(`Workflow: ${result.workflowId}`);
console.log(`Rows queued: ${result.rowsQueued}`); Example response:
{
"workflowId": "wf-3b1a-5c2d",
"rowsQueued": 38
}Errors: 400 — Validation failure, unknown column, or unsupported data type, 404 — Table not found
List Enrichment Runs
/api/tables/{id}/enrich/runs List the most recent enrichment workflow runs for a table. Useful for showing enrichment history and status in the UI.
Query Parameters
| Param | Type | Req | Description |
|---|---|---|---|
| limit | number | — | Max runs to return (1-10, default 5) |
Fetch recent enrichment runs to show history alongside a table:
// Show recent enrichment runs for a table
const runs: Array<{ workflowId: string; status: string; rowsQueued: number; createdAt: string }> =
await fetch('/api/tables/tbl-6c1d-9e7f/enrich/runs?limit=5').then((r) => r.json());
runs.forEach((run) => {
console.log(`${run.workflowId} [${run.status}] — ${run.rowsQueued} rows — ${run.createdAt}`);
}); Example response:
[
{
"workflowId": "wf-3b1a-5c2d",
"status": "completed",
"targetColumn": "sentiment",
"rowsQueued": 38,
"rowsCompleted": 38,
"createdAt": "2026-04-03T11:00:00.000Z"
}
] Column Config Reference
| Config Field | Applies To | Description |
|---|---|---|
| options | select | Array of allowed option strings |
| formula | computed | Formula expression string |
| formulaType | computed | arithmetic, text_concat, date_diff, conditional, or aggregate |
| resultType | computed | Expected result data type |
| dependencies | computed | Column names the formula depends on |
| targetTableId | relation | ID of the related table |
| displayColumn | relation | Column name to display from the related table |
Filter Operators
| Operator | Description |
|---|---|
| eq | Equal to |
| neq | Not equal to |
| gt / gte | Greater than / greater than or equal |
| lt / lte | Less than / less than or equal |
| contains | String contains |
| starts_with | String starts with |
| in | Value is in array |
| is_empty | Value is null or empty |
| is_not_empty | Value is not null or empty |