Developer API
The HTTP door the CLI, the MCP server and CI use: tokens, projects, the contract loop (check, apply, patch, verify, deploy) and operating the running app (SQL, rows, schema, snapshots, deploys, runs, tail, secrets).
On this page
- Authentication
- Endpoints
- POST /tokens
- GET /tokens
- DELETE /tokens/:tokenId
- GET /me
- GET /docs
- POST /components/check
- GET /projects
- POST /projects
- GET /projects/:id
- GET /projects/:id/contract
- POST /projects/:id/check
- POST /projects/:id/apply
- POST /projects/:id/patch
- POST /projects/:id/screenshot
- POST /projects/:id/verify
- POST /projects/:id/data/query
- GET /projects/:id/logs
- POST /projects/:id/deploy
- GET /projects/:id/demo-reset
- POST /projects/:id/demo-reset
- GET /projects/:id/sign-in
- POST /projects/:id/sign-in
- POST /projects/:id/sql
- POST /projects/:id/rows
- GET /projects/:id/schema
- GET /projects/:id/snapshots
- POST /projects/:id/snapshots
- POST /projects/:id/snapshots/:snapshotId/restore
- GET /projects/:id/deploys
- POST /projects/:id/deploys/:deploymentId/rollback
- POST /projects/:id/workflows/:workflowId/run
- GET /projects/:id/runs/:runId
- GET /projects/:id/dead-letters
- POST /projects/:id/dead-letters/replay
- GET /projects/:id/tail
- GET /projects/:id/secrets
- PUT /projects/:id/secrets/:name
- DELETE /projects/:id/secrets/:name
Base URL: https://appbricx.com/api/dev/v1 (self-hosted: <your API>/dev/v1). Errors answer { error, code? } with the HTTP status. The MCP tools and the CLI call the same operations and get the same answers.
Authentication
- Builder session only
- The signed-in builder session. A personal access token can't mint another token.
Authorization: Bearer $SESSION_JWT - Developer token
- A personal access token (apx_pat_…, one workspace, scopes read / apply / deploy), the CLI's browser sign-in token, or the builder session. A project outside the token's reach answers 404, never 403.
Authorization: Bearer $APPBRICX_TOKEN
Endpoints
Mint a token
POST/api/dev/v1/tokens
Create a personal access token for one workspace. The token is shown once.
- Auth
- Builder session only:
Authorization: Bearer $SESSION_JWT
Scope: builder session
Body
| Name | Type | Description |
|---|---|---|
namerequired | string | 1 to 80 characters. |
workspaceIdrequired | uuid | The workspace it reaches. |
scopesoptional | Array<"read" | "apply" | "deploy"> | Default ["read", "apply"]. |
expiresInDaysoptional | number | 1 to 366; omitted, it doesn't expire. |
Response
201 Created.
{
"token": "apx_pat_…",
"id": "t_1",
"name": "ci",
"workspaceId": "6a1f…",
"scopes": [
"read",
"apply",
"deploy"
],
"expiresAt": "2027-01-10T00:00:00Z",
"createdAt": "…",
"note": "Shown once — store it now. Revoke with DELETE /dev/v1/tokens/:id."
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 403 | SESSION_REQUIRED | Called with a token, not a session. |
| 400 | BAD_REQUEST | "Validation failed" with details per field. |
| 400 | INVALID_SCOPE | Unknown scope, or a viewer asking for more than read. |
| 404 | none | "Workspace not found". |
curl -X POST "https://appbricx.com/api/dev/v1/tokens" \
-H "Authorization: Bearer $SESSION_JWT" \
-H "content-type: application/json" \
-d '{"name":"ci","workspaceId":"6a1f…","scopes":["read","apply","deploy"],"expiresInDays":90}'const res = await fetch("https://appbricx.com/api/dev/v1/tokens", {
method: "POST",
headers: {
Authorization: `Bearer ${SESSION_JWT}`,
"content-type": "application/json",
},
body: JSON.stringify({
"name": "ci",
"workspaceId": "6a1f…",
"scopes": [
"read",
"apply",
"deploy"
],
"expiresInDays": 90
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:61
List my tokens
GET/api/dev/v1/tokens
Your unrevoked personal access tokens (prefix only, never the token).
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: any
Response
{
"tokens": [
{
"id": "t_1",
"name": "ci",
"workspace_id": "6a1f…",
"token_prefix": "apx_pat_Ab12Cd",
"scopes": [
"read",
"apply"
],
"expires_at": null,
"last_used_at": "…",
"created_at": "…"
}
]
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
curl "https://appbricx.com/api/dev/v1/tokens" \
-H "Authorization: Bearer $APPBRICX_TOKEN"const res = await fetch("https://appbricx.com/api/dev/v1/tokens", {
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:62
Revoke a token
DELETE/api/dev/v1/tokens/:tokenId
Revoke one of your tokens. It stops working at once.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: any (own tokens)
Path parameters
| Name | Type | Description |
|---|---|---|
:tokenIdrequired | string | The token id. |
Response
{
"revoked": "0b6e…"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Token not found". |
curl -X DELETE "https://appbricx.com/api/dev/v1/tokens/0b6e…" \
-H "Authorization: Bearer $APPBRICX_TOKEN"const res = await fetch("https://appbricx.com/api/dev/v1/tokens/0b6e…", {
method: "DELETE",
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:63
Who am I
GET/api/dev/v1/me
The signed-in user, the credential kind, its scopes and workspaces.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: any
Response
{
"userId": "u_1",
"email": "dev@example.com",
"auth": "pat",
"scopes": [
"read",
"apply"
],
"workspaces": [
{
"id": "6a1f…",
"name": "Acme",
"role": "owner"
}
]
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
curl "https://appbricx.com/api/dev/v1/me" \
-H "Authorization: Bearer $APPBRICX_TOKEN"const res = await fetch("https://appbricx.com/api/dev/v1/me", {
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:65
Agent guide
GET/api/dev/v1/docs
The contract guide for authors, as markdown.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: any
Response
text/markdown.
# appbricx contract guide
Loop: get_contract → edit → check_contract …Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
curl "https://appbricx.com/api/dev/v1/docs" \
-H "Authorization: Bearer $APPBRICX_TOKEN"const res = await fetch("https://appbricx.com/api/dev/v1/docs", {
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
},
});
const text = await res.text();Source: services/api/src/routes/dev-api/index.ts:66
Check a custom component
POST/api/dev/v1/components/check
Run one custom code component through every gate (import allowlist, strict TypeScript against the kit, compile, sandboxed server render with the sample props and with empty props). No project needed, nothing saved.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: any
Body
| Name | Type | Description |
|---|---|---|
namerequired | string | PascalCase; the default export must use it. |
sourcerequired | string | The whole TSX file (200,000 chars max). |
sampleoptional | object | Props for the render gate. |
Response
A failing gate: { ok: false, stage, errors }.
{
"ok": true,
"passed": [
"static",
"types",
"compile",
"render"
]
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 400 | BAD_REQUEST | "Validation failed" with details per field. |
curl -X POST "https://appbricx.com/api/dev/v1/components/check" \
-H "Authorization: Bearer $APPBRICX_TOKEN" \
-H "content-type: application/json" \
-d '{"name":"WeekHeatmap","source":"import type { AppHandle } from \"@app/kit/primitives\";\nexport default function WeekHeatmap({ app }: { app?: AppHandle }) { return null; }","sample":{}}'const res = await fetch("https://appbricx.com/api/dev/v1/components/check", {
method: "POST",
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
"content-type": "application/json",
},
body: JSON.stringify({
"name": "WeekHeatmap",
"source": "import type { AppHandle } from \"@app/kit/primitives\";\nexport default function WeekHeatmap({ app }: { app?: AppHandle }) { return null; }",
"sample": {}
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:68
List projects
GET/api/dev/v1/projects
Projects this credential can reach, newest first (200 max).
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: any
Response
{
"projects": [
{
"id": "3b7e…",
"name": "Tablefor",
"slug": "tablefor",
"workspace_id": "6a1f…",
"updated_at": "…"
}
]
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
curl "https://appbricx.com/api/dev/v1/projects" \
-H "Authorization: Bearer $APPBRICX_TOKEN"const res = await fetch("https://appbricx.com/api/dev/v1/projects", {
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:69
Create a project
POST/api/dev/v1/projects
Create an empty project. Then apply a contract to build it.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: apply
Body
| Name | Type | Description |
|---|---|---|
namerequired | string | 1 to 100 characters. |
workspaceIdoptional | uuid | Required for a session; a token is pinned to its workspace. |
descriptionoptional | string | Up to 2000 characters. |
Response
201 Created.
{
"project": {
"id": "3b7e…",
"name": "Tablefor",
"slug": "tablefor",
"workspaceId": "6a1f…"
}
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 403 | FORBIDDEN_SCOPE | The token lacks the "apply" scope. |
| 400 | BAD_REQUEST | "Validation failed" with details per field. |
| 404 | none | "Workspace not found". |
curl -X POST "https://appbricx.com/api/dev/v1/projects" \
-H "Authorization: Bearer $APPBRICX_TOKEN" \
-H "content-type: application/json" \
-d '{"name":"Tablefor"}'const res = await fetch("https://appbricx.com/api/dev/v1/projects", {
method: "POST",
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
"content-type": "application/json",
},
body: JSON.stringify({
"name": "Tablefor"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:70
Project state
GET/api/dev/v1/projects/:id
Whether the project has been built, the last build failure if any, and its preview path.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: any
Path parameters
| Name | Type | Description |
|---|---|---|
:idrequired | string | The project id (uuid). |
Response
{
"project": {
"id": "3b7e…",
"name": "Tablefor",
"workspaceId": "6a1f…",
"role": "owner"
},
"state": {
"built": true,
"lastError": null
},
"previewPath": "/preview/3b7e…/"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Project not found": the id is wrong or outside the token's reach. |
curl "https://appbricx.com/api/dev/v1/projects/3b7e…" \
-H "Authorization: Bearer $APPBRICX_TOKEN"const res = await fetch("https://appbricx.com/api/dev/v1/projects/3b7e…", {
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:71
Get the contract
GET/api/dev/v1/projects/:id/contract
The project's current contract (null before the first apply). Seeds come back as "UNCHANGED": rows live in the database.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: any
Path parameters
| Name | Type | Description |
|---|---|---|
:idrequired | string | The project id (uuid). |
Response
{
"contract": {
"app": {
"name": "Tablefor"
},
"personas": [
{
"id": "staff",
"kind": "staff"
}
],
"entities": [
{
"name": "bookings",
"fields": [
{
"name": "date",
"type": "date"
}
]
}
],
"seeds": "UNCHANGED"
},
"built": true
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Project not found": the id is wrong or outside the token's reach. |
curl "https://appbricx.com/api/dev/v1/projects/3b7e…/contract" \
-H "Authorization: Bearer $APPBRICX_TOKEN"const res = await fetch("https://appbricx.com/api/dev/v1/projects/3b7e…/contract", {
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:72
Check a contract
POST/api/dev/v1/projects/:id/check
Normalize a contract and report errors (must be empty to apply), fixes and warnings. No side effects.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: any
Path parameters
| Name | Type | Description |
|---|---|---|
:idrequired | string | The project id (uuid). |
Body
| Name | Type | Description |
|---|---|---|
contractrequired | object | The full contract. |
Response
{
"report": {
"ok": true,
"errors": [],
"fixes": [
"entities.tickets.fields.status: enum_values filled from seeds"
],
"warnings": []
}
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Project not found": the id is wrong or outside the token's reach. |
| 400 | none | "contract is required". |
curl -X POST "https://appbricx.com/api/dev/v1/projects/3b7e…/check" \
-H "Authorization: Bearer $APPBRICX_TOKEN" \
-H "content-type: application/json" \
-d '{"contract":{"app":{"name":"Tablefor"},"personas":[],"entities":[]}}'const res = await fetch("https://appbricx.com/api/dev/v1/projects/3b7e…/check", {
method: "POST",
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
"content-type": "application/json",
},
body: JSON.stringify({
"contract": {
"app": {
"name": "Tablefor"
},
"personas": [],
"entities": []
}
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:73
Apply a contract
POST/api/dev/v1/projects/:id/apply
Build the dev environment from a full contract: database created or evolved additively (rows kept), pages compiled, workflows armed.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: apply
Path parameters
| Name | Type | Description |
|---|---|---|
:idrequired | string | The project id (uuid). |
Body
| Name | Type | Description |
|---|---|---|
contractrequired | object | The full contract. |
forceoptional | boolean | Overwrite hand-edited compiled files (backed up first). |
Response
{
"report": {
"ok": true,
"errors": [],
"fixes": [
"entities.tickets.fields.status: enum_values filled from seeds"
],
"warnings": []
},
"summary": "Built 4 tables, 9 pages, 2 workflows. Demo sign-ins: staff@demo.app / customer@demo.app",
"warnings": []
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Project not found": the id is wrong or outside the token's reach. |
| 403 | FORBIDDEN_SCOPE | The token lacks the "apply" scope. |
| 400 | none | "contract is required". |
| 422 | CONTRACT_INVALID | The report has errors. |
| 409 | PROJECT_BUSY | Another build is running (retry). |
| 409 | HAND_EDITED_FILES | Compiled files were edited by hand; re-run with force. |
| 500 | BUILD_FAILED | The build failed at stage. |
curl -X POST "https://appbricx.com/api/dev/v1/projects/3b7e…/apply" \
-H "Authorization: Bearer $APPBRICX_TOKEN" \
-H "content-type: application/json" \
-d '{"contract":{"app":{"name":"Tablefor"}},"force":false}'const res = await fetch("https://appbricx.com/api/dev/v1/projects/3b7e…/apply", {
method: "POST",
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
"content-type": "application/json",
},
body: JSON.stringify({
"contract": {
"app": {
"name": "Tablefor"
}
},
"force": false
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:74
Patch the contract
POST/api/dev/v1/projects/:id/patch
Apply typed edit ops (add_entity, adopt_table, add_field, update_field, remove_field, set_workflow, add_page, set_component, edit_component, set_app…) to the current contract, then rebuild. adopt_table turns a table you made by hand with SQL into a contract entity: columns are read from the dev database and its rows are kept.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: apply
Path parameters
| Name | Type | Description |
|---|---|---|
:idrequired | string | The project id (uuid). |
Body
| Name | Type | Description |
|---|---|---|
editsrequired | object[] | 1 to 50 ops, each { op, ... }. |
dryRunoptional | boolean | Report only. |
forceoptional | boolean | Overwrite hand-edited files. |
Response
{
"report": {
"ok": true,
"errors": [],
"fixes": [
"entities.tickets.fields.status: enum_values filled from seeds"
],
"warnings": []
},
"summary": "Would add bookings.party_size"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Project not found": the id is wrong or outside the token's reach. |
| 403 | FORBIDDEN_SCOPE | The token lacks the "apply" scope. |
| 422 | CONTRACT_INVALID | The result is invalid. |
| 409 | PROJECT_BUSY | Busy. |
curl -X POST "https://appbricx.com/api/dev/v1/projects/3b7e…/patch" \
-H "Authorization: Bearer $APPBRICX_TOKEN" \
-H "content-type: application/json" \
-d '{"edits":[{"op":"add_field","entity":"bookings","field":{"name":"party_size","type":"number"}}],"dryRun":true}'const res = await fetch("https://appbricx.com/api/dev/v1/projects/3b7e…/patch", {
method: "POST",
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
"content-type": "application/json",
},
body: JSON.stringify({
"edits": [
{
"op": "add_field",
"entity": "bookings",
"field": {
"name": "party_size",
"type": "number"
}
}
],
"dryRun": true
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:78
Screenshot the app
POST/api/dev/v1/projects/:id/screenshot
Open the dev preview in a real browser (390×844 by default), signed in as a demo role or as a guest, and return the PNG plus page errors, horizontal overflow and the visible text. ?format=png answers the image itself.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: any
Path parameters
| Name | Type | Description |
|---|---|---|
:idrequired | string | The project id (uuid). |
Query parameters
| Name | Type | Description |
|---|---|---|
formatoptional | "png" | Answer image/png instead of JSON. |
Body
| Name | Type | Description |
|---|---|---|
routeoptional | string | App route like /people. |
asoptional | string | Persona id, demo login email, or "guest". |
widthoptional | number | 320 to 1600. |
heightoptional | number | 480 to 1200. |
fullPageoptional | boolean | The whole scrollable page. |
Response
{
"png": "iVBORw0KGgo…",
"route": "/bookings",
"as": "staff",
"errors": [],
"overflowX": 0,
"text": "Bookings · Today · 3 tables free"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Project not found": the id is wrong or outside the token's reach. |
| 400 | BAD_REQUEST | Invalid route, size or persona. |
| 409 | NOT_BUILT | Apply a contract first. |
| 422 | SCREENSHOT_FAILED | The browser couldn't capture the page. |
curl -X POST "https://appbricx.com/api/dev/v1/projects/3b7e…/screenshot" \
-H "Authorization: Bearer $APPBRICX_TOKEN" \
-H "content-type: application/json" \
-d '{"route":"/bookings","as":"staff","width":390}'const res = await fetch("https://appbricx.com/api/dev/v1/projects/3b7e…/screenshot", {
method: "POST",
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
"content-type": "application/json",
},
body: JSON.stringify({
"route": "/bookings",
"as": "staff",
"width": 390
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:82
Verify
POST/api/dev/v1/projects/:id/verify
Run verify on the dev environment: static checks, data checks (demo sign-ins, role by table permission matrix with rolled-back writes, signed-out access, workflows armed) and a browser pass. Takes a few minutes.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: any
Path parameters
| Name | Type | Description |
|---|---|---|
:idrequired | string | The project id (uuid). |
Response
{
"report": {
"status": "pass",
"stages": [
{
"name": "static",
"status": "pass",
"checks": []
},
{
"name": "data",
"status": "pass",
"checks": []
},
{
"name": "browser",
"status": "pass",
"checks": []
}
]
}
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Project not found": the id is wrong or outside the token's reach. |
curl -X POST "https://appbricx.com/api/dev/v1/projects/3b7e…/verify" \
-H "Authorization: Bearer $APPBRICX_TOKEN"const res = await fetch("https://appbricx.com/api/dev/v1/projects/3b7e…/verify", {
method: "POST",
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:88
Query dev rows
POST/api/dev/v1/projects/:id/data/query
Read rows from a table in the dev database (read-only, 200 max). Filters are equality.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: any
Path parameters
| Name | Type | Description |
|---|---|---|
:idrequired | string | The project id (uuid). |
Body
| Name | Type | Description |
|---|---|---|
tablerequired | string | The table (entity also accepted). |
whereoptional | object | Column → value equality. |
orderByoptional | string | Default created_at. |
descoptional | boolean | Default true. |
limitoptional | number | 1 to 200 (default 50). |
Response
{
"table": "bookings",
"rows": [
{
"id": "b_1",
"status": "confirmed",
"date": "2026-10-12"
}
],
"rowCount": 1
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Project not found": the id is wrong or outside the token's reach. |
| 400 | BAD_REQUEST | "Validation failed" with details per field. |
| 409 | none | No app yet: apply a contract first. |
| 400 | BAD_QUERY | Unknown table or column. |
curl -X POST "https://appbricx.com/api/dev/v1/projects/3b7e…/data/query" \
-H "Authorization: Bearer $APPBRICX_TOKEN" \
-H "content-type: application/json" \
-d '{"table":"bookings","where":{"status":"confirmed"},"limit":10}'const res = await fetch("https://appbricx.com/api/dev/v1/projects/3b7e…/data/query", {
method: "POST",
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
"content-type": "application/json",
},
body: JSON.stringify({
"table": "bookings",
"where": {
"status": "confirmed"
},
"limit": 10
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:89
Workflow logs
GET/api/dev/v1/projects/:id/logs
Recent workflow runs (status, error and their last 8 log lines) and in-app notifications.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: any
Path parameters
| Name | Type | Description |
|---|---|---|
:idrequired | string | The project id (uuid). |
Query parameters
| Name | Type | Description |
|---|---|---|
limitoptional | number | 1 to 200 (default 30). |
workflowoptional | string | Only this workflow's runs. |
Response
{
"runs": [
{
"id": "4f0a…",
"workflow_id": "send-invoice",
"status": "failed",
"error": "SMTP timeout",
"log": [
{
"ts": "…",
"level": "error",
"message": "timeout"
}
]
}
],
"notifications": []
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Project not found": the id is wrong or outside the token's reach. |
curl "https://appbricx.com/api/dev/v1/projects/3b7e…/logs?limit=10" \
-H "Authorization: Bearer $APPBRICX_TOKEN"const res = await fetch("https://appbricx.com/api/dev/v1/projects/3b7e…/logs?limit=10", {
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:90
Deploy
POST/api/dev/v1/projects/:id/deploy
Publish to https://<subdomain>.<publish domain>. Runs verify first and refuses on red unless skipVerify. Takes a few minutes.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: deploy
Path parameters
| Name | Type | Description |
|---|---|---|
:idrequired | string | The project id (uuid). |
Body
| Name | Type | Description |
|---|---|---|
environmentoptional | "production" | "preview" | Default production. |
allowDestructiveoptional | boolean | Confirm destructive schema changes on promote. |
skipVerifyoptional | boolean | Publish even when verify is red. |
subdomainoptional | string | One DNS label (up to 48 chars). Omitted, the project keeps its own. |
demoResetoptional | boolean | true resets published data to the preview's every night; false stops it. |
Response
{
"deploymentId": "d_9",
"url": "https://tablefor.apps.appbricx.com",
"status": "live",
"durationMs": 142000,
"report": {
"status": "pass"
}
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Project not found": the id is wrong or outside the token's reach. |
| 403 | FORBIDDEN_SCOPE | The token lacks the "deploy" scope. |
| 400 | BAD_REQUEST | "Validation failed" with details per field. |
| 400 | INVALID_SUBDOMAIN | Not a valid label. |
| 409 | SUBDOMAIN_TAKEN | Another project has it. |
| 422 | VERIFY_FAILED | Verify is red (report attached). |
| 409 | none | "A deployment is already in progress" (deploymentId attached). |
| 500 | DEPLOY_FAILED | The pipeline failed (buildLog tail attached). |
curl -X POST "https://appbricx.com/api/dev/v1/projects/3b7e…/deploy" \
-H "Authorization: Bearer $APPBRICX_TOKEN" \
-H "content-type: application/json" \
-d '{"subdomain":"tablefor"}'const res = await fetch("https://appbricx.com/api/dev/v1/projects/3b7e…/deploy", {
method: "POST",
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
"content-type": "application/json",
},
body: JSON.stringify({
"subdomain": "tablefor"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:92
Demo mode state
GET/api/dev/v1/projects/:id/demo-reset
Whether the published data is reset to the preview's every night.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: deploy
Path parameters
| Name | Type | Description |
|---|---|---|
:idrequired | string | The project id (uuid). |
Response
{
"demoReset": {
"enabled": true,
"lastRunAt": "2026-10-12T03:00:00Z"
}
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Project not found": the id is wrong or outside the token's reach. |
| 403 | FORBIDDEN_SCOPE | The token lacks the "deploy" scope. |
curl "https://appbricx.com/api/dev/v1/projects/3b7e…/demo-reset" \
-H "Authorization: Bearer $APPBRICX_TOKEN"const res = await fetch("https://appbricx.com/api/dev/v1/projects/3b7e…/demo-reset", {
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:93
Change demo mode
POST/api/dev/v1/projects/:id/demo-reset
Turn nightly demo resets on or off, or reset now.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: deploy
Path parameters
| Name | Type | Description |
|---|---|---|
:idrequired | string | The project id (uuid). |
Body
| Name | Type | Description |
|---|---|---|
enabledoptional | boolean | Turn on or off. |
runNowoptional | boolean | Reset the published data now. |
Response
500 when runNow failed.
{
"demoReset": {
"enabled": true
},
"run": {
"ok": true
}
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Project not found": the id is wrong or outside the token's reach. |
| 403 | FORBIDDEN_SCOPE | The token lacks the "deploy" scope. |
| 400 | BAD_REQUEST | "Validation failed" with details per field. |
curl -X POST "https://appbricx.com/api/dev/v1/projects/3b7e…/demo-reset" \
-H "Authorization: Bearer $APPBRICX_TOKEN" \
-H "content-type: application/json" \
-d '{"enabled":true,"runNow":true}'const res = await fetch("https://appbricx.com/api/dev/v1/projects/3b7e…/demo-reset", {
method: "POST",
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
"content-type": "application/json",
},
body: JSON.stringify({
"enabled": true,
"runNow": true
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:94
Sign-in providers
GET/api/dev/v1/projects/:id/sign-in
Google and GitHub sign-in for the app and whether each is on.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: read
Path parameters
| Name | Type | Description |
|---|---|---|
:idrequired | string | The project id (uuid). |
Response
{
"providers": [
{
"provider": "google",
"enabled": true,
"client": "platform"
},
{
"provider": "github",
"enabled": false
}
]
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Project not found": the id is wrong or outside the token's reach. |
| 403 | FORBIDDEN_SCOPE | The token lacks the "read" scope. |
curl "https://appbricx.com/api/dev/v1/projects/3b7e…/sign-in" \
-H "Authorization: Bearer $APPBRICX_TOKEN"const res = await fetch("https://appbricx.com/api/dev/v1/projects/3b7e…/sign-in", {
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:95
Change a sign-in provider
POST/api/dev/v1/projects/:id/sign-in
Turn a provider on or off. client "platform" uses Appbricx's own Google client (no Google Cloud setup); "own" takes your client id and secret (register https://<app host>/__appbricx/auth/oauth/<provider>/callback).
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: apply
Path parameters
| Name | Type | Description |
|---|---|---|
:idrequired | string | The project id (uuid). |
Body
| Name | Type | Description |
|---|---|---|
providerrequired | "google" | "github" | The provider. |
enabledrequired | boolean | On or off. |
clientoptional | "platform" | "own" | Whose OAuth client. |
clientIdoptional | string | Own client id. |
clientSecretoptional | string | Own client secret. |
Response
{
"providers": [
{
"provider": "google",
"enabled": true,
"client": "platform"
}
]
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Project not found": the id is wrong or outside the token's reach. |
| 403 | FORBIDDEN_SCOPE | The token lacks the "apply" scope. |
| 400 | BAD_REQUEST | "Validation failed" with details per field. |
| 400 | none | The configuration isn't valid (e.g. own client without a secret). |
curl -X POST "https://appbricx.com/api/dev/v1/projects/3b7e…/sign-in" \
-H "Authorization: Bearer $APPBRICX_TOKEN" \
-H "content-type: application/json" \
-d '{"provider":"google","enabled":true,"client":"platform"}'const res = await fetch("https://appbricx.com/api/dev/v1/projects/3b7e…/sign-in", {
method: "POST",
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
"content-type": "application/json",
},
body: JSON.stringify({
"provider": "google",
"enabled": true,
"client": "platform"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:96
Run SQL
POST/api/dev/v1/projects/:id/sql
Run SQL like the builder's SQL editor: several statements in one transaction, DDL and DML, as the tenant admin (past row-level security). Read-only scripts run in a rolled-back transaction. A prod write takes a snapshot first.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: read for read-only scripts; apply on dev, deploy on prod for writes
Path parameters
| Name | Type | Description |
|---|---|---|
:idrequired | string | The project id (uuid). |
Body
| Name | Type | Description |
|---|---|---|
sqlrequired | string | One or more statements (64 KB max). |
envoptional | "dev" | "prod" | Which database: dev (the preview, default) or prod (the published app). |
Response
Rows are the last statement that returned any (1000 max). Prod writes add snapshotId.
{
"rows": [
{
"count": "128"
}
],
"rowCount": 1,
"truncated": false,
"statements": [
{
"sql": "create index if not exists bookings_date on bookings(date)",
"command": "CREATE",
"rowCount": 0
},
{
"sql": "select count(*) from bookings",
"command": "SELECT",
"rowCount": 1
}
],
"env": "dev",
"readOnly": false,
"durationMs": 41
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Project not found": the id is wrong or outside the token's reach. |
| 403 | FORBIDDEN_SCOPE | The token lacks the "apply / deploy" scope. |
| 400 | BAD_REQUEST | Validation failed or no statement. |
| 400 | STMT_FORBIDDEN | References pg_catalog or information_schema (use schema). |
| 400 | (SQL code) | A statement failed; the whole script rolled back (rolledBack: true). |
| 503 | SNAPSHOT_FAILED | Couldn't snapshot prod before the write; nothing changed. |
curl -X POST "https://appbricx.com/api/dev/v1/projects/3b7e…/sql" \
-H "Authorization: Bearer $APPBRICX_TOKEN" \
-H "content-type: application/json" \
-d '{"sql":"create index if not exists bookings_date on bookings(date); select count(*) from bookings;","env":"dev"}'const res = await fetch("https://appbricx.com/api/dev/v1/projects/3b7e…/sql", {
method: "POST",
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
"content-type": "application/json",
},
body: JSON.stringify({
"sql": "create index if not exists bookings_date on bookings(date); select count(*) from bookings;",
"env": "dev"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:100
Edit rows
POST/api/dev/v1/projects/:id/rows
Insert, update or delete rows through the app's own data API: access rules, before-write rules, lifecycle guards, row-level security and change events behave as in the app. as "admin" (default) writes elevated; as { role } acts as an app user with that role.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: apply on dev, deploy on prod
Path parameters
| Name | Type | Description |
|---|---|---|
:idrequired | string | The project id (uuid). |
Body
| Name | Type | Description |
|---|---|---|
tablerequired | string | The table. |
envoptional | "dev" | "prod" | Which database: dev (the preview, default) or prod (the published app). |
oprequired | "insert" | "update" | "delete" | What to do. |
rowsoptional | object[] | insert: 1 to 100 rows. |
idoptional | string | number | update / delete: one id. |
idsoptional | Array<string | number> | update / delete: 1 to 100 ids. |
patchoptional | object | update: columns to set. |
asoptional | "admin" | { role, user_id? } | Who acts (default admin). |
Response
422 when every row failed. Per-row codes: RULE_REFUSED, E_LIFECYCLE_DIRECT_WRITE, NOT_FOUND, the stamp code.
{
"table": "bookings",
"env": "dev",
"op": "update",
"as": {
"role": "staff",
"userId": "u_7"
},
"succeeded": 2,
"failed": 0,
"results": [
{
"ok": true,
"id": "b_1",
"row": {
"id": "b_1",
"status": "cancelled"
}
},
{
"ok": true,
"id": "b_2",
"row": {
"id": "b_2",
"status": "cancelled"
}
}
]
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Project not found": the id is wrong or outside the token's reach. |
| 403 | FORBIDDEN_SCOPE | The token lacks the "apply / deploy" scope. |
| 400 | BAD_REQUEST | Validation failed. |
| 409 | NO_PERSONA | No app user holds that role in this env. |
| 403 | TABLE_FORBIDDEN | The role may not do this, or it's an internal table. |
| 503 | SNAPSHOT_FAILED | Prod snapshot failed; nothing changed. |
curl -X POST "https://appbricx.com/api/dev/v1/projects/3b7e…/rows" \
-H "Authorization: Bearer $APPBRICX_TOKEN" \
-H "content-type: application/json" \
-d '{"table":"bookings","op":"update","ids":["b_1","b_2"],"patch":{"status":"cancelled"},"as":{"role":"staff"}}'const res = await fetch("https://appbricx.com/api/dev/v1/projects/3b7e…/rows", {
method: "POST",
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
"content-type": "application/json",
},
body: JSON.stringify({
"table": "bookings",
"op": "update",
"ids": [
"b_1",
"b_2"
],
"patch": {
"status": "cancelled"
},
"as": {
"role": "staff"
}
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:101
Describe the schema
GET/api/dev/v1/projects/:id/schema
The real schema: tables with columns, keys, constraints, indexes, triggers and policies; views, functions and enums. Each table's origin says whether the contract manages it, the platform made it, it's internal, or it was made by hand.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: read
Path parameters
| Name | Type | Description |
|---|---|---|
:idrequired | string | The project id (uuid). |
Query parameters
| Name | Type | Description |
|---|---|---|
envoptional | "dev" | "prod" | Which database: dev (the preview, default) or prod (the published app). |
Response
{
"env": "prod",
"tables": [
{
"name": "bookings",
"origin": "contract",
"columns": [
{
"name": "date",
"type": "date",
"nullable": false,
"managedByContract": true
}
],
"indexes": [
"bookings_date"
]
}
],
"views": [],
"functions": [],
"enums": []
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Project not found": the id is wrong or outside the token's reach. |
| 403 | FORBIDDEN_SCOPE | The token lacks the "read" scope. |
| 409 | none | The env's database doesn't exist yet (prod before the first publish). |
curl "https://appbricx.com/api/dev/v1/projects/3b7e…/schema?env=prod" \
-H "Authorization: Bearer $APPBRICX_TOKEN"const res = await fetch("https://appbricx.com/api/dev/v1/projects/3b7e…/schema?env=prod", {
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:102
List snapshots
GET/api/dev/v1/projects/:id/snapshots
Database snapshots (pg_dump) of dev and prod. Every publish and every agent write on prod takes one.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: read
Path parameters
| Name | Type | Description |
|---|---|---|
:idrequired | string | The project id (uuid). |
Query parameters
| Name | Type | Description |
|---|---|---|
envoptional | "dev" | "prod" | Only this environment. |
Response
{
"snapshots": [
{
"id": "prod-2026-10-12T09-00-00Z-a1b2",
"env": "prod",
"reason": "publish",
"createdAt": "…",
"bytes": 482113
}
]
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Project not found": the id is wrong or outside the token's reach. |
| 403 | FORBIDDEN_SCOPE | The token lacks the "read" scope. |
| 503 | SNAPSHOTS_UNAVAILABLE | Snapshots aren't configured on this install. |
curl "https://appbricx.com/api/dev/v1/projects/3b7e…/snapshots?env=prod" \
-H "Authorization: Bearer $APPBRICX_TOKEN"const res = await fetch("https://appbricx.com/api/dev/v1/projects/3b7e…/snapshots?env=prod", {
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:103
Take a snapshot
POST/api/dev/v1/projects/:id/snapshots
Take a snapshot now (default env prod).
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: apply
Path parameters
| Name | Type | Description |
|---|---|---|
:idrequired | string | The project id (uuid). |
Body
| Name | Type | Description |
|---|---|---|
envoptional | "dev" | "prod" | Default prod. |
Response
201 Created.
{
"snapshot": {
"id": "prod-2026-10-12T09-30-00Z-c3d4",
"env": "prod",
"reason": "manual",
"createdAt": "…",
"bytes": 482990
}
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Project not found": the id is wrong or outside the token's reach. |
| 403 | FORBIDDEN_SCOPE | The token lacks the "apply" scope. |
| 500 | SNAPSHOT_FAILED | pg_dump failed. |
curl -X POST "https://appbricx.com/api/dev/v1/projects/3b7e…/snapshots" \
-H "Authorization: Bearer $APPBRICX_TOKEN" \
-H "content-type: application/json" \
-d '{"env":"prod"}'const res = await fetch("https://appbricx.com/api/dev/v1/projects/3b7e…/snapshots", {
method: "POST",
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
"content-type": "application/json",
},
body: JSON.stringify({
"env": "prod"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:105
Restore a snapshot
POST/api/dev/v1/projects/:id/snapshots/:snapshotId/restore
Replace the whole database with a snapshot. The current state is snapshotted first (undoSnapshotId), so a restore can be undone.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: deploy
Path parameters
| Name | Type | Description |
|---|---|---|
:idrequired | string | The project id (uuid). |
:snapshotIdrequired | string | From the list, e.g. prod-… |
Response
{
"restored": "prod-2026-10-12T09-00-00Z-a1b2",
"env": "prod",
"undoSnapshotId": "prod-2026-10-12T09-31-12Z-e5f6"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Project not found": the id is wrong or outside the token's reach. |
| 403 | FORBIDDEN_SCOPE | The token lacks the "deploy" scope. |
| 404 | SNAPSHOT_NOT_FOUND | No such snapshot. |
| 500 | RESTORE_FAILED | The restore failed. |
curl -X POST "https://appbricx.com/api/dev/v1/projects/3b7e…/snapshots/prod-2026-10-12T09-00-00Z-a1b2/restore" \
-H "Authorization: Bearer $APPBRICX_TOKEN"const res = await fetch("https://appbricx.com/api/dev/v1/projects/3b7e…/snapshots/prod-2026-10-12T09-00-00Z-a1b2/restore", {
method: "POST",
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:107
Deploy history
GET/api/dev/v1/projects/:id/deploys
The project's deployments: status, url, git sha and timing.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: read
Path parameters
| Name | Type | Description |
|---|---|---|
:idrequired | string | The project id (uuid). |
Query parameters
| Name | Type | Description |
|---|---|---|
environmentoptional | "production" | "preview" | Filter. |
limitoptional | number | 1 to 100 (default 20). |
Response
{
"deployments": [
{
"id": "d_9",
"environment": "production",
"status": "live",
"url": "https://tablefor.apps.appbricx.com",
"version_number": 12,
"git_sha": "a1b2c3d",
"build_time_ms": 81000,
"created_at": "…"
}
]
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Project not found": the id is wrong or outside the token's reach. |
| 403 | FORBIDDEN_SCOPE | The token lacks the "read" scope. |
curl "https://appbricx.com/api/dev/v1/projects/3b7e…/deploys?limit=5" \
-H "Authorization: Bearer $APPBRICX_TOKEN"const res = await fetch("https://appbricx.com/api/dev/v1/projects/3b7e…/deploys?limit=5", {
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:109
Roll back
POST/api/dev/v1/projects/:id/deploys/:deploymentId/rollback
Switch what's live back to an earlier successful deployment (its stored build, else a rebuild of its git sha). Files only: roll the database back with snapshots.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: deploy
Path parameters
| Name | Type | Description |
|---|---|---|
:idrequired | string | The project id (uuid). |
:deploymentIdrequired | string | The deployment to go back to. |
Response
{
"data": {
"url": "https://tablefor.apps.appbricx.com",
"mode": "stored-build"
}
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Project not found": the id is wrong or outside the token's reach. |
| 403 | FORBIDDEN_SCOPE | The token lacks the "deploy" scope. |
| 404 | none | No such successful deployment. |
curl -X POST "https://appbricx.com/api/dev/v1/projects/3b7e…/deploys/5c1d…/rollback" \
-H "Authorization: Bearer $APPBRICX_TOKEN"const res = await fetch("https://appbricx.com/api/dev/v1/projects/3b7e…/deploys/5c1d…/rollback", {
method: "POST",
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:111
Run a workflow
POST/api/dev/v1/projects/:id/workflows/:workflowId/run
Run a workflow now with input. Server functions run inline (as the persona in as, or signed out); other workflows are queued. Waits up to 30 seconds for the result.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: apply on dev, deploy on prod
Path parameters
| Name | Type | Description |
|---|---|---|
:idrequired | string | The project id (uuid). |
:workflowIdrequired | string | The workflow id. |
Body
| Name | Type | Description |
|---|---|---|
inputoptional | object | Its trigger payload. |
envoptional | "dev" | "prod" | Which database: dev (the preview, default) or prod (the published app). |
asoptional | { role, user_id? } | Act as an app user holding this role. |
Response
202 with finished: false when still running; read it with GET …/runs/:runId.
{
"runId": "4f0a…",
"status": "succeeded",
"finished": true,
"result": {
"sent": true
},
"error": null
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Project not found": the id is wrong or outside the token's reach. |
| 403 | FORBIDDEN_SCOPE | The token lacks the "apply / deploy" scope. |
| 404 | WORKFLOW_NOT_FOUND | No compiled workflow of that id. |
| 409 | NO_PERSONA | No app user holds that role. |
| 422 | WORKFLOW_FAILED | The run failed. |
| 429 | QUOTA_EXCEEDED | Daily runs used up. |
| 422 | ENQUEUE_FAILED | Couldn't start it. |
curl -X POST "https://appbricx.com/api/dev/v1/projects/3b7e…/workflows/send-invoice/run" \
-H "Authorization: Bearer $APPBRICX_TOKEN" \
-H "content-type: application/json" \
-d '{"input":{"order":"b_1"},"env":"dev"}'const res = await fetch("https://appbricx.com/api/dev/v1/projects/3b7e…/workflows/send-invoice/run", {
method: "POST",
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
"content-type": "application/json",
},
body: JSON.stringify({
"input": {
"order": "b_1"
},
"env": "dev"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:113
Get a run
GET/api/dev/v1/projects/:id/runs/:runId
One run with its log.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: read
Path parameters
| Name | Type | Description |
|---|---|---|
:idrequired | string | The project id (uuid). |
:runIdrequired | string | The run id. |
Response
{
"run": {
"id": "4f0a…",
"workflow_id": "send-invoice",
"status": "succeeded",
"error": null,
"logs": [
{
"ts": "…",
"level": "info",
"message": "invoice sent"
}
]
}
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Project not found": the id is wrong or outside the token's reach. |
| 403 | FORBIDDEN_SCOPE | The token lacks the "read" scope. |
| 404 | RUN_NOT_FOUND | No such run. |
curl "https://appbricx.com/api/dev/v1/projects/3b7e…/runs/4f0a1c2d-…" \
-H "Authorization: Bearer $APPBRICX_TOKEN"const res = await fetch("https://appbricx.com/api/dev/v1/projects/3b7e…/runs/4f0a1c2d-…", {
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:117
Dead letters
GET/api/dev/v1/projects/:id/dead-letters
Jobs that used up their retries.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: read
Path parameters
| Name | Type | Description |
|---|---|---|
:idrequired | string | The project id (uuid). |
Query parameters
| Name | Type | Description |
|---|---|---|
limitoptional | number | 1 to 200. |
Response
{
"deadLetters": [
{
"id": "j_9",
"workflow_id": "send-invoice",
"env": "prod",
"last_error": "SMTP timeout"
}
]
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Project not found": the id is wrong or outside the token's reach. |
| 403 | FORBIDDEN_SCOPE | The token lacks the "read" scope. |
curl "https://appbricx.com/api/dev/v1/projects/3b7e…/dead-letters" \
-H "Authorization: Bearer $APPBRICX_TOKEN"const res = await fetch("https://appbricx.com/api/dev/v1/projects/3b7e…/dead-letters", {
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:119
Replay dead letters
POST/api/dev/v1/projects/:id/dead-letters/replay
Queue dead letters again (all when ids is omitted).
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: apply (deploy when a job ran on prod)
Path parameters
| Name | Type | Description |
|---|---|---|
:idrequired | string | The project id (uuid). |
Body
| Name | Type | Description |
|---|---|---|
idsoptional | uuid[] | 1 to 100 job ids. |
Response
{
"replayed": [
"9a0b…"
],
"skipped": []
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Project not found": the id is wrong or outside the token's reach. |
| 403 | FORBIDDEN_SCOPE | The token lacks the "apply / deploy" scope. |
curl -X POST "https://appbricx.com/api/dev/v1/projects/3b7e…/dead-letters/replay" \
-H "Authorization: Bearer $APPBRICX_TOKEN" \
-H "content-type: application/json" \
-d '{"ids":["9a0b…"]}'const res = await fetch("https://appbricx.com/api/dev/v1/projects/3b7e…/dead-letters/replay", {
method: "POST",
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
"content-type": "application/json",
},
body: JSON.stringify({
"ids": [
"9a0b…"
]
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:121
Tail events and requests
GET/api/dev/v1/projects/:id/tail
Recent activity: events (row writes and topic messages), requests (every error and about 10% of the rest, 7 days) or errors (4xx and 5xx only). Paths only, never bodies, query strings or tokens.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: read
Path parameters
| Name | Type | Description |
|---|---|---|
:idrequired | string | The project id (uuid). |
Query parameters
| Name | Type | Description |
|---|---|---|
kindrequired | "events" | "requests" | "errors" | What to show. |
envoptional | "dev" | "prod" | Which database: dev (the preview, default) or prod (the published app). |
sinceoptional | string | 15m, 2h, 1d or an ISO time. |
limitoptional | number | 1 to 500 (default 100). |
Response
{
"kind": "errors",
"env": "prod",
"requests": [
{
"at": "…",
"method": "POST",
"path": "/__appbricx/api/v1/bookings",
"status": 422,
"ms": 38,
"error": "RULE_REFUSED",
"app_user_id": "u_7"
}
],
"note": "…"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Project not found": the id is wrong or outside the token's reach. |
| 403 | FORBIDDEN_SCOPE | The token lacks the "read" scope. |
| 400 | BAD_REQUEST | since isn't a duration or ISO time. |
curl "https://appbricx.com/api/dev/v1/projects/3b7e…/tail?kind=errors&env=prod&since=2h" \
-H "Authorization: Bearer $APPBRICX_TOKEN"const res = await fetch("https://appbricx.com/api/dev/v1/projects/3b7e…/tail?kind=errors&env=prod&since=2h", {
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:123
List secrets
GET/api/dev/v1/projects/:id/secrets
The app's secrets: names, env and updated-at, never values. Also which names the app declares and which are missing.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: read
Path parameters
| Name | Type | Description |
|---|---|---|
:idrequired | string | The project id (uuid). |
Response
{
"secrets": [
{
"name": "STRIPE_KEY",
"scope": "project",
"env": "prod",
"secret": true,
"updatedAt": "…"
}
],
"declaredByApp": [
"STRIPE_KEY",
"SHIPPO_KEY"
],
"missing": [
"SHIPPO_KEY"
],
"note": "Values are never returned."
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Project not found": the id is wrong or outside the token's reach. |
| 403 | FORBIDDEN_SCOPE | The token lacks the "read" scope. |
curl "https://appbricx.com/api/dev/v1/projects/3b7e…/secrets" \
-H "Authorization: Bearer $APPBRICX_TOKEN"const res = await fetch("https://appbricx.com/api/dev/v1/projects/3b7e…/secrets", {
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:125
Set a secret
PUT/api/dev/v1/projects/:id/secrets/:name
Create or update a project secret. ctx.secrets reads env prod or all.
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: apply, and a workspace owner or admin
Path parameters
| Name | Type | Description |
|---|---|---|
:idrequired | string | The project id (uuid). |
:namerequired | string | An env var name (letters, digits, _). |
Body
| Name | Type | Description |
|---|---|---|
valuerequired | string | Up to 10,000 characters. |
envoptional | "dev" | "prod" | "all" | Default all. |
Response
201 when created, 200 when updated.
{
"name": "SHIPPO_KEY",
"env": "all",
"created": true,
"updatedAt": "…"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Project not found": the id is wrong or outside the token's reach. |
| 403 | FORBIDDEN_SCOPE | The token lacks the "apply" scope. |
| 403 | FORBIDDEN_ROLE | Only workspace owners and admins write secrets. |
| 400 | BAD_REQUEST | Bad name or missing value. |
curl -X PUT "https://appbricx.com/api/dev/v1/projects/3b7e…/secrets/SHIPPO_KEY" \
-H "Authorization: Bearer $APPBRICX_TOKEN" \
-H "content-type: application/json" \
-d '{"value":"shippo_live_…","env":"all"}'const res = await fetch("https://appbricx.com/api/dev/v1/projects/3b7e…/secrets/SHIPPO_KEY", {
method: "PUT",
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
"content-type": "application/json",
},
body: JSON.stringify({
"value": "shippo_live_…",
"env": "all"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:126
Delete a secret
DELETE/api/dev/v1/projects/:id/secrets/:name
Delete a project secret (env limits it to one environment).
- Auth
- Developer token:
Authorization: Bearer $APPBRICX_TOKEN
Scope: apply, and a workspace owner or admin
Path parameters
| Name | Type | Description |
|---|---|---|
:idrequired | string | The project id (uuid). |
:namerequired | string | The secret name. |
Query parameters
| Name | Type | Description |
|---|---|---|
envoptional | "dev" | "prod" | "all" | Only this environment. |
Response
{
"deleted": "SHIPPO_KEY",
"envs": [
"dev"
]
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing, invalid, expired or revoked token. |
| 404 | none | "Project not found": the id is wrong or outside the token's reach. |
| 403 | FORBIDDEN_SCOPE | The token lacks the "apply" scope. |
| 403 | FORBIDDEN_ROLE | Owners and admins only. |
| 404 | SECRET_NOT_FOUND | No such secret. |
curl -X DELETE "https://appbricx.com/api/dev/v1/projects/3b7e…/secrets/SHIPPO_KEY?env=dev" \
-H "Authorization: Bearer $APPBRICX_TOKEN"const res = await fetch("https://appbricx.com/api/dev/v1/projects/3b7e…/secrets/SHIPPO_KEY?env=dev", {
method: "DELETE",
headers: {
Authorization: `Bearer ${APPBRICX_TOKEN}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? data.error ?? res.statusText);Source: services/api/src/routes/dev-api/index.ts:130