Skip to content

Tables

Tables API

Custom data tables allow you to store and manage structured data that agents can query and manipulate.

Roles: Read operations (list/get tables, list rows) require the Chat role or above. All write operations (create/update/delete tables, insert/update/delete rows, column changes) require Admin or Owner.

Table Management

List Tables

GET /orgs/{organizationId}/tables

Query Parameters:

  • limit (optional): Number of results (default: 20)
  • offset (optional): Pagination offset (default: 0)

Response:

{
  "items": [
    {
      "name": "customers",
      "organizationId": "org-123",
      "description": "Customer information",
      "fields": [
        {
          "name": "name",
          "type": "text",
          "required": true,
          "comments": "Customer full name"
        },
        {
          "name": "email",
          "type": "text",
          "required": true,
          "comments": "Email address"
        },
        {
          "name": "is_active",
          "type": "boolean",
          "required": false,
          "default": true,
          "comments": "Account status"
        },
        {
          "name": "metadata",
          "type": "jsonb",
          "required": false,
          "comments": "Additional data"
        }
      ],
      "creatorUserId": "user-456",
      "createdAt": "2024-01-01T00:00:00Z",
      "updatedAt": "2024-01-01T00:00:00Z"
    }
  ],
  "totalRows": 1,
  "offset": 0
}

Create Table

POST /orgs/{organizationId}/tables

Required Role: Admin or Owner

Required Fields:

  • name: Table name (must be unique). Only letters, numbers, and underscores are allowed.
  • fields: Array of field schemas (at least one).

Optional Fields:

  • description: Table description
Every table has an auto-generated integer primary key named id. id is a reserved column name — do not define a field called id (the request is rejected) and do not supply id in row data.

Request Body:

{
  "name": "products",
  "description": "Product catalog",
  "fields": [
    {
      "name": "name",
      "type": "text",
      "required": true,
      "comments": "Product name"
    },
    {
      "name": "price",
      "type": "numeric",
      "required": false,
      "default": 0,
      "comments": "Price in USD"
    },
    {
      "name": "in_stock",
      "type": "boolean",
      "required": false,
      "default": true,
      "comments": "Availability status"
    },
    {
      "name": "metadata",
      "type": "jsonb",
      "required": false,
      "comments": "Product metadata"
    },
    {
      "name": "created_at",
      "type": "timestamp",
      "required": false,
      "comments": "Creation timestamp"
    }
  ]
}

Field Types:

  • text - Text/string data
  • numeric - Numbers (integers and decimals)
  • boolean - True/false values
  • timestamp - Date and time values
  • jsonb - JSON objects (stored as PostgreSQL JSONB)

Field Schema:

  • name (required): Field name — only letters, numbers, and underscores are allowed
  • type (required): Field type
  • required (required): Whether field is required (boolean)
  • default (optional): Default value if not provided
  • comments (required): Description/comment for the field

Response: Returns created CustomTable object

Get Table

GET /orgs/{organizationId}/tables/{tableName}

Returns table metadata and field schemas.

Response: Returns CustomTable object

Delete Table

DELETE /orgs/{organizationId}/tables/{tableName}

Required Role: Admin or Owner

Permanently deletes the table and all its data.

Response:

{
  "organizationId": "org-123",
  "tableName": "products"
}
There is no endpoint to add, remove, or re-type fields after creation — create a new table if you need a different schema. You can rename a column and link/unlink columns to the Data Dictionary (see Column Operations).

Table Data Operations

List Rows

GET /orgs/{organizationId}/tables/{tableName}/data

Query Parameters:

  • limit (optional): Number of rows (default: 20)
  • offset (optional): Pagination offset (default: 0)
  • orderBy (optional): Sort specification (format: field1:asc,field2:desc)
  • filters (optional): Filter specification. Each segment is field:operator:value:join — the trailing join (and/or) is required on every segment, including a single filter. Segments are separated by ||| (recommended) or , (legacy). Prefer ||| because a comma separator collides with comma-separated in/nin values.

