Auto CRUD REST API
Point your schema at /__appbricx/api/v1/:table and get list / get / create / update / delete — same auth + RLS as the data plane. No Express, Fastify, or hand-rolled routers.
Path note. SDKs call
/__appbricx/*. Legacy /__appbricx/* still works (API rewrites both).1. Configure ACL — tables.json
Create .appbricx/backend/api/tables.json. Always do this before demos: an empty ACL can expose every non-internal table.
{
"allow": ["leads", "waitlist"],
"tables": {
"leads": {
"expose": true,
"methods": ["GET", "POST", "PATCH"]
},
"waitlist": {
"expose": true,
"methods": ["GET", "POST"]
}
}
}allow— optional allow-list of table names.expose: false— hide a table even if listed elsewhere.methods— HTTP verbs permitted for that table.- Tables starting with
_appbricxare always blocked.
2. Auth headers (every call)
| Header | Required | Purpose |
|---|---|---|
Authorization: Bearer <data-token> | Yes | Project-scoped data API token |
x-appbricx-data-api: 1 | Yes | Marks the request as data-plane traffic |
x-appbricx-app-session: <jwt> | When RLS | End-user session so app.user_id is set |
In the preview iframe the platform injects __APPBRICX_DATA_TOKEN (and session when logged in). Outside preview, use a project API key / data token from project settings.
3. Endpoints
| Method | Path | Notes |
|---|---|---|
| GET | /__appbricx/api/v1/:table | Query: limit, offset, where (JSON object) |
| GET | /__appbricx/api/v1/:table/:id | Primary key lookup |
| POST | /__appbricx/api/v1/:table | JSON body = column map |
| PATCH | /__appbricx/api/v1/:table/:id | Partial update |
| DELETE | /__appbricx/api/v1/:table/:id | Delete by id |
4. SDK examples
import { runtime } from "@appbricx/runtime";
// List with filter
const page = await runtime.api.list("leads", {
limit: 50,
offset: 0,
where: { status: "new" },
});
if (!page.ok) console.error(page.error);
// Create
const created = await runtime.api.create("leads", {
email: "demo@example.com",
source: "mobile",
});
// Get / update / delete
await runtime.api.get("leads", created.rows[0].id);
await runtime.api.update("leads", created.rows[0].id, { status: "contacted" });
await runtime.api.delete("leads", created.rows[0].id);5. curl / Postman
# List
curl -sS "$ORIGIN/__appbricx/api/v1/leads?limit=20&where=%7B%22status%22%3A%22new%22%7D" \
-H "Authorization: Bearer $DATA_TOKEN" \
-H "x-appbricx-data-api: 1" \
-H "x-appbricx-app-session: $APP_SESSION"
# Create
curl -sS -X POST "$ORIGIN/__appbricx/api/v1/leads" \
-H "Authorization: Bearer $DATA_TOKEN" \
-H "x-appbricx-data-api: 1" \
-H "content-type: application/json" \
-d '{"email":"a@b.com","source":"postman"}'How it works internally
- Request hits Caddy → API (
/__appbricxrewritten to/__appbricx). requireRuntimeAuthvalidates data token +x-appbricx-data-api: 1.- ACL from
tables.jsongates table + method. - CRUD helpers build parameterized SQL against the project PGlite worker; RLS uses
app.user_idfrom the app session. - Mutating statements emit CDC events (topics / workflows if bound).
Named queries vs auto CRUD. Use CRUD for simple table shapes. Use named queries for joins, aggregates, or logic shared with workflows.