Databases
Manage tables and rows, import data, and run SQL queries via the API.
Endpoints for databases under /api/v1/databases.
Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/databases | List tables |
| POST | /api/v1/databases | Create a table |
| PATCH | /api/v1/databases/{tableId} | Update a table |
| GET | /api/v1/databases/{tableId}/rows | List rows |
| POST | /api/v1/databases/{tableId}/rows | Insert rows |
| PATCH | /api/v1/databases/{tableId}/rows | Update rows |
| DELETE | /api/v1/databases/{tableId}/rows | Delete rows |
| GET | /api/v1/databases/{tableId}/export | Export a table |
| GET | /api/v1/databases/{tableId}/managed-rows | Read managed rows |
| PUT | /api/v1/databases/{tableId}/managed-rows | Create or update one managed row |
| POST | /api/v1/databases/import | Import data (CSV) |
| POST | /api/v1/databases/query | Run a SQL query |
| GET | /api/v1/configuration/catalog | List configuration catalog |
| GET | /api/v1/configuration/history | Read configuration history |
| POST | /api/v1/configuration/{operation} | Export, validate, preview, prepare, apply, or declare configuration |
Tables and rows
GET /api/v1/databases returns { databases[], tables[] } for the workspace.
POST /api/v1/databases creates a database (when only databaseName is given) or a table:
| Field | Type | Notes |
|---|---|---|
databaseName | string | Target database (required) |
tableName | string | If present, creates a table in that database |
columns | array | Column definitions; type ∈ text | number | status | date | json |
rows | array | Optional initial rows; scalar columns keep their documented scalar values, while a json column accepts any JSON value, including arrays, objects, booleans, and null |
GET /api/v1/databases/{tableId}/rows supports limit and offset and returns
{ table, rows[], pagination: { limit, offset, returned, total } }. POST inserts one row
({ values }), PATCH updates by { rowIndex, values }, and DELETE removes by { rowIndex }.
PATCH /api/v1/databases/{tableId} trashes a table ({ trashed }) or adds a column
({ column, defaultValue }).
Each row's values object must name only declared columns. Scalar columns accept strings or finite
numbers; values supplied as JavaScript numbers (not strings) must be finite. A partial PATCH updates
only the supplied keys and leaves every other cell untouched, including cells stored as absent or JSON
null. Structured values are valid only in columns declared as json. Invalid table creation, inserts,
or updates return 400 (BAD_REQUEST in tRPC); they are never surfaced as an internal-server error.
JSON exports preserve the original structure, while CSV exports serialize each JSON cell as compact JSON
text.
Import CSV
POST /api/v1/databases/import is multipart/form-data only:
| Field | Notes |
|---|---|
file | Required, .csv / CSV mime; max 5 MB (413), non-CSV → 415 |
databaseId / databaseName | Target database |
tableName | Defaults to a cleaned-up filename |
hasHeader | true/1/yes, default true |
delimiter | Optional delimiter override (auto-detected otherwise) |
columns | JSON array of { name, type } type overrides |
Returns 201 with { table, stats, rowsUrl }. Malformed CSV returns 422 { error, code }.
Run a SQL query
POST /api/v1/databases/query runs a read-only query against one database's tables:
{ "databaseId": "db_...", "sql": "SELECT * FROM \"orders\" WHERE total > 100 LIMIT 100" }
Both fields are required. Queries are SELECT-only and run in a sandbox over the database's
tables (never against Postgres). Success returns { columns, rows, truncated, durationMs }. A
missing database returns 404; a slow query returns 408 { error, code, availableTables }; other
SQL errors return 400. See Databases & SQL for the constraints.
Export
GET /api/v1/databases/{tableId}/export?format=csv|json (default json) returns the table as a
downloadable file with a Content-Disposition: attachment header.
Managed configuration
Managed configuration applies only to tables explicitly declared as non-sensitive configuration by
an Admin or Owner. The declaration fixes the textual key and lifecycle columns (active and
retired), the allowed column types, and any local table references. Ordinary tables retain their
existing API behavior. Declared tables reject legacy CSV append and workflow writes; use the stable
managed key and expected revision instead.
Declaration is permanent: it cannot be undone, and ordinary writers cannot be restored.
Recognized secrets in either lifecycle value are rejected before declaration, including for empty
tables. A column with tableReference: false is equivalent to one that omits the flag.
All managed configuration requests are scoped to the workspace of the authenticated API key. A
client that has organization access must still name the destination workspace with the
workspaceId query parameter when targeting it; the key's effective access is checked against that
workspace. A key or organization principal cannot use this parameter to cross the authorized
workspace boundary.
The dynamic configuration route has this method split:
| Method | Path | Operation |
|---|---|---|
| GET | /api/v1/configuration/catalog | List declared tables, contracts, databases, and affected writers |
| GET | /api/v1/configuration/history | Read workspace-scoped application receipts |
| POST | /api/v1/configuration/export | Export selected declared tables |
| POST | /api/v1/configuration/validate | Validate a package without reading or writing destination rows |
| POST | /api/v1/configuration/preview | Calculate actions and blockers |
| POST | /api/v1/configuration/prepare | Persist a proposal and return a panel URL for human review |
| POST | /api/v1/configuration/apply | Apply a currently confirmed plan when application is enabled |
| POST | /api/v1/configuration/declare | Declare one existing table as managed configuration |
Using the wrong method for a listed operation returns HTTP 405 with an Allow header naming
the supported method. Unknown operation names and internal row-handler names return HTTP 404
for either method; use the dedicated managed-rows route for row access.
The catalog also returns applyEnabled and applyDisabledReason. The reason is null when
deployment settings enable writes, or writes_disabled when the feature flag is off. These fields
describe deployment availability; the caller still needs Admin or Owner authority to write.
Without durable storage, the catalog returns HTTP 503 with applyEnabled: false and
applyDisabledReason: "storage_unavailable", without table or database metadata.
There is no API confirm operation. prepare returns a proposal link; the authenticated human
session in the Tables panel performs the confirmation before apply. Agents and API clients can
prepare a proposal but cannot issue that confirmation themselves. GET and PUT
/api/v1/databases/{tableId}/managed-rows read or write one declared table by stable key. The PUT
body is { "key", "expectedRevision", "values" }; the key is immutable and a stale revision is a
conflict.
For row writes, a declared table-reference cell contains the ID of a managed table in the same
workspace. A table owned by an applied package can reference only another table in that deployment.
Portable exports replace these IDs with logical $table keys. An export reference conflict returns
bounded details.blockers with logical tableKey, row key, and reason; preview returns the same
identifiers in its blockers. Error details retain the full row key, up to 256 characters, so keys
with a shared prefix remain distinguishable. Existing rows remain readable so an authorized editor can correct a
legacy invalid reference.
Configuration packages use fluxus.configuration-artifact.v1 and are capped at 20 tables, 500
rows per table, 1,000 rows total, and 1 MiB. Before loading row values, export checks the selected
stored JSON text in PostgreSQL and rejects more than 32 MiB. This is a separate read budget, not an
increase to the portable package limit. Packages contain only explicitly selected non-sensitive
configuration. Source IDs are not portable, and references use logical $table keys within the
package. Export reads all selected tables from one consistent database snapshot, including when
concurrent writers commit changes. Every destination must be mapped explicitly to an existing declared table or to a named
table in a selected database.
Preview and apply compare the package, the observed destination, and the last successful baseline.
A row already equal to the desired package is unchanged, even if its prior baseline differs;
that is convergence. Differences unexplained by the desired package or baseline are drift.
Schema, ownership, classification, missing, unexpected, or changed-row drift blocks apply; no SQL
override, delete operation, or ignore-drift flag exists. A successful apply writes all package
tables, its baseline, and its receipt atomically. Repeating a request with the same requestId and
input returns the existing receipt after authorization checks; using that identity for different
input is a conflict, which makes a lost response safe to retry. An authorized retry can recover a
committed receipt even after the write flag is disabled; a request without a committed receipt
still returns 503 while writes are disabled.
GET /api/v1/configuration/history returns up to the latest 50 workspace-scoped receipts. Each
receipt identifies the actor (actorId), package key, action identifiers, status: "applied", and creation time;
it does not include managed row values. In the Tables panel, an existing row draft retains the
revision it originally read even if background data refreshes. A failed new-row save leaves its
entered values available for correction. Reload rows and discard edits clears local drafts and
fetches current rows when you want to recover from a stale revision.
Apply, declaration, and managed-row writes require Admin or Owner authority, durable PostgreSQL,
and MANAGED_CONFIGURATION_APPLY_ENABLED=true. Until deployment certification enables the flag,
apply remains unavailable while ordinary tables continue to work. A deployment without
DATABASE_URL does not advertise apply availability. Errors use 400 for validation, 403 for
authorization, 404 for an unknown or cross-workspace resource, 409 for drift or ownership
conflict, 412 for expired confirmation/proposal state, and 503 when durable storage or apply is
unavailable.