Example with Filters and Sorting:

GET /orgs/org-123/tables/products/data?limit=10&offset=0&orderBy=price:desc&filters=in_stock:eq:true:and|||price:gte:100:and

Filter Operators:

  • eq - Equal to
  • ne - Not equal to
  • gt - Greater than
  • lt - Less than
  • gte - Greater than or equal to
  • lte - Less than or equal to
  • like - Case-sensitive pattern matching
  • ilike - Case-insensitive pattern matching
  • in - In list (comma-separated values)
  • nin - Not in list
For like/ilike, the value is wrapped in %…% automatically (spaces become %), so pass the raw substring — do not add your own % wildcards.

Filter Join (applied between this segment and the previous one, left-to-right with no bracketing; the join on the first segment is ignored):

  • and - AND with the preceding condition
  • or - OR with the preceding condition

Response:

{
  "items": [
    {
      "id": 1,
      "name": "Premium Widget",
      "price": 299.99,
      "in_stock": true,
      "metadata": {
        "category": "electronics",
        "tags": ["featured", "premium"]
      },
      "created_at": "2024-01-01T00:00:00Z"
    }
  ],
  "totalRows": 45,
  "offset": 0
}

Create Row

POST /orgs/{organizationId}/tables/{tableName}/data

Required Role: Admin or Owner

Request Body:

{
  "data": {
    "name": "New Product",
    "price": 49.99,
    "in_stock": true,
    "metadata": {
      "category": "accessories"
    }
  }
}

Do not include id — it is auto-generated and rejected if supplied. Any key that is not a defined field is rejected with a 400.

Response: Returns the created row (including its generated id)

Update Row

PUT /orgs/{organizationId}/tables/{tableName}/data/{id}

Required Role: Admin or Owner

The {id} parameter is the numeric row ID (auto-generated integer).

Request Body:

{
  "data": {
    "price": 39.99,
    "in_stock": false
  }
}

Only include fields you want to update. Other fields remain unchanged.

Response: Returns the updated row

Delete Row

DELETE /orgs/{organizationId}/tables/{tableName}/data/{id}

Required Role: Admin or Owner

Permanently deletes the row.

Response:

{
  "organizationId": "org-123",
  "tableName": "products",
  "id": 101
}

Bulk Operations

Batch Insert

POST /orgs/{organizationId}/tables/{tableName}/data/batch

Required Role: Admin or Owner

Insert multiple rows in a single request.

Request Body:

{
  "data": [
    {
      "name": "Product 1",
      "price": 10.00,
      "in_stock": true
    },
    {
      "name": "Product 2",
      "price": 20.00,
      "in_stock": true
    },
    {
      "name": "Product 3",
      "price": 30.00,
      "in_stock": false
    }
  ]
}

Rows are validated individually; invalid rows are skipped and reported in errors, while valid rows are still inserted. ids contains the generated integer IDs of the inserted rows.

Response:

{
  "inserted": 3,
  "failed": 0,
  "errors": [],
  "ids": [1, 2, 3]
}

If validation fails for some rows:

{
  "inserted": 2,
  "failed": 1,
  "errors": [
    {
      "index": 1,
      "error": "Invalid record"
    }
  ],
  "ids": [1, 3]
}

Column Operations

Rename Column

PUT /orgs/{organizationId}/tables/{tableName}/columns/{columnName}

Required Role: Admin or Owner

Request Body:

{
  "newName": "unit_price"
}

newName must be alphanumeric/underscore only and must not be a reserved name (id).

Response: Returns the updated CustomTable object.

Link Column to the Data Dictionary

POST /orgs/{organizationId}/tables/{tableName}/columns/{columnName}/dd-link

Required Role: Admin or Owner

Links (or re-links) a column to a Data Dictionary field. The column’s physical name must equal ddFieldName (rename the column first if it differs).

Request Body:

{
  "ddFieldName": "sale_price"
}

ddFieldName must be lowercase letters, numbers, and underscores.

Response: Returns the updated CustomTable object.

Unlink Column from the Data Dictionary

DELETE /orgs/{organizationId}/tables/{tableName}/columns/{columnName}/dd-link

Required Role: Admin or Owner

Strips the Data Dictionary link while preserving the column and its data. Idempotent — unlinking a column that is not linked returns the table unchanged.

Response: Returns the updated CustomTable object.

Using Tables with Agents

Agents can query and manipulate tables using the database tool.

Example Agent Configuration:

{
  "name": "Inventory Assistant",
  "tools": [
    {
      "toolCode": "database",
      "configuration": {
        "tables": ["products", "inventory"]
      }
    }
  ]
}

The agent can then:

  • “Show me all products under $100”
  • “Update the price of product 123 to $79.99”
  • “Add a new product called ‘Widget Pro’ with price $150”
  • “How many products are currently in stock?”

Filter Examples

Every segment carries a trailing join (and/or), even a single filter.

Simple Equality

filters=in_stock:eq:true:and

Multiple Conditions (AND)

filters=in_stock:eq:true:and|||price:gte:100:and

Multiple Conditions (OR)

filters=category:eq:electronics:and|||category:eq:accessories:or

The join on the second segment (or) is what combines the two conditions.

Pattern Matching

filters=name:ilike:widget:and

Matches any product name containing “widget” (case-insensitive). The value is wrapped in %…% automatically.

Numeric Ranges

filters=price:gte:50:and|||price:lte:200:and

Matches prices between $50 and $200

Complex Example

filters=in_stock:eq:true:and|||price:gte:100:and|||category:in:electronics,accessories:and

Note the ||| separator between segments — an in value list uses commas internally, so the comma separator cannot be used here.

Sorting Examples

Single Field Ascending

orderBy=price:asc

Single Field Descending

orderBy=created_at:desc

Multiple Fields

orderBy=in_stock:desc,price:asc

First by in_stock (descending), then by price (ascending)

Best Practices

  1. Table Naming - Use lowercase snake_case for table names (e.g., customer_orders)
  2. Field Naming - Use lowercase snake_case for field names (e.g., created_at)
  3. Required Fields - Mark critical fields as required
  4. Default Values - Provide sensible defaults when possible
  5. Comments - Always provide descriptive comments for fields
  6. JSONB for Flexibility - Use jsonb type for dynamic/nested data
  7. Batch Operations - Use batch insert for importing large datasets
  8. Indexing - Tables are automatically indexed on the auto-generated id
  9. Data Validation - All data is validated against field schemas before insert

Limitations

  • No Field Changes: Cannot add, remove, or re-type fields after creation (you can rename a column and link/unlink DD fields)
  • Write Operations Are Admin/Owner Only: Reads are available to the Chat role
  • Row ID: Each row has an auto-generated numeric id (reserved, not customizable, never supplied on write)
  • No Joins: Each table is independent (no foreign key relationships)
  • No Computed Fields: All fields must be explicitly provided

Common Issues

Issue Solution
Table creation fails Check table name is unique and lowercase
Insert fails validation Verify all required fields are provided
Filter not working Check field name spelling and operator syntax
Cannot change fields Field set is fixed - rename columns or create a new table
Permission denied Ensure user has Admin or Owner role
Row not found Verify row ID exists in table

Data Types Details

text

Stores string data. No length limit.

numeric

Stores numbers. Can be integers or decimals.

{ "price": 99.99 }
{ "quantity": 5 }

boolean

Stores true or false.

{ "is_active": true }

timestamp

Stores ISO 8601 formatted timestamps.

{ "created_at": "2024-01-01T12:00:00Z" }

jsonb

Stores JSON objects. Supports nested structures.

{
  "metadata": {
    "tags": ["featured", "sale"],
    "attributes": {
      "color": "blue",
      "size": "large"
    }
  }
}