Data API

Every app answers an HTTP API on its own host, the same one its pages use. A backend, a script or a mobile app can read and write your tables, call your server actions and run your named queries, and the same access policies decide what each caller gets. You write no routes: the API comes from db/ and server/.

Where it is

APP=https://dev-teamday.apps.appbricx.com     # the dev site; production: https://teamday.apps.appbricx.com or your domain

The host decides the environment: the dev site reads and writes the dev database, the production host and custom domains production's. Your agent can call the same API without keys with the call_app tool.

Keys and sessions

HeaderWhenWhat
Authorization: Bearer <key>AlwaysA project key: the browser key (dpk_c_…, every page carries it; needs an Origin of the app), or a server key (dpk_s_…, made in the console under the project's Settings, API keys).
X-Appbricx-Data-Api: 1On /__appbricx/api, queries and actionsMarks a data-API call.
X-Appbricx-App-Session: <token>To act as a signed-in userThe token POST /__appbricx/auth/login answers (or the session cookie). Without it, the call is a signed-out visitor.

A server key needs no Origin and may act as a user with X-Appbricx-App-User and X-Appbricx-App-Role. Treat it like a database password and keep it on servers.

KEY=$(curl -s "$APP/" | grep -o 'dpk_c_[A-Za-z0-9_-]*' | head -1)
H=(-H "Authorization: Bearer $KEY" -H "Origin: $APP" -H 'X-Appbricx-Data-Api: 1')

SESSION=$(curl -sS -X POST "$APP/__appbricx/auth/login" -H "Authorization: Bearer $KEY" \
  -H 'content-type: application/json' -d '{"email": "hr@demo.teamday.app", "password": "…"}' | jq -r .token)
U=(-H "X-Appbricx-App-Session: $SESSION")

Tables

RouteDoes
GET /__appbricx/api/v1/<table>List: where, filter, sort, limit, offset or cursor, embed
GET /__appbricx/api/v1/<table>/<id>One row
POST /__appbricx/api/v1/<table>Create
PATCH /__appbricx/api/v1/<table>/<id>Update
DELETE /__appbricx/api/v1/<table>/<id>Delete (to the trash on a ctx.soft_delete table)
PATCH …/<id>/transition, POST …/<id>/restoreMove a lifecycle state; bring a row back from the trash
GET …/<table>/aggregate, GET …/<table>/search?q=, GET /__appbricx/api/v1/searchcount, sum, avg, min, max by group or time bucket; full-text search
GET /__appbricx/api/v1/openapi.jsonThe app's OpenAPI document
# Published experiences under ₹2,000 a head, signed out (the public policy decides)
curl -sS -G "$APP/__appbricx/api/v1/experience" "${H[@]}" \
  --data-urlencode 'where={"status":"published"}' \
  --data-urlencode 'filter=[{"field":"price","op":"lte","value":2000}]' \
  --data-urlencode 'sort={"field":"title","dir":"asc"}'

# My bookings with the experience's title, as the signed-in booker
curl -sS -G "$APP/__appbricx/api/v1/booking" "${H[@]}" "${U[@]}" --data-urlencode 'embed=experience(title)'

# Cancel one
curl -sS -X PATCH "$APP/__appbricx/api/v1/booking/$BOOKING/transition" "${H[@]}" "${U[@]}" \
  -H 'content-type: application/json' -d '{"field": "status", "to": "cancelled"}'

Answers are {"ok": true, "rows": [...], "rowCount": n, "fields": [...]}; with a cursor they add nextCursor and hasMore. Columns marked @hide <role> are left out for that role; a table marked @api off isn't served.

Actions, queries and batches

# A server action (server/bookings.ts confirmBooking), as an ops user
curl -sS -X POST "$APP/__appbricx/actions/confirmBooking" "${H[@]}" "${U[@]}" \
  -H 'content-type: application/json' -H "Idempotency-Key: confirm-$BOOKING" \
  -d "{\"booking\": \"$BOOKING\"}"
# → {"ok": true, "result": {"ref": "BK-ACME1", "status": "confirmed"}, "runId": "…"}

# A named query (db/queries/experience-bookings.sql)
curl -sS -X POST "$APP/__appbricx/queries/experience-bookings" "${H[@]}" "${U[@]}" \
  -H 'content-type: application/json' -d "{\"experience\": \"$EXPERIENCE\"}"

# Up to 20 calls in one round trip, each with its own status
curl -sS -X POST "$APP/__appbricx/batch" "${H[@]}" "${U[@]}" -H 'content-type: application/json' \
  -d '[{"method": "GET", "path": "/__appbricx/api/v1/booking?limit=5"}, {"method": "GET", "path": "/__appbricx/api/v1/experience?limit=5"}]'

An action's input is checked against its schema before it runs; the same Idempotency-Key twice answers the first result without running again.

Also on the app host

  • Sign-in: /__appbricx/auth/* (sign-up, login, sign-in links, social sign-in, two-factor, sessions).
  • Files: POST /__appbricx/files/sign, then upload; download with GET /__appbricx/files/<id>.
  • Webhooks: POST /__appbricx/hooks/<name> delivers to a webhook(name) workflow.
  • Realtime and topics: /__appbricx/realtime/*, /__appbricx/topics/<name>/subscribe (server-sent events).
  • AI: POST /__appbricx/ai/chat on the project's own key.

Limits and errors

  • 600 requests a minute per project (1,200 with a server key), a quarter of that per IP and per user: 429 RATE_LIMITED with Retry-After.
  • 403 TABLE_FORBIDDEN: the role may not use the table; 400 RLS_VIOLATION: a policy refused the row; 422 RULE_REFUSED: your trigger's message; 409 CONFLICT: a unique value is taken.
  • Errors answer {"ok": false, "error": {"code": "…", "message": "…"}}. Ask your agent to run explain on a refused request to see which policy decided.

Related