GET /api/v1/schemas
List all schemas with their fields. Supports cursor-based pagination.
Query Parameters
| Parameter | Type | Default | Description |
|---|
limit | integer | 20 | Max results per page |
cursor | string | — | Cursor for pagination |
Response
{
"data": [
{
"id": "schema_001",
"tenantId": "tenant_001",
"name": "customers",
"displayName": "Customers",
"description": "Core customer entity",
"tableName": "ds_customers",
"entityType": "customer",
"schemaType": "customer",
"status": "active",
"linkedSchemaId": null,
"joinMapping": null,
"summaryColumns": null,
"autoEnrich": false,
"fields": [
{
"id": "field_001",
"schemaId": "schema_001",
"name": "email",
"displayName": "Email",
"dataType": "varchar",
"length": 255,
"precision": null,
"scale": null,
"isNullable": false,
"isPrimaryKey": false,
"isUnique": true,
"isPredictor": false,
"defaultValue": null,
"description": "",
"ordinal": 1
}
],
"createdAt": "2026-01-10T08:00:00.000Z",
"updatedAt": "2026-01-10T08:00:00.000Z"
}
],
"pagination": {
"total": 5,
"hasMore": false,
"limit": 50,
"cursor": null
}
}
POST /api/v1/schemas
Create a new schema. This creates both a metadata record and a real PostgreSQL table via DDL.
Creating a schema executes CREATE TABLE against the database. If the DDL fails, the metadata record is automatically rolled back.
Request Body
| Field | Type | Required | Description |
|---|
name | string | Yes | Schema name (auto-sanitized to lowercase alphanumeric + underscores) |
displayName | string | Yes | Human-readable name |
description | string | No | Description |
entityType | string | No | Entity type: "customer", "account", "custom". Default: "custom" |
schemaType | string | No | Schema type: "customer", "transaction", "event", etc. Default: "customer" |
linkedSchemaId | string | No | ID of a customer-type schema to link to (must be in the same tenant) |
joinMapping | object | No | Join configuration for linking to the parent schema |
summaryColumns | array | No | Column names to display in summary views. Default: [] |
autoEnrich | boolean | No | Whether to auto-enrich from this schema during decision flows. Default: false |
customPrimaryKey | object | No | Sugar for declaring a natural primary key column. Shape: { "name": "customer_id", "dataType": "varchar" }. When set, the auto id BIGSERIAL column is skipped and the named column is promoted to a fields[0] entry with isPrimaryKey: true. Equivalent to passing it explicitly in fields[] — pick whichever is cleaner. |
fields | array | No | Initial field definitions (see field object below) |
Field Object
| Field | Type | Default | Description |
|---|
name | string | — | Column name |
displayName | string | name | Display label |
dataType | string | "varchar" | PostgreSQL data type |
length | integer | null | Length for varchar/char types |
precision | integer | null | Precision for decimal types |
scale | integer | null | Scale for decimal types |
isNullable | boolean | true | Whether the column allows NULL |
isPrimaryKey | boolean | false | Primary key constraint |
isUnique | boolean | false | Unique constraint |
defaultValue | string | null | Default value expression |
description | string | "" | Field description |
Example
curl -X POST https://playground.kaireonai.com/api/v1/schemas \
-H "Content-Type: application/json" \
-H "X-Tenant-Id: my-tenant" \
-d '{
"name": "customers",
"displayName": "Customers",
"entityType": "customer",
"fields": [
{ "name": "email", "dataType": "varchar", "length": 255, "isUnique": true },
{ "name": "loan_amount", "dataType": "decimal", "precision": 12, "scale": 2 },
{ "name": "tenure_months", "dataType": "integer" }
]
}'
Example with customPrimaryKey sugar
curl -X POST https://playground.kaireonai.com/api/v1/schemas \
-H "Content-Type: application/json" \
-H "X-Tenant-Id: my-tenant" \
-d '{
"name": "accounts",
"displayName": "Accounts",
"entityType": "account",
"schemaType": "collection",
"customPrimaryKey": { "name": "account_id", "dataType": "varchar" }
}'
This is equivalent to passing fields: [{ "name": "account_id", "dataType": "varchar", "isPrimaryKey": true, "isNullable": false, "isUnique": true }] directly — pick whichever is more ergonomic for your use case. When set, the table is not given the default id BIGSERIAL auto-key.
Response: 201 Created
PUT /api/v1/schemas
Update an existing schema’s metadata. Only provided fields are changed. This does not modify the underlying PostgreSQL table structure — use the field endpoints for DDL changes.
Request Body
| Field | Type | Required | Description |
|---|
id | string | Yes | Schema ID to update |
displayName | string | No | Updated display name |
description | string | No | Updated description |
entityType | string | No | Updated entity type |
schemaType | string | No | Updated schema type |
linkedSchemaId | string | No | Updated linked schema ID (must reference a customer-type schema in the same tenant) |
joinMapping | object | No | Updated join configuration |
summaryColumns | array | No | Updated summary columns |
autoEnrich | boolean | No | Updated auto-enrich flag |
Response: 200 OK with the updated schema object including fields.
Error Codes
| Code | Reason |
|---|
400 | Missing id, or linkedSchemaId references an invalid schema |
404 | Schema not found |
DELETE /api/v1/schemas
Delete a schema and drop its backing PostgreSQL table.
Query Parameters
| Parameter | Type | Required | Description |
|---|
id | string | Yes | Schema ID to delete |
This drops the underlying database table. All data in the table is permanently lost.
Response: 204 No Content
GET /api/v1/schemas/fields
List the fields (columns) for a single schema. Used by the Schema Joins editor
and any other UI surface that needs a column list without pulling the full schema
object.
Query Parameters
| Parameter | Type | Required | Description |
|---|
schemaId | string | Yes | Schema ID whose fields you want |
Response: 200 OK with an array of field objects, sorted by ordinal.
[
{
"id": "...",
"schemaId": "...",
"name": "customer_id",
"displayName": "customer_id",
"dataType": "varchar",
"length": 64,
"isNullable": false,
"isPrimaryKey": true,
"ordinal": 1
}
]
Errors:
400 Bad Request — schemaId query parameter missing
404 Not Found — schema does not exist in caller’s tenant
POST /api/v1/schemas/fields
Add a column to an existing schema. Executes ALTER TABLE ADD COLUMN.
Request Body
| Field | Type | Required | Description |
|---|
schemaId | string | Yes | Parent schema ID |
name | string | Yes | Column name |
dataType | string | Yes | PostgreSQL data type |
displayName | string | No | Display label |
length | integer | No | Length for varchar/char |
precision | integer | No | Precision for decimal |
scale | integer | No | Scale for decimal |
isNullable | boolean | No | Default: true |
isPrimaryKey | boolean | No | Default: false |
isUnique | boolean | No | Default: false |
isPredictor | boolean | No | Mark column as ML predictor. Default: false |
defaultValue | string | No | Default value expression |
description | string | No | Field description |
upsert | boolean | No | When true, allows overwriting metadata on an existing field with the same name. Used by the CSV re-upload + predictor-toggle codepaths. Default: false (returns 409 on duplicate name). |
Response: 201 Created with the field object on success, 200 OK when upsert: true matched an existing row.
Duplicate-name handling
Without upsert: true, the endpoint returns 409 Conflict if a field with the
same name already exists on this schema:
{
"title": "Conflict",
"detail": "A field named \"first_name\" already exists on this schema. Use PUT to alter it, DELETE to remove it, or pass upsert=true to overwrite metadata."
}
This protects the metadata-vs-PG invariant — re-POSTing a field name without
opting in won’t silently overwrite length/nullable/predictor flags while the
real PG column stays unchanged.
PUT /api/v1/schemas/fields
Alter an existing column. Executes ALTER TABLE RENAME COLUMN and/or
ALTER COLUMN TYPE / SET NOT NULL / DROP NOT NULL against the live table.
Request Body
| Field | Type | Required | Description |
|---|
fieldId | string | Yes | The SchemaField.id to alter |
newName | string | No | New column name (snake_case). Triggers ALTER TABLE RENAME COLUMN when different from current. |
dataType | string | No | New PostgreSQL data type |
length | integer | No | New length |
precision | integer | No | New precision |
scale | integer | No | New scale |
isNullable | boolean | No | When provided, toggles SET NULL / DROP NULL |
displayName | string | No | Display label |
description | string | No | Field description |
Response: 200 OK with the updated field object.
Error Codes
| Status | Meaning |
|---|
| 400 | Invalid newName format, missing fieldId, or unsupported dataType |
| 404 | Field not found in this tenant |
| 409 | newName collides with another field on the same schema |
| 422 | Primary-key columns can’t be altered here (use the PrimaryKey panel), or the requested type cast would fail on existing data (e.g. varchar → integer with non-numeric rows) |
Type changes run a USING cast on existing data. Narrowing casts that lose precision return 422 — the operation is refused, not silently truncated.
DELETE /api/v1/schemas/fields
Remove a column from a schema. Executes ALTER TABLE DROP COLUMN.
Query Parameters
| Parameter | Type | Required | Description |
|---|
fieldId | string | Yes | Field ID to delete |
This drops the column from the underlying table. Data in this column is permanently lost.
Response: 204 No Content
Roles
| Endpoint | Allowed Roles |
|---|
GET /schemas | admin, editor, viewer |
POST /schemas | admin, editor |
PUT /schemas | admin, editor |
DELETE /schemas | admin, editor |
GET /schemas/fields | admin, editor, viewer |
POST /schemas/fields | admin, editor |
PUT /schemas/fields | admin, editor |
DELETE /schemas/fields | admin, editor |
See also: Data Platform | Computed Values