Dynamic tables

Create app-private tables, CRUD rows, and read/write them through the Data API — no separate database needed.


Dynamic tables are an app-private data store. Without attaching an external database, you can create tables and add, read, update, and delete rows. AxHub manages them in a PostgreSQL schema isolated per app.

Create a table

Create one from the CLI by specifying columns. The command defaults to a dry-run, so add --execute to actually create it.

axhub tables create orders --app demo \
  --column 'title:text' \
  --column 'qty:int' \
  --column 'done:bool' \
  --execute

A column is name:type[:nullable][:default]. There are eight types:

TypeDescription
textString
int · bigintIntegers
floatFloating point
boolTrue/false
timestamptzTimestamp with timezone
uuidUUID
jsonbJSON

With many columns, you can pass a schema file with --schema table.yaml. To track which user owns a row, set --owner-column <column>.

Work with rows

Add, read, update, and delete rows from the CLI too. Inserts, updates, and deletes need --execute.

axhub data insert orders --app demo --body '{"title":"First order","qty":2}' --execute
axhub data list orders --app demo
axhub data get orders <row-id> --app demo
axhub data count orders --app demo
axhub data update orders <row-id> --app demo --body '{"done":true}' --execute
axhub data delete orders <row-id> --app demo --execute

To insert many rows at once, use --batch rows.jsonl (one JSON object per line).

The Data API

There are two ways to work with table data from code.

① Inside an app — the SDK (recommended) · simplest, as the logged-in user. See Call the backend with the SDK.

const app = await makeApp(); // template-provided helper
const todos = await app.data.discover<{ id: string; title: string; done: boolean }>('todos');
await todos.list({ limit: 20 });
await todos.insert({ title: 'A task', done: false });

② Externally — HTTP API + PAT · for agents, CI, or server-to-server calls from outside the app, use HTTP directly. The path is /data/{tenant}/{app}/{table}.

Method · pathWhat it does
GET /data/{tenant}/{app}/{table}List
GET …/{table}/_countCount
GET …/{table}/{id}Get one
POST …/{table}Insert
PATCH …/{table}/{id}Update
DELETE …/{table}/{id}Delete
const base = `https://api.axhub.ai/data/${tenant}/${app}/orders`;
const headers = { 'X-Api-Key': process.env.AXHUB_API_KEY, 'Content-Type': 'application/json' };

const rows = await fetch(base, { headers }).then((r) => r.json()); // list
await fetch(base, { method: 'POST', headers, body: JSON.stringify({ title: 'First order', qty: 2 }) }); // insert

A PAT (personal access token) is sent in the X-Api-Key header. Don't hard-code it — keep it as a runtime secret in environment variables and read it on the server. Creating and managing tables (DDL) uses login (OAuth), not a PAT.

Share with another app

By default only the app that created a table can access it. To open read/write to another app or principal, grant a table grant.

axhub tables grants list --app demo

See the Data API for the exact endpoints, queries, grants, and auth.