TotalBase
Data API

Overview

The Data API at a glance: URLs, the request and response envelope, status codes and how a request is processed.

The Data API is the REST API over the tables, views and functions you expose. It lives under /api on the instance's address. It has no version prefix, and it's the same API the admin, the App and the MCP server use.

URLs

/api/{schema}/table/{name}            rows of a table
/api/{schema}/table/{name}/{id}       one row, by its primary key
/api/{schema}/view/{name}             rows of a view (read-only)
/api/{schema}/function/{name}         call a function

The table, view or function segment is always written out.

MethodOn a tableOn a viewOn a function
GETread rows, or one row by keyread rows
POSTcreate one row, several (bulk=true), or a row with its childrencall it
PATCHupdate one row by key, rows matching where, or several by key
DELETEdelete one row by key, rows matching where, or several by key

A method exists for a resource only if it's exposed. Otherwise the answer is 404, as for a table that doesn't exist.

Some routes aren't resources:

RouteFor
/api/auth/*signing in, registration, passwords: Authentication
/api/tokensyour personal access tokens
POST /api/transactionsseveral writes in one transaction: Transactions
GET /api/executions/{id}the result of an asynchronous call
/api/totalbase/table/files/*uploading and serving files
/api/workflows/{slug}/*running workflows: Manual trigger
GET /api/openapi.jsonthe OpenAPI document for the caller

Every route, with its method and what it needs: Routes.

Request and response

Bodies use one envelope in both directions. What you send goes in data, what you get back comes in data, and information about the result comes in meta:

POST /api/public/table/customers
Authorization: Bearer <token>
Content-Type: application/json

{ "data": { "name": "Mercado del Norte S.A.", "status": "ACTIVE" } }
{ "data": { "id": "6a0f...", "name": "Mercado del Norte S.A.", "status": "ACTIVE" } }

Reads take their options as query parameters and have no body:

GET /api/public/table/customers?fields=id,name&where[status][_eq]=ACTIVE&count=true
{ "data": [ { "id": "6a0f...", "name": "Mercado del Norte S.A." } ], "meta": { "count": 38 } }

Errors come in errors:

{ "errors": [ { "source": "POSTGRESQL", "code": "UNIQUE_VIOLATION",
                "message": "A customer with this tax id already exists.", "field": "tax_id" } ] }
  • source is POSTGRESQL when the database refused, and TOTALBASE for TotalBase's own checks.
  • code is stable and safe to branch on. The full list is in Errors.
  • field, when present, names the one column the error is about, so a form can show it there.

Column names keep their PostgreSQL spelling (reported_at). Keys TotalBase defines are camelCase (meta.count).

Status codes

StatusWhen
200a read, an update, a delete (returning what it removed), a function call
201a create
204nothing to return
400invalid query or input: an unknown operator, a malformed value, a limit exceeded
401no credential, or an invalid or expired one
403authenticated, but the action or a field isn't allowed
404the resource or row doesn't exist, or isn't visible to you
409a conflict: unique or foreign key violation, an idempotency key in use
413the body is too large
422a rule refused the values: CHECK, NOT NULL, RAISE EXCEPTION, a payload check
429too many requests
503PostgreSQL cancelled the statement (timeout)

What happens to a request

Raw SQL from clients is never accepted. Every request is processed the same way:

request  →  limits  →  parse the query  →  resolve the resource (exposed?)
         →  permissions (action, fields, row filter)  →  compile to parameterized SQL
         →  PostgreSQL: one transaction, with a statement timeout
  • Values are always sent as bind parameters, and names come only from PostgreSQL's catalog, so a request can't inject SQL.
  • Each request runs in one PostgreSQL transaction, reads included, with statement_timeout set to api.request_timeout_ms (30 s by default).
  • Writes also record an audit entry and an event in the same transaction. See Audit and events.

What the Data API doesn't do

These are deliberate. Each has a PostgreSQL answer:

Instead ofUse
saved queriesa view
computed fieldsa generated column, a view, or a function
custom actions on a tablea function
full-text searchsearch is a substring match. For more, a view or function using PostgreSQL's full-text search.
relations without a foreign keyadd the foreign key

Next steps