DocsPlatformData API

Data API

Define an entity in your manifest, deploy, and get a full REST API instantly. No controllers, no SQL, no boilerplate.

Every entity also gets auto-generated MCP tools, so AI agents and external AI tools can read and write to the same data with the same RBAC enforced.


Endpoints

Base URL: /api/v1/apps/{appId}/collections/{entity}

Method Path Permission Description
GET / .read List records with simple equality filters
POST / .create Create one record
POST /query .read Query with advanced operators
POST /bulk .create Bulk create (up to 1000 records)
GET /{id} .read Get one record by ID
PATCH /{id} .update Partial update
DELETE /{id} .delete Delete one record

Every request requires a valid Authorization: Bearer token. The Core resolves the user's permissions and checks app:{appId}:{entity}.{action} before executing any SQL. Unknown entities return 404.


System columns

Every entity gets three auto-managed columns:

Column Type Description
id UUID Primary key. Auto-generated on create.
created_at TIMESTAMPTZ Set on insert. Cannot be overridden.
updated_at TIMESTAMPTZ Set on insert and every update. Cannot be overridden.

List records (GET)

GET /api/v1/apps/{appId}/collections/{entity}?stage=lead&limit=50&sort=created_at&order=desc

Query parameters

Parameter Default Description
Any field name Equality filter. Multiple fields combine with AND. Type is auto-cast to match the column type.
limit (none — returns all) Max records to return. Range: 1-1000. If omitted, no LIMIT is applied (returns all matching records).
offset 0 Records to skip (pagination).
sort created_at Column to sort by. Must be a known field, id, created_at, or updated_at. Unknown fields fallback to created_at.
order desc Sort direction: asc or desc.
linked Set to true to enrich with federated identity data from all apps, or a comma-separated list of app IDs.

Response

Returns a JSON array of record objects:

[
  {
    "id": "a1b2c3d4-...",
    "first_name": "Alice",
    "last_name": "Smith",
    "stage": "lead",
    "created_at": "2024-12-01T10:00:00Z",
    "updated_at": "2024-12-01T10:00:00Z"
  }
]

Query records (POST /query)

For complex filters beyond simple equality:

POST /api/v1/apps/{appId}/collections/{entity}/query

Request body

{
  "where": {
    "$or": [
      { "stage": "lead" },
      { "amount": { "$gt": 10000 } }
    ]
  },
  "orderBy": "created_at",
  "order": "desc",
  "limit": 50,
  "offset": 0,
  "linked": true
}
Field Type Default Description
where object Filter conditions using the operator DSL (see below).
orderBy string created_at Column to sort by.
order string desc Sort direction: asc or desc.
limit number 100 Max records. Range: 1-1000.
offset number 0 Records to skip.
linked boolean or string[] Enrich with federated identity data. true for all apps, or array of specific app IDs.

Response

{
  "data": [{ "id": "...", "first_name": "Alice", ... }],
  "total": 42
}

The total field is the count of all matching records (ignoring limit/offset), useful for pagination.


Where operators

Operator Description Example
Direct value Equality (shorthand for $eq) { "status": "active" }
$eq Equal. Handles null: { "$eq": null } becomes IS NULL. { "status": { "$eq": "active" } }
$ne Not equal. Handles null: { "$ne": null } becomes IS NOT NULL. { "status": { "$ne": "archived" } }
$gt Greater than { "amount": { "$gt": 100 } }
$gte Greater than or equal { "amount": { "$gte": 100 } }
$lt Less than { "amount": { "$lt": 1000 } }
$lte Less than or equal { "amount": { "$lte": 1000 } }
$like SQL LIKE (case-sensitive) { "name": { "$like": "%acme%" } }
$ilike SQL ILIKE (case-insensitive) { "name": { "$ilike": "%acme%" } }
$in Value in array { "status": { "$in": ["active", "lead"] } }
$nin Value not in array { "status": { "$nin": ["archived", "deleted"] } }
$contains PostgreSQL array contains (for [text] and [number] fields) { "tags": { "$contains": ["vip"] } }
$isNull Is null / is not null { "email": { "$isNull": true } }

Logical operators

Operator Description Example
$and AND multiple conditions (array) { "$and": [{ "stage": "lead" }, { "amount": { "$gt": 1000 } }] }
$or OR multiple conditions (array) { "$or": [{ "stage": "lead" }, { "stage": "qualified" }] }
$not Negate a condition (object) { "$not": { "status": "archived" } }

Multiple keys at the top level are implicitly ANDed.


Create record (POST)

POST /api/v1/apps/{appId}/collections/{entity}
{ "first_name": "Alice", "last_name": "Smith", "email": "alice@example.com" }

Response (201):

{
  "id": "a1b2c3d4-...",
  "first_name": "Alice",
  "last_name": "Smith",
  "email": "alice@example.com",
  "created_at": "2024-12-01T10:00:00Z",
  "updated_at": "2024-12-01T10:00:00Z"
}

Body must contain at least one writable field. System fields (id, created_at, updated_at) are ignored if sent.


Bulk create (POST /bulk)

POST /api/v1/apps/{appId}/collections/{entity}/bulk

Body: a JSON array of record objects. Maximum 1000 records per request.

[
  { "first_name": "Alice", "email": "alice@example.com" },
  { "first_name": "Bob", "email": "bob@example.com" }
]

Response (201): array of created records with IDs.


Get record (GET /{id})

GET /api/v1/apps/{appId}/collections/{entity}/{id}

Returns the full record object. 404 if not found.


Update record (PATCH /{id})

PATCH /api/v1/apps/{appId}/collections/{entity}/{id}
{ "first_name": "Alice B." }

Only the fields you send are updated. updated_at is set automatically. Returns the full updated record.


Delete record (DELETE /{id})

DELETE /api/v1/apps/{appId}/collections/{entity}/{id}

Returns { "message": "record '{id}' deleted" }. 404 if not found.


Linked entities (identity federation)

When entities declare identityKind and identityKey in the manifest, the linked parameter enriches records with data from other apps that share the same identity kind.

Example: if contacts in your CRM declares identityKind: "contact" and identityKey: "email", and the support app also has contacts with the same identity kind, passing linked=true adds a _linked object to each record with matching data from other apps.

The Core only includes data from apps the current user has .read permission for.


Federated query

Query across all entities that share the same identity kind:

POST /api/v1/federated/{identityKind}/query

Same request body as POST /query. Returns records from all apps that declare the matching identityKind, filtered by the user's read permissions. Each record includes _source: { app, entity } metadata.


Schema Sync Engine

Every deploy triggers an automatic diff between your manifest and the live PostgreSQL schema. The Core generates and runs the minimum DDL within a transaction.

Change DDL executed
New entity CREATE TABLE
New field ALTER TABLE ADD COLUMN
Field type changed ALTER COLUMN TYPE with USING cast
required added SET NOT NULL
required removed DROP NOT NULL
default_value set SET DEFAULT
default_value removed DROP DEFAULT
on_delete changed Replace foreign key constraint
Field removed DROP COLUMN CASCADE
Entity removed DROP TABLE CASCADE
indexes changed Drop + recreate (tag-owned, no churn if unchanged)
checks / enum_values changed Drop + recreate (tag-owned, no churn if unchanged)

Column DDL runs in a single transaction per table. Indexes and checks are reconciled in a separate pass. If any statement fails, the transaction rolls back and the deploy is rejected. Unchanged objects are left untouched (no drop/recreate churn).

The Schema Sync Engine drops columns removed from the manifest and can drop orphaned tables. Only id, created_at, and updated_at are protected.

Auto-generated MCP

Every entity gets auto-generated MCP tools. AI agents and external AI tools interact with your data through MCP, with the same RBAC permissions enforced. The tools mirror the REST API: query, create, update, delete.