| Header | Notes |
|---|---|
Authorization | Required. Bearer <token> |
X-Tenant-Id | Required. |
Returns all tenant models and all global models.
Response
{
"models": [
{
"id": 1,
"modelKey": "blog_post",
"name": "Blog Post",
"description": null,
"isGlobal": false,
"status": "active",
"fields": []
}
]
}
Returns a single model with its fields. Returns 404 if not found.
Creates a new tenant model. Requires admin or modeller role.
Request body
| Field | Notes |
|---|---|
modelKey | Required. Unique identifier key. |
name | Required. |
description | Optional. |
fields | Optional array of field definitions. |
Field definition shape
{
"fieldKey": "title",
"name": "Title",
"fieldType": "string",
"sortOrder": 1,
"required": true,
"defaultValue": null,
"allowedModelIds": []
}
Alternate: a TypeScript type as the body
Send Content-Type: text/plain with a TS interface/type declaration instead of JSON, and the model is created from it directly — no fields array needed.
interface BlogPost {
title: string;
viewCount: number;
published?: boolean;
publishedAt?: Date;
status: 'draft' | 'published';
tags?: string[];
author: Author;
}
The interface name becomes modelKey/name. Each property becomes a field: string→string, number→decimal, boolean→boolean, Date→datetime, a string-literal union→enum (the literals become validationRules.options), T[]/Array<T>→an array field, Imaginary/Imaginary[]→imaginary/imaginary_array (reserved sentinel names, checked before the rule below), and a capitalized identifier (like Author above)→a content reference, resolved against an existing model whose modelKey matches that name (snake_cased). A property without ? is required. This also works on PUT /models/:id and POST /space/:id/models.
Replaces a model's definition including all fields. Automatically migrates existing content rows — adds default values for new required fields and removes data for deleted fields. Returns a migration summary. Requires admin or modeller role. Returns 403 for global models. Accepts the same TS-type text/plain body as POST /models above.
Response
{
"model": {},
"migration": {
"contentUpdated": 3,
"fieldsAdded": [
"summary"
],
"fieldsRemoved": [
"legacy_field"
],
"fieldsChanged": []
}
}
Permanently deletes a model and its fields. Returns 409 if content exists for the model or if another model's field references it. Returns 403 for global models. Requires admin or modeller role.
Validates a data object against the model's required fields without writing to the database.
Request body
{
"data": {
"title": "Hello",
"body": "World"
}
}
Response
{
"valid": true,
"errors": []
}
Dry-runs a proposed field list against existing content to identify migration issues before calling PUT /models/:id. Requires admin or modeller role.
Response
{
"valid": true,
"affectedContent": 12,
"changes": {
"fieldsAdded": [
"summary"
],
"fieldsRemoved": [],
"fieldsChanged": []
},
"errors": []
}
Returns paginated content items for the model.
Query parameters
| Parameter | Notes |
|---|---|
page | Default 1. |
pageSize | Default 20, max 100. |
status | Optional filter. |