REST API
Tento obsah zatím není dostupný ve vašem jazyce.
Adminium exposes a REST API at /api/v1. The dashboard is built on it: there is
no private API the UI uses and you cannot.
Versioning
Section titled “Versioning”/api/v1 is additive-only. New fields and new routes may appear; existing
ones do not change shape or disappear. A breaking change would ship as
/api/v2, side by side.
Authentication
Section titled “Authentication”Two mechanisms:
| Used by | Sends | |
|---|---|---|
| Session cookie | The dashboard | adminium_session — httpOnly, signed, SameSite=Lax |
| API key | Your scripts and integrations | Authorization: Bearer <key> |
API keys are scoped and revocable. A key acts with one role’s permissions; mint one
with POST /api/v1/api-keys from a signed-in session, as
Endpoints and keys
shows. Issue one per integration, never share one between two, and revoke on rotation.
These are not the keys your pages use: a browser or server key for the public API
(adm_pub_…, adm_srv_…) calls only /api/v1/public/*, and only what it was granted.
curl -H "Authorization: Bearer $ADMINIUM_API_KEY" \ https://admin.example.com/api/v1/system/infoEvery request is authorized against the caller’s role. An API key cannot do what its role cannot do — the RBAC check is the same one the UI goes through.
Health
Section titled “Health”GET /api/v1/healthzReturns JSON with ok. Check the body, not just the status code — bare
/healthz has no route and is answered by the SPA history fallback with a 200,
so a probe there reports healthy even when the meta store is unreachable.
GET /api/v1/readyzReadiness, as opposed to /healthz’s liveness: can this process serve a real
request right now? It reports per-dependency verdicts — most importantly
whether the meta store is reachable — and answers 503 when it is not. Point
load-balancer and orchestrator readiness gates here, not at /healthz. (The
Docker image’s own HEALTHCHECK deliberately probes /api/v1/healthz instead:
restarting the container cannot reconnect a dead meta database, so a database
blip must not become a crash-loop.)
GET /api/v1/system/infoVersion and instance information.
Dates in rows
Section titled “Dates in rows”A date column is a calendar day, and every reply spells it as its text, "2026-08-14", on
every engine. It is never an instant: read it as that day (split the text, or format it at UTC),
because new Date("2026-08-14") is midnight UTC, which is the day before west of Greenwich.
Before 0.3.4 a Postgres or MySQL date came back as the server’s local midnight as an instant
("2026-08-13T22:00:00.000Z" on a server in Berlin). A timestamptz or timestamp column is
unchanged.
The machine-readable spec
Section titled “The machine-readable spec”The full contract is published as OpenAPI 3.1, generated from the route tree
itself — every /api/v1 route declares a Zod schema and the server refuses to
boot without one, so the spec is derived from the code that enforces it rather
than written alongside it. CI fails when the two disagree.
https://docs.adminium.dev/openapi.jsonPoint a client generator, Postman, Insomnia, or an editor’s OpenAPI extension at it. Request and response shapes, query parameters, enums and status codes are all in there; the sections below are the map, not the territory.
Route groups
Section titled “Route groups”Forty-eight namespaces. Counts are operations, not paths.
| Group | Ops | |
|---|---|---|
/api/v1/about/* |
2 | Build version, edition, and the update check |
/api/v1/add-ons/* |
33 | Installed add-ons — list what a host should mount, preview what installing would do, install from a verified package, enable or disable per host, and uninstall |
/api/v1/api-docs |
1 | The public API catalogue behind /api-docs — what live keys can call; 404 while the page is off |
/api/v1/api-keys/* |
3 | Issue, list and revoke API keys |
/api/v1/apps/* |
30 | Micro-SaaS apps installed into this instance — upload a built bundle or download one from the opt-in online catalog, browse what is staged or offered, plan its tables against a connection, install (with the public access it asks for, unless declined), update (giving what a new version adds to its public access only when allowed, and taking back what it drops), rename an older install’s tables to the app’s prefix, change one app’s settings, switch it off and on, set its domains and instances, add and remove its sample data, discard a staged version, and uninstall |
/api/v1/assistant/* |
13 | The page assistant — open a session on a page, ask it something, read what the turn came back with, and act on the draft it proposed. Every route needs the assistant permission; saving what it drafts additionally needs the same permission the page’s own save needs. The assistant reads; nothing it does writes a record on its own. |
/api/v1/audit/* |
2 | The audit log — list and read single entries |
/api/v1/auth/* |
12 | Login, logout, session listing, 2FA enrolment, password change and reset |
/api/v1/automation-runs/* |
3 | Every execution of a rule — the last seven days, the three status filters, one run’s full step-by-step trace, and today’s counters |
/api/v1/automations/* |
9 | Automation rules — the trigger, the steps and the branches between them; the tables, columns, templates and roles a rule can name; the 30-day counters the cards show; and a dry run that walks the flow without executing anything |
/api/v1/bootstrap |
1 | Everything the dashboard needs on first paint, in one call |
/api/v1/branding/* |
4 | Instance name, colours and logo (read is public; writes are admin) |
/api/v1/connections/* |
29 | Databases Adminium is pointed at — CRUD, connection test, introspection, schema snapshots, diffs, overrides, and generation |
/api/v1/data/* |
25 | Rows in your database — list, read, create, update, delete, bulk write, undo, and inbound references |
/api/v1/designer/* |
2 | Adminium Designer on a server people reach — whether it is allowed and switched on, and the switch itself (a Super Admin, with their password). The Designer’s own routes exist only while the server runs it |
/api/v1/documents/* |
14 | Documents drawn from your own records — the register of what was issued, the bytes behind each one, and the mappings that say which columns make which document. A document keeps a frozen copy of what it was drawn from, so editing or deleting the source row never changes an invoice somebody already has. Reading one needs read access to every table its mapping uses; a caller without all of them is told the document exists and not what is in it. |
/api/v1/email-blocks/* |
3 | Reusable email sections saved from the editor — list, save one, delete one |
/api/v1/email-runs |
1 | Campaign sends — cancel a scheduled or running run |
/api/v1/email-templates/* |
17 | Email templates and campaigns — the documents, their language variations, the starters, test sends of the on-screen document, and export/import of a bundle |
/api/v1/events |
1 | Server-sent events — the fallback when a WebSocket cannot be established |
/api/v1/exports/* |
7 | Queued exports of a whole result set, and their downloads |
/api/v1/files/* |
12 | Uploaded files and record attachments — upload, list, download (with Range and ETag), attach to a record, rename, move to trash and restore |
/api/v1/geo/* |
4 | Places and routes, for anybody signed in. Towns found by name from a list that ships with the server (no other site is asked, with maps on or off), one town by its id, the towns of up to 200 addresses at once, and the legs between 2 to 50 stops: straight-line estimates unless routes along roads are switched on in Settings → Maps |
/api/v1/healthz |
1 | Liveness |
/api/v1/i18n/* |
13 | Runtime translations — locales, keys, bundles, import/export, format errors |
/api/v1/imports/* |
7 | CSV/spreadsheet imports — upload, dry run, run, error report |
/api/v1/invoices/* |
10 | Invoice templates and invoices — the documents, their language variations, the starters, duplicates, and building an invoice from a template |
/api/v1/jobs/* |
4 | Background jobs — enqueue, poll, cancel |
/api/v1/ledgers/* |
4 | The rules that hand rows to an add-on’s ledger (stock, gift cards) — which tables post into it, whether each rule runs, how many rows hold something under it and how many saves wait to be worked out; the tables and columns a new rule may be drawn from; making the ledger’s items from a table’s rows; and recording the saves let through while the add-on could not be asked. Reading needs a session and the connection; the two runs need the grant that changes what a table’s columns mean. A rule itself is stored under /connections/:id/tables/:table/postings. |
/api/v1/llm/* |
16 | LLM assist — provider config, runs, prompts, diffs, apply, undo |
/api/v1/me/* |
15 | The signed-in user — profile, preferences, notifications, saved layouts |
/api/v1/meta/* |
2 | Where the meta store lives, and relocating it |
/api/v1/onboarding/* |
2 | The first-run checklist |
/api/v1/option-lists/* |
5 | Named sets of answers a column accepts, written once and pointed at by as many columns as need them. Reading one needs only a session — a create dialog has to render the choices to anyone who may add a row — while writing needs the same grant that points a column at a list. The built-in lists live in code and are served with their labels in the caller’s locale; editing one makes an ordinary copy rather than changing it. Deleting a list a column still names is refused with 409 and the columns using it. |
/api/v1/pages/* |
15 | Pages and dashboards — layout, config, nav order, shared views, and what a template needs from a table (with a new table drafted to fit when none does) |
/api/v1/payments/* |
40 | Studio → Payments: connect a payment service with your own keys, choose the ways to pay and their rules, mark tables payable, set the pay page and make pay links. Setup changes need payments.manage and a signed-in person (never an API key); payments.view reads only. |
/api/v1/people |
1 | Names and ids of staff, for picking somebody (an owner, a hand-over) |
/api/v1/permissions |
1 | The permission catalog every role is built from |
/api/v1/project/* |
9 | A project folder on the server that runs one — which pages and schema customizations differ from the deployed files, settling a page changed on both sides, the changed copies adminium pull --from writes into the project, running the project’s actions, the built files of its own pages and widgets, and what Studio shows about the project |
/api/v1/public/* |
32 | The scoped public API for customer- and staff-facing pages (off by default) |
/api/v1/public-api/* |
3 | Turn the public API on or off, and see whether this instance opted in |
/api/v1/public-endpoints/* |
5 | Build the endpoints a key can be granted — source, columns, filters, methods and limits |
/api/v1/public-keys/* |
5 | Issue, reveal, rotate and revoke the browser-safe keys your pages use |
/api/v1/public-scopes/* |
4 | Define what a public key may read — resources, columns, filters and time zone |
/api/v1/readyz |
1 | Readiness — per-dependency verdicts, 503 when a dependency is down |
/api/v1/report-documents/* |
9 | Report templates and reports — the block documents behind the report builder, the starters, duplicates, and building a report from a template |
/api/v1/roles/* |
8 | RBAC roles, their permission sets, and which rows each reaches |
/api/v1/scheduled-reports/* |
4 | Recurring exports delivered on a schedule |
/api/v1/schema-import |
1 | Parse a schema file (SQL, Prisma, Drizzle, the JSON IR, …) into the IR |
/api/v1/search |
1 | Cross-resource search for the command palette |
/api/v1/settings/* |
12 | Instance settings — defaults, branding, email, security, telemetry, workspace |
/api/v1/setup/* |
4 | First-boot super-admin creation, whether setup is still open, and — in that same window — checking a database for an Adminium store already in it and adopting that store |
/api/v1/storage/* |
8 | Where uploaded and generated files are stored — configure destinations (this server’s disk, an S3-compatible bucket, a WebDAV server), test one, choose the default, and move existing files between them |
/api/v1/surfaces/* |
6 | Hosted app surfaces — placement in the dashboard, and attaching your own domains |
/api/v1/system |
1 | Version and instance information |
/api/v1/teams/* |
6 | Teams and who leads them: a lead reaches the rows of the people in the team |
/api/v1/users/* |
11 | People in the workspace — invite, suspend, delete, assign roles |
/api/v1/widget-data/* |
3 | The queries widgets run, singly and in batches |
/api/v1/words |
1 | What staff are told of the rows a screen shows, in an add-on’s stock words — in, low or out for up to sixty rows of a table named as it is stored, and for a caller who reads the add-on’s stock tables the figure behind the word: the exact count, the batch and its expiry, the line that runs out first, and whether stock or the day’s portions bind |
Three of these deserve a note, because the obvious guess is wrong:
- There is no
/api/v1/schema/*. Snapshots, diffs and overrides are nested under the connection they belong to:/connections/:id/schema/*. - There is no
/api/v1/generate/*. Generation is an action on a connection:POST /connections/:id/generate. - There is no
/api/v1/views/*. Shared views belong to a page (/pages/:pageId/views); per-user saved layouts belong to you (/me/views/:pageId/layout).
Every operation
Section titled “Every operation”The complete surface, straight from the spec. Path parameters appear as
{name}.
/about
Section titled “/about”GET /api/v1/aboutGET /api/v1/about/update-check/add-ons
Section titled “/add-ons”GET /api/v1/add-ons/{key}/adjustsPOST /api/v1/add-ons/{key}/look-upPOST /api/v1/add-ons/{key}/codes/makeGET /api/v1/add-ons/{key}/kitPOST /api/v1/add-ons/{key}/documents/renderGET /api/v1/add-ons/catalogPUT /api/v1/add-ons/catalogPOST /api/v1/add-ons/catalog/refreshPOST /api/v1/add-ons/downloadPOST /api/v1/add-ons/uploadDELETE /api/v1/add-ons/staged/{key}/{version}POST /api/v1/add-ons/{key}/upgradePOST /api/v1/add-ons/{key}/update/planPOST /api/v1/add-ons/{key}/updateGET /api/v1/add-onsPOST /api/v1/add-onsGET /api/v1/add-ons/{key}/planPOST /api/v1/add-ons/planPOST /api/v1/add-ons/{key}/attachmentsGET /api/v1/add-ons/{key}/bundle/{*}GET /api/v1/add-ons/{key}/pages/{ref}/bundlePOST /api/v1/add-ons/{key}/connectDELETE /api/v1/add-ons/{key}/connectPOST /api/v1/add-ons/{key}/connect/oauth/startPOST /api/v1/add-ons/{key}/connect/oauth/completePATCH /api/v1/add-ons/{key}DELETE /api/v1/add-ons/{key}PUT /api/v1/add-ons/{key}/settingsGET /api/v1/add-ons/{key}/sample-dataPOST /api/v1/add-ons/{key}/sample-dataPOST /api/v1/add-ons/{key}/sample-data/remove-planPOST /api/v1/add-ons/{key}/sample-data/removeGET /api/v1/add-ons/{key}/uninstall-plan/api-docs
Section titled “/api-docs”GET /api/v1/api-docs/api-keys
Section titled “/api-keys”GET /api/v1/api-keysPOST /api/v1/api-keysDELETE /api/v1/api-keys/{id}POST /api/v1/apps/{key}/documents/renderGET /api/v1/appsPOST /api/v1/apps/uploadGET /api/v1/apps/catalogPUT /api/v1/apps/catalogPOST /api/v1/apps/catalog/refreshPOST /api/v1/apps/downloadPOST /api/v1/apps/planPOST /api/v1/apps/installPOST /api/v1/apps/{key}/updateGET /api/v1/apps/{key}/sample-dataPOST /api/v1/apps/{key}/sample-dataPOST /api/v1/apps/{key}/sample-data/remove-planPOST /api/v1/apps/{key}/sample-data/removeGET /api/v1/apps/{key}/uninstall-planGET /api/v1/apps/{key}/settingsPATCH /api/v1/apps/{key}/settingsGET /api/v1/apps/{key}/overviewPOST /api/v1/apps/{key}/disablePOST /api/v1/apps/{key}/enableGET /api/v1/apps/{key}/partsGET /api/v1/apps/{key}/parts/{part}/openGET /api/v1/apps/{key}/rulesPUT /api/v1/apps/{key}/parts/{part}PUT /api/v1/apps/{key}/domainsPUT /api/v1/apps/{key}/instancesPOST /api/v1/apps/{key}/rename-tables/planPOST /api/v1/apps/{key}/rename-tablesDELETE /api/v1/apps/staged/{key}/{version}DELETE /api/v1/apps/{key}/assistant
Section titled “/assistant”GET /api/v1/assistant/availabilityPUT /api/v1/assistant/voice/minePOST /api/v1/assistant/transcribePOST /api/v1/assistant/sessionsGET /api/v1/assistant/sessions/currentPOST /api/v1/assistant/factsPOST /api/v1/assistant/sessions/{id}/turnsGET /api/v1/assistant/sessions/{id}/turns/{turnId}POST /api/v1/assistant/sessions/{id}/turns/{turnId}/cancelPOST /api/v1/assistant/sessions/{id}/turns/{turnId}/actionsGET /api/v1/assistant/settingsPUT /api/v1/assistant/settingsPOST /api/v1/assistant/sessions/{id}/close/audit
Section titled “/audit”GET /api/v1/auditGET /api/v1/audit/{id}POST /api/v1/auth/loginPOST /api/v1/auth/2fa/verifyPOST /api/v1/auth/logoutGET /api/v1/auth/sessionGET /api/v1/auth/sessionsDELETE /api/v1/auth/sessions/{id}POST /api/v1/auth/password/changePOST /api/v1/auth/password/forgotPOST /api/v1/auth/password/resetPOST /api/v1/auth/2fa/enrollPOST /api/v1/auth/2fa/activatePOST /api/v1/auth/2fa/disable/automation-runs
Section titled “/automation-runs”GET /api/v1/automation-runs/statsGET /api/v1/automation-runsGET /api/v1/automation-runs/{id}/automations
Section titled “/automations”GET /api/v1/automationsPOST /api/v1/automationsGET /api/v1/automations/sourcesGET /api/v1/automations/statsGET /api/v1/automations/{id}PATCH /api/v1/automations/{id}DELETE /api/v1/automations/{id}POST /api/v1/automations/{id}/duplicatePOST /api/v1/automations/{id}/test/bootstrap
Section titled “/bootstrap”GET /api/v1/bootstrap/branding
Section titled “/branding”GET /api/v1/brandingGET /api/v1/branding/logoPOST /api/v1/branding/logoDELETE /api/v1/branding/logo/connections
Section titled “/connections”GET /api/v1/connectionsPOST /api/v1/connectionsPOST /api/v1/connections/testGET /api/v1/connections/{id}PATCH /api/v1/connections/{id}DELETE /api/v1/connections/{id}POST /api/v1/connections/{id}/testPOST /api/v1/connections/{id}/introspectGET /api/v1/connections/{id}/schemaGET /api/v1/connections/{id}/schema/snapshotsGET /api/v1/connections/{id}/schema/snapshots/{snapshotId}GET /api/v1/connections/{id}/schema/diffGET /api/v1/connections/{id}/schema/overridesPUT /api/v1/connections/{id}/schema/overridesGET /api/v1/connections/{id}/overridesPUT /api/v1/connections/{id}/overridesPUT /api/v1/connections/{id}/tables/{table}/postings/{posting}DELETE /api/v1/connections/{id}/tables/{table}/postings/{posting}PATCH /api/v1/connections/{id}/tables/{table}/postings/{posting}/switchPUT /api/v1/connections/{id}/tables/{table}/adjustDELETE /api/v1/connections/{id}/tables/{table}/adjustPATCH /api/v1/connections/{id}/tables/{table}/adjust/switchPOST /api/v1/connections/{id}/schema/planPOST /api/v1/connections/{id}/schema/applyPOST /api/v1/connections/{id}/schema/adoptGET /api/v1/connections/{id}/schema/changesPUT /api/v1/connections/{id}/diagram-layoutPOST /api/v1/connections/{id}/generateGET /api/v1/connections/{id}/shape-rulesGET /api/v1/data/{connectionId}/{table}POST /api/v1/data/{connectionId}/{table}POST /api/v1/data/undo/{token}POST /api/v1/data/{connectionId}/{table}/one-by-onePOST /api/v1/data/{connectionId}/{table}/bulkGET /api/v1/data/{connectionId}/{table}/{recordId}/referencesGET /api/v1/data/{connectionId}/{table}/{recordId}/historyGET /api/v1/data/{connectionId}/{table}/{recordId}/removed/{childTable}GET /api/v1/data/{connectionId}/{table}/{recordId}/removed/{childTable}/{childId}/historyPOST /api/v1/data/{connectionId}/{table}/personGET /api/v1/data/{connectionId}/{table}/{recordId}/claim-lockDELETE /api/v1/data/{connectionId}/{table}/{recordId}/claim-lockPOST /api/v1/data/{connectionId}/{table}/{recordId}/regenerate-codeGET /api/v1/data/{connectionId}/{table}/{recordId}PATCH /api/v1/data/{connectionId}/{table}/{recordId}DELETE /api/v1/data/{connectionId}/{table}/{recordId}GET /api/v1/data/{connectionId}/{table}/availabilityGET /api/v1/data/{connectionId}/{table}/booking-slotsGET /api/v1/data/{connectionId}/{table}/capacity-countsGET /api/v1/data/{connectionId}/{table}/{recordId}/nightlyGET /api/v1/data/{connectionId}/{table}/{recordId}/links/{relationId}POST /api/v1/data/{connectionId}/{table}/dry-runPOST /api/v1/data/{connectionId}/{table}/{recordId}/dry-runPOST /api/v1/data/{connectionId}/{table}/tryPOST /api/v1/data/{connectionId}/{table}/{recordId}/actions/{actionId}/designer
Section titled “/designer”GET /api/v1/designer/livePUT /api/v1/designer/live/documents
Section titled “/documents”GET /api/v1/documents/kindsGET /api/v1/documents/providersGET /api/v1/documents/profilesPOST /api/v1/documents/profilesPUT /api/v1/documents/profiles/{id}DELETE /api/v1/documents/profiles/{id}GET /api/v1/documentsGET /api/v1/documents/{id}GET /api/v1/documents/{id}/contentGET /api/v1/documents/{id}/printPOST /api/v1/documents/renderGET /api/v1/documents/print-once/{ticket}POST /api/v1/documents/{id}/voidPOST /api/v1/documents/{id}/send/email-blocks
Section titled “/email-blocks”GET /api/v1/email-blocksPOST /api/v1/email-blocksDELETE /api/v1/email-blocks/{id}/email-runs
Section titled “/email-runs”POST /api/v1/email-runs/{id}/cancel/email-templates
Section titled “/email-templates”GET /api/v1/email-templatesPOST /api/v1/email-templatesGET /api/v1/email-templates/startersGET /api/v1/email-templates/exportGET /api/v1/email-templates/{key}/{locale}GET /api/v1/email-templates/{id}PUT /api/v1/email-templates/{id}PATCH /api/v1/email-templates/{id}DELETE /api/v1/email-templates/{id}POST /api/v1/email-templates/{id}/duplicatePOST /api/v1/email-templates/{id}/languagesPOST /api/v1/email-templates/{id}/from-templatePOST /api/v1/email-templates/{id}/test-sendPOST /api/v1/email-templates/{id}/audience/previewPOST /api/v1/email-templates/{id}/sendGET /api/v1/email-templates/{id}/runsPOST /api/v1/email-templates/import/events
Section titled “/events”GET /api/v1/events/exports
Section titled “/exports”GET /api/v1/exports/sourcesGET /api/v1/exports/viewsPOST /api/v1/exports/previewGET /api/v1/exportsPOST /api/v1/exportsGET /api/v1/exports/{id}GET /api/v1/exports/{id}/download/files
Section titled “/files”GET /api/v1/filesPOST /api/v1/filesPOST /api/v1/files/{id}/copyGET /api/v1/files/usageGET /api/v1/files/{id}PATCH /api/v1/files/{id}DELETE /api/v1/files/{id}POST /api/v1/files/resolveGET /api/v1/files/{id}/contentPOST /api/v1/files/{id}/attachPOST /api/v1/files/{id}/detachPOST /api/v1/files/{id}/restoreGET /api/v1/geo/placesGET /api/v1/geo/places/{id}POST /api/v1/geo/places/matchPOST /api/v1/geo/route/healthz
Section titled “/healthz”GET /api/v1/healthzGET /api/v1/i18n/manifestGET /api/v1/i18n/bundle/{locale}/{namespace}GET /api/v1/i18n/format-errorsGET /api/v1/i18n/keysPUT /api/v1/i18n/keysDELETE /api/v1/i18n/keysPOST /api/v1/i18n/keys/bulkGET /api/v1/i18n/export/{locale}POST /api/v1/i18n/import/{locale}GET /api/v1/i18n/localesPOST /api/v1/i18n/localesPATCH /api/v1/i18n/locales/{locale}DELETE /api/v1/i18n/locales/{locale}/imports
Section titled “/imports”POST /api/v1/imports/uploadGET /api/v1/importsPOST /api/v1/importsPOST /api/v1/imports/{id}/runGET /api/v1/imports/{id}/previewGET /api/v1/imports/{id}GET /api/v1/imports/{id}/error-report/invoices
Section titled “/invoices”GET /api/v1/invoicesPOST /api/v1/invoicesGET /api/v1/invoices/startersGET /api/v1/invoices/{id}PUT /api/v1/invoices/{id}PATCH /api/v1/invoices/{id}DELETE /api/v1/invoices/{id}POST /api/v1/invoices/{id}/duplicatePOST /api/v1/invoices/{id}/languagesPOST /api/v1/invoices/{id}/from-templateGET /api/v1/jobsPOST /api/v1/jobsGET /api/v1/jobs/{id}POST /api/v1/jobs/{id}/cancel/ledgers
Section titled “/ledgers”GET /api/v1/ledgers/{addOn}/{ledger}/postingsGET /api/v1/ledgers/{addOn}/{ledger}/sourcesPOST /api/v1/ledgers/{addOn}/{ledger}/make-itemsPOST /api/v1/ledgers/{addOn}/{ledger}/catch-upGET /api/v1/llm/configPUT /api/v1/llm/configGET /api/v1/llm/connectionsGET /api/v1/llm/connections/{id}/modelsPOST /api/v1/llm/config/testPOST /api/v1/llm/config/assistant-testGET /api/v1/llm/modelsGET /api/v1/llm/runsPOST /api/v1/llm/runsPOST /api/v1/llm/runs/{id}/executePOST /api/v1/llm/runs/{id}/responseGET /api/v1/llm/runs/{id}GET /api/v1/llm/runs/{id}/promptGET /api/v1/llm/runs/{id}/diffPOST /api/v1/llm/runs/{id}/applyPOST /api/v1/llm/runs/{id}/undo/{token}GET /api/v1/mePATCH /api/v1/meGET /api/v1/me/prefsPATCH /api/v1/me/prefsGET /api/v1/me/notificationsPOST /api/v1/me/notifications/{id}/readPOST /api/v1/me/notifications/read-allGET /api/v1/me/notification-prefsPUT /api/v1/me/notification-prefsGET /api/v1/me/notification-schedulePUT /api/v1/me/notification-scheduleGET /api/v1/me/summariesPOST /api/v1/me/summaries/{ruleId}/sendPUT /api/v1/me/views/{pageId}/layoutDELETE /api/v1/me/views/{pageId}/layoutGET /api/v1/meta/placementPOST /api/v1/meta/relocate/onboarding
Section titled “/onboarding”GET /api/v1/onboardingPOST /api/v1/onboarding/dismiss/option-lists
Section titled “/option-lists”GET /api/v1/option-listsPOST /api/v1/option-listsGET /api/v1/option-lists/{key}PATCH /api/v1/option-lists/{key}DELETE /api/v1/option-lists/{key}/pages
Section titled “/pages”GET /api/v1/pages/{pageId}PATCH /api/v1/pages/{pageId}DELETE /api/v1/pages/{pageId}PATCH /api/v1/pages/{pageId}/layoutGET /api/v1/pagesPOST /api/v1/pagesGET /api/v1/pages/fitGET /api/v1/pages/fit/new-tablePUT /api/v1/pages/nav-orderPATCH /api/v1/pages/{pageId}/configPOST /api/v1/pages/{pageId}/duplicateGET /api/v1/pages/{pageId}/viewsPOST /api/v1/pages/{pageId}/viewsPATCH /api/v1/pages/{pageId}/views/{viewId}DELETE /api/v1/pages/{pageId}/views/{viewId}/payments
Section titled “/payments”GET /api/v1/payments/setupPOST /api/v1/payments/alarms/{id}/dismissPUT /api/v1/payments/services/{key}/modes/{mode}/fields/{field}DELETE /api/v1/payments/services/{key}/modes/{mode}/fields/{field}POST /api/v1/payments/services/{key}/modes/{mode}/checkPUT /api/v1/payments/services/{key}/pathPUT /api/v1/payments/services/{key}/stoppedPUT /api/v1/payments/services/{key}/livePOST /api/v1/payments/services/{key}/disconnectGET /api/v1/payments/attempts/{id}/refundPOST /api/v1/payments/refundsGET /api/v1/payments/activityGET /api/v1/payments/activity/countGET /api/v1/payments/activity.csvPOST /api/v1/payments/activity/attempts/{id}/checkPOST /api/v1/payments/activity/refunds/{id}/checkGET /api/v1/payments/services/{key}/key-appPOST /api/v1/payments/links/{id}/emailPOST /api/v1/payments/test-paymentsGET /api/v1/payments/test-payments/{rowKey}POST /api/v1/payments/panelPOST /api/v1/payments/record-paymentGET /api/v1/payments/payablesPOST /api/v1/payments/payablesPOST /api/v1/payments/payables/checkPATCH /api/v1/payments/payables/{id}DELETE /api/v1/payments/payables/{id}GET /api/v1/payments/waysPUT /api/v1/payments/ways/{source}/{method}DELETE /api/v1/payments/ways/{source}/{method}POST /api/v1/payments/ways/previewGET /api/v1/payments/services/{source}/methodsPOST /api/v1/payments/pay-page/logoGET /api/v1/payments/pay-pagePUT /api/v1/payments/pay-pageGET /api/v1/payments/pay-addressPUT /api/v1/payments/pay-addressPOST /api/v1/payments/linksPOST /api/v1/payments/links/currentPOST /api/v1/payments/links/{id}/revoke/people
Section titled “/people”GET /api/v1/people/permissions
Section titled “/permissions”GET /api/v1/permissions/catalog/project
Section titled “/project”GET /api/v1/project/statusPOST /api/v1/project/resolveGET /api/v1/project/exportGET /api/v1/project/actionsPOST /api/v1/project/actions/{id}GET /api/v1/project/overviewGET /api/v1/project/client/{*}GET /api/v1/project/apps/{key}/removalsPOST /api/v1/project/apps/{key}/removals/public
Section titled “/public”GET /api/v1/public/configGET /api/v1/public/records/{ref}POST /api/v1/public/records/{ref}GET /api/v1/public/availability/{ref}GET /api/v1/public/records/{ref}/{id}PUT /api/v1/public/records/{ref}/{id}PATCH /api/v1/public/records/{ref}/{id}DELETE /api/v1/public/records/{ref}/{id}POST /api/v1/public/records/{ref}/dry-runPOST /api/v1/public/records/{ref}/{id}/dry-runPOST /api/v1/public/records/{ref}/batchPOST /api/v1/public/claimPOST /api/v1/public/claim/codePOST /api/v1/public/claim/verifyPOST /api/v1/public/claim/tokenPOST /api/v1/public/claim/linkPOST /api/v1/public/claim/link/resendPOST /api/v1/public/claim/link/peekPOST /api/v1/public/claim/link/verifyGET /api/v1/public/files/{ref}/{rowId}/{column}GET /api/v1/public/pictures/{keyId}/{ref}/{rowId}/{column}/{fileId}GET /api/v1/public/add-ons/{key}/settingsGET /api/v1/public/challengePOST /api/v1/public/documents/renderGET /api/v1/public/documentsGET /api/v1/public/documents/{id}GET /api/v1/public/documents/{id}/contentPOST /api/v1/public/documents/{id}/emailPOST /api/v1/public/session/revoke-allDELETE /api/v1/public/accountPOST /api/v1/public/records/{ref}/{id}/new-linkDELETE /api/v1/public/session/public-api
Section titled “/public-api”GET /api/v1/public-apiPUT /api/v1/public-apiGET /api/v1/public-api/stats/public-endpoints
Section titled “/public-endpoints”GET /api/v1/public-endpointsPOST /api/v1/public-endpoints/checkPUT /api/v1/public-endpoints/{connectionId}/{ref}DELETE /api/v1/public-endpoints/{connectionId}/{ref}POST /api/v1/public-endpoints/{connectionId}/{ref}/rename/public-keys
Section titled “/public-keys”GET /api/v1/public-keysPOST /api/v1/public-keysGET /api/v1/public-keys/{id}/revealPOST /api/v1/public-keys/{id}/rotateDELETE /api/v1/public-keys/{id}/public-scopes
Section titled “/public-scopes”GET /api/v1/public-scopesPOST /api/v1/public-scopesPATCH /api/v1/public-scopes/{id}DELETE /api/v1/public-scopes/{id}/readyz
Section titled “/readyz”GET /api/v1/readyz/report-documents
Section titled “/report-documents”GET /api/v1/report-documentsPOST /api/v1/report-documentsGET /api/v1/report-documents/startersGET /api/v1/report-documents/{id}PUT /api/v1/report-documents/{id}PATCH /api/v1/report-documents/{id}DELETE /api/v1/report-documents/{id}POST /api/v1/report-documents/{id}/duplicatePOST /api/v1/report-documents/{id}/from-template/roles
Section titled “/roles”GET /api/v1/rolesPOST /api/v1/rolesPATCH /api/v1/roles/{id}DELETE /api/v1/roles/{id}GET /api/v1/roles/{id}/permissionsPUT /api/v1/roles/{id}/permissionsPOST /api/v1/roles/rows/previewGET /api/v1/roles/{id}/rows/scheduled-reports
Section titled “/scheduled-reports”GET /api/v1/scheduled-reportsPOST /api/v1/scheduled-reportsPATCH /api/v1/scheduled-reports/{id}DELETE /api/v1/scheduled-reports/{id}/schema-import
Section titled “/schema-import”POST /api/v1/schema-import/parse/search
Section titled “/search”GET /api/v1/search/settings
Section titled “/settings”GET /api/v1/settings/defaultsPUT /api/v1/settings/defaultsGET /api/v1/settings/workspacePUT /api/v1/settings/brandingGET /api/v1/settings/securityPUT /api/v1/settings/securityGET /api/v1/settings/telemetryPUT /api/v1/settings/telemetryGET /api/v1/settings/mapsPUT /api/v1/settings/mapsGET /api/v1/settings/emailPUT /api/v1/settings/email/setup
Section titled “/setup”GET /api/v1/setup/statePOST /api/v1/setup/super-adminPOST /api/v1/setup/probePOST /api/v1/setup/adopt/storage
Section titled “/storage”GET /api/v1/storage/destinationsPOST /api/v1/storage/destinationsPATCH /api/v1/storage/destinations/{id}DELETE /api/v1/storage/destinations/{id}POST /api/v1/storage/destinations/{id}/testPOST /api/v1/storage/destinations/testPOST /api/v1/storage/destinations/{id}/defaultPOST /api/v1/storage/migrate/surfaces
Section titled “/surfaces”GET /api/v1/surfacesPUT /api/v1/surfaces/{appKey}/placementPUT /api/v1/surfaces/{appKey}/namePUT /api/v1/surfaces/{appKey}/connectionPUT /api/v1/surfaces/instancesPUT /api/v1/surfaces/domains/system
Section titled “/system”GET /api/v1/system/info/teams
Section titled “/teams”GET /api/v1/teamsPOST /api/v1/teamsPATCH /api/v1/teams/{id}DELETE /api/v1/teams/{id}PUT /api/v1/teams/{id}/members/{userId}DELETE /api/v1/teams/{id}/members/{userId}/users
Section titled “/users”POST /api/v1/users/{id}/rolesPUT /api/v1/users/{id}/rolesDELETE /api/v1/users/{id}/roles/{roleId}GET /api/v1/users/{id}/rowsPOST /api/v1/users/{id}/rows/hand-overGET /api/v1/usersPOST /api/v1/usersGET /api/v1/users/{id}PATCH /api/v1/users/{id}DELETE /api/v1/users/{id}POST /api/v1/users/{id}/invite/resend/widget-data
Section titled “/widget-data”POST /api/v1/widget-data/queryPOST /api/v1/widget-data/link-filtersPOST /api/v1/widget-data/batch/words
Section titled “/words”GET /api/v1/words/{addOn}/{wordsId}Records with their rows, quotes and retries
Section titled “Records with their rows, quotes and retries”The staff side of the API, under /api/v1/data/{connectionId}/{table}, answers a session cookie
or an API key. What a desk needs to take a booking or an order in one save is below: a record with
its child rows, a quote before the save, a check of the price shown, a retry that finds its first
save, and the counts and nights a desk screen reads. Refusals use the envelope and codes in
Errors. The rules an app declares for these are in the manifest reference,
under Dry runs, price checks and retries.
A record with its child rows
Section titled “A record with its child rows”POST /api/v1/data/{connectionId}/{table} takes children beside values: the rows below the
record, keyed by relation id (model.relations[].id in GET /api/v1/connections/{id}/schema), or
by the short name the app that made the child table gives it (quote_lines, or crm:quote_lines
for another manifest’s) when that table has exactly one link to the record’s. A short name is
turned into its relation before anything is judged, so its rows meet every grant, limit
(creatable, writable, the rows a role reaches) and rule (linkable) a list named by its
relation meets; a short name for a table with no link, or two, is refused as a relation that is not
there, and two names for one list are refused. The same holds for a row’s own children, against
its table, and on PATCH and the dry runs (Adminium 0.3.35). Each
row is { key?, values, children? }, two levels at most and up to 1,000 rows per list, and 1,000
below one create in all (the public API’s creates take 200). A row’s own
children hold { values } rows only. Every row is written in one transaction, or none; totals
are settled from the bottom up, and the reply is the record as stored.
{ "values": { "customer_name": "Ada Park", "pickup_at": "2026-10-02T18:30:00Z" }, "children": { "<order lines relation id>": [ { "values": { "menu_item_id": 12, "qty": 2 }, "children": { "<line options relation id>": [{ "values": { "option_id": 4 } }] } } ] }}{ "data": { "id": 981, "customer_name": "Ada Park", "total": "31.50" }, "undoToken": "…" }The rows are held to what the app’s public create entries on the table declare: a row’s values
agree with its parent’s (no more guests than the room sleeps), and each list stays within its least
and most. A refusal about one row names it in details: relation and row, and under for a
row one level further down. Rows below a child row are refused 422 when a table of the write
runs a before hook. On PATCH, a row with a key is changed, a row without one is added, a
row the list leaves out is removed, and [] empties the list; rows below a child row come with a
new record only.
When the write happened
Section titled “When the write happened”occurredAt on a POST or a PATCH is the time the thing really happened, as a staff device
says: a door scan made offline and sent later. It is an ISO instant with an offset, at most six
hours in the past and at most 60 seconds ahead (a time ahead is taken as now). A signed-in user or
an API key may send it. Anything else is refused 422 VALIDATION_FAILED with
fields.occurredAt (out-of-range). The write judges its own rules by that time, so a stamp
says when the scan was made and a window open then lets it through. Limits shared with other
writers still count on the server’s clock. The audit row keeps the time sent as
changes.occurredAt, beside the time it arrived.
{ "values": { "status": "admitted" }, "occurredAt": "2026-10-02T19:04:12+02:00" }Quotes of a create and a change
Section titled “Quotes of a create and a change”POST …/{table}/dry-run tries a create with its child rows. POST …/{table}/{recordId}/dry-run
tries a change of one record. Each works out every figure the save would, refuses what the save
would refuse, and keeps nothing: the write is rolled back, nothing is announced, no number from a
series is taken, and no code the save would make is shown. A create quote needs the table’s create
permission. A change quote needs update.
The create quote takes the create’s values and children. The change quote takes values,
children (one level) and from. Its own places are left out of the limits it is judged
against, so a full house still quotes a stay’s own dates. It takes no named lock; like any
update, it holds its own row until it rolls back.
{ "values": { "depart": "2026-08-08" } }{ "data": { "id": 412, "depart": "2026-08-08", "room_total": "875.00", "total": "953.75" }, "children": {}, "nights": [ { "date": "2026-08-03", "rate": "170.00", "base": "150.00", "tags": ["August"] }, { "date": "2026-08-07", "rate": "195.00", "base": "150.00", "tags": ["Weekend", "August"] } ]}children holds, per relation the request sent, each row as the save would leave it:
{ "<relation id>": [{ "data": {…} }] }. A create quote’s rows carry their own children one
level down. nights is there only for a row
priced by the night: each night’s rate, the base
before what was added, and the tags naming what was added. It is left out for a caller who
cannot read the priced column or a column the price rule reads (its dates, its rates). The change
quote also refuses 404 for a record that is not there, 422 for rows below a child row, and
422 naming the relation for a list whose table runs a before hook, because a quote runs none.
Checking the price shown
Section titled “Checking the price shown”expect: { total, column? } on a create or a PATCH saves only at the price the desk showed.
total is a decimal string. The figure is compared on the record as the save leaves it, totals
settled, inside the transaction. A different figure refuses the save 409 PRICE_CHANGED,
with details.column and details.total (the figure it would have saved), and nothing is kept.
{ "values": { "depart": "2026-08-06" }, "expect": { "total": "495.95" } }Without column, the figure is the one the app’s public create or change entries on the table
check. column may name another money column. The column must be one the caller may read (403
otherwise) and must hold a number: a text column, an unknown one, or no column at all is refused
422 with fields.expect. A price check is also refused 422 with repeat, and on a create
whose child or link table runs a before hook.
A retry that finds its first save
Section titled “A retry that finds its first save”clientKey on a create is a key the form mints for this save, 22 to 64 letters, digits, - or
_. Sent again after a reply that never came, it answers the record the first save made, with
200 and no undo, instead of making a second one. The values of the retry are not applied. Two
sent at once make one record.
{ "data": { "id": 981, "first_name": "Zoe" }, "undoToken": null, "replayed": true }The table must keep a retry key: the column the app’s public create entries keep a guest’s key
in, or, on a table no guest creates rows of (a desk’s payments), a column the app marks
retryKey. The key is stored there as a keyed hash, never as sent, and per person: another user’s same
key makes another record, and a guest’s key never finds a desk’s. A key of the wrong form, a
table that keeps none, or a key sent with repeat is refused 422 with fields.clientKey.
A table whose rows take a resource for a time (a booking, a
cover) takes a keyed create of one row like any other: the row is placed under the rule’s lock,
and a retry answers it. The key is no way round the rule: a second key for a time that is taken
is refused 409 BOOKING_TAKEN, as a create with no key is. With children such a create is
refused 409 (details.reason: "CAPACITY_ONE_AT_A_TIME"): those rows are written one at a time.
The state a form loaded, and undo
Section titled “The state a form loaded, and undo”On a table with states, from on a PATCH names the state the
form loaded the row in. The change is made only while the row is still there. A row another screen
moved on since is refused 409 STATE_MOVE_REFUSED, with details.column, from (where the
row is now), to and named. from on a table without states is refused 422 with
fields.from.
{ "values": { "status": "preparing" }, "from": "ready" }A bulk update (POST …/{table}/bulk) takes from too: every row must still be in that state, or
the whole write is refused 409 STATE_MOVE_REFUSED, naming the row by details.id, and nothing
is written. A show’s live orders are cancelled in a few writes this way, one per state they were
seen in, rather than one request per order.
The values a form loaded
Section titled “The values a form loaded”seen on a PATCH names up to 8 plain columns as the form loaded them. The change is made only
while each still holds that value: two door phones ticking one party’s arrivals, or two people
editing one note, cannot both write over what they did not see. A row that moved is refused 409
ROW_CHANGED, with details.column, expected and retry: true; read the row again and decide.
A column the caller may not read is refused as it would be in values.
{ "values": { "arrived": 3 }, "seen": { "arrived": 2 } }A change of a row that moves through states usually gets undoToken: null: an undo would put the
state back with no rules. The exception is a change that was a status move alone (with the stamps
the move wrote), on a table whose states list a move marked undo the other way. Its token is
given, and POST /api/v1/data/undo/{token} makes that listed move, naming the state the change
left, so it is judged, stamped and announced like any move. A move marked undo is made only
by a write that names from; without it, it is refused 409 STATE_MOVE_REFUSED with
details.undo: true. See Undo of a move.
A limit’s counts
Section titled “A limit’s counts”GET …/{table}/capacity-counts answers one of the table’s
limits as a desk sees it: each pool’s size, what is taken, what
holds keep, and what is left. It is counted as the write path counts, with no lock, and with none
of a guest’s filters: no notice, pause or sales window hides a pool. A pool made smaller after it
was sold can show less than nothing left.
| Parameter | Rule |
|---|---|
rule |
Which limit, 0 to 2. Default 0. |
date |
One day, YYYY-MM-DD: a slot limit’s day, or a parent limit that counts by day. |
from, days |
A strip: up to 31 days for a slot limit, up to 62 nights for a night limit. |
under, value |
A parent limit: the pools’ rows whose column under holds value (an event’s ticket types). |
values |
With under: up to 50 values, comma-separated (a page of events), in one ask. Each row then says the value it is under (under). |
ids |
A parent or night limit: pool row ids, comma-separated. Up to 500 pool rows are counted in one ask. |
A slot limit takes date or from and days. A parent limit takes ids or under and value,
with date when it counts by day. A night limit takes from and days, and every pool when
ids is absent. A form its kind does not take, or a rule the table does not have, is refused
422 VALIDATION_FAILED; a table with no limit is 404.
GET /api/v1/data/{connectionId}/{table}/capacity-counts?under=event_id&value=42{ "data": { "kind": "parent", "rows": [{ "id": "7", "size": 200, "taken": 143, "held": 6, "left": 51, "also": [] }] }}A slot limit’s rows are { time, size, taken, held } for one day (paused or closed when so),
or { date, size, taken, held } per day of a strip. A parent limit’s rows carry kept when it
keeps places back for a waitlist, and also, the wider pools the row takes from. A night limit’s
rows are { pool, date, size, outOfService, taken, held, left } per pool and night, where
size already leaves out the rooms out of service. It needs the table’s read grant, read access
to the pools’ tables (403 TABLE_FORBIDDEN), and the rule’s columns and any under column
readable unmasked (403).
A row’s nights
Section titled “A row’s nights”GET …/{table}/{recordId}/nightly spells out a row priced by the night: each night with its
rate, priced from the rates as they are now. column may name the priced column, and only that
one.
{ "data": { "column": "room_total", "nights": [ { "date": "2026-08-03", "rate": "170.00", "base": "150.00", "tags": ["August"], "qty": "1", "amount": "170.00" }, { "date": "2026-08-04", "rate": "170.00", "base": "150.00", "tags": ["August"], "qty": "1", "amount": "170.00" } ], "total": "340.00", "stale": false }}When the rates changed after the row was priced, the nights no longer add up to the stored
figure. The reply is then stale: true with one line for them all: the first night’s date, the
number of nights as qty, no rate, and the stored figure as amount. A table with no price by the
night is 404. A caller who may not read the priced column or a column the price rule reads is
refused 403 COLUMN_FORBIDDEN.
The public API for app pages
Section titled “The public API for app pages”The routes under /api/v1/public/* answer a public key (adm_pub_…) and, for a guest who has
found or signed in as themselves, a session sent in x-adminium-public-session. Errors here are
{ error: { code, params?, message } }, and the code is the contract. What each entry allows is
declared in the app’s public access and listed by
GET /public/config. The client library’s method is named where there is one.
A create with its rows
Section titled “A create with its rows”POST /public/records/{ref} takes children beside values: the rows below the new one, by the
names refs[ref].children lists in the config, two levels at most and 200 rows per list. It
writes every row or none (createTree in the client).
{ "values": { "name": "Ada Park", "email": "ada@example.com", "client_key": "V1StGXR8_Z5jdHi6B-myT3Kq9wLp0aZxQ" }, "children": { "lines": [{ "values": { "item_id": 12, "qty": 2 }, "children": { "options": [{ "values": { "option_id": 4 } }] } }] }, "expect": { "total": "31.50" }}{ "data": { "id": 981, "total": "31.50" }, "children": { "lines": [{ "data": { "item_id": 12, "qty": 2 }, "children": { "options": [{ "data": { "option_id": 4 } }] } }] }, "rank": 3, "link": { "key": "link", "token": "…", "session": "adm_pubs_…", "expiresAt": 1790000000000 }}expectis{ total }, the price the guest was shown. A different figure is refused409PUBLIC_PRICE_CHANGED, withparams.totalandparams.lines, and nothing is written. On an entry that checks no price it is refused400PUBLIC_WRITE_REFUSED.PATCHtakes it too.- The retry key travels in
values, in the column the entry names as itsclientKey: 22 to 64 letters, digits,-or_(newClientKey()mints one). Stored as a keyed hash, it makes a second send of the same order answer200withdata,childrenandreplayed: true, and nolink. A key of the wrong form is refused400PUBLIC_WRITE_REFUSEDwithparams.reason: "format". A retry replays even after the app’s switch for the entry was turned off. Any other create is then refused403PUBLIC_SWITCHED_OFF. rankis there on an entry that ranks: how many matching rows come at or before the new one.linkis the new row’s own link, answered once, on an entry that gives one: the key it opens through, the code for the page’s URL fragment, and a session already open on the row.replacesis the own-link session of a hold this create lets go in the same write, so a changed checkout does not hold its places twice.
Public quotes of a create and a change
Section titled “Public quotes of a create and a change”POST /public/records/{ref}/dry-run tries the same create and keeps nothing (quote in the
client). It takes values, children and replaces, where the hold named is judged as let go.
The entry must declare dryRun: true; otherwise the route answers 404 PUBLIC_REF_NOT_FOUND.
A quote costs a read, not a write, and needs no proof of work. It may come before the guest has
typed their details, and it shows figures only: no retry key, no running number, no code.
{ "data": { "subtotal": "28.90", "tax": "2.60", "total": "31.50" }, "children": { "lines": [{ "data": { "unit_price": "12.50", "line_total": "25.00" } }] }, "capacity": [{ "pool": "12", "state": "available", "at": "2026-10-02" }], "exact": true}exact is false when a before hook runs on a table of the write. A quote runs none, so the
save may come out otherwise. nights is added for a row priced by the night.
POST /public/records/{ref}/{id}/dry-run tries a change of the guest’s own row: { values } in,
{ data, exact, nights?, children? } out (quoteChange in the client, which answers
{ data, exact, nights }). data is the row as the change would leave it, with the stamps and
totals the change would decide. children holds the rows below it that the change moves (the
extras that follow a stay’s nights and guests), under each child entry on the key that is read
with this row as its parent, shown as that entry shows them. The entry must declare dryRun: true
and allow the change.
A row’s own link, made again
Section titled “A row’s own link, made again”POST /public/records/{ref}/{id}/new-link makes a row’s own link again for its signed-in person
(newLink in the client). The old link stops opening the row, every session it opened ends, and
the new link is emailed as the app’s own message. The reply is 202 with { "data": {} }; the
new code never comes back here. The entry must declare newLink.
It needs a verified session of a person signed in by email. A row’s own link, a lookup session,
another person’s row and a row that is not there all get the same 404 PUBLIC_REF_NOT_FOUND.
A second ask within 60 seconds changes nothing and answers 202. Once five new links were made for
one row in a day, the next ask is refused 409 PUBLIC_LIMIT_REACHED. When no email can be sent, it is refused 503
PUBLIC_CODE_UNAVAILABLE before the old link is stopped.
On an entry opened by a row’s own link, the same route makes another own link of that row again
(the confirm link of a confirmation email, “Send it again”), with the session the row’s own link
opened. The email goes to the row’s own address; the asking session stays open. When the entry’s
newLink.when does not hold for the row, or that link is stopped, the ask is refused 409
PUBLIC_WRITE_REFUSED, and nothing is made, sent or counted. Such asks are limited to 5 a day per row
and 5 a day per mailbox over the table (409 PUBLIC_LIMIT_REACHED). The email is queued in the same
transaction as the new code: when it cannot be, nothing is made and nothing counted. See
Identity and own links.
Signing out, and deleting details
Section titled “Signing out, and deleting details”| Route | Client | What it does |
|---|---|---|
DELETE /public/session |
signOut |
Ends this session. Always 200 { "data": {} }. |
POST /public/session/revoke-all |
signOutEverywhere |
Ends every session of the person, this one too, and takes back every sign-in link still open to their address. |
DELETE /public/account |
forgetMe |
Deletes the person’s details, then ends every session and sends the old address one last email. |
The last two need a verified session of a person signed in by email: a row’s own link is refused
403 PUBLIC_CLAIM_UNAVAILABLE, a lookup session 403 PUBLIC_CLAIM_LEVEL, and no session
404 PUBLIC_REF_NOT_FOUND. Both answer 200 { "data": {} }.
DELETE /public/account also needs a mailbox proved in the last ten minutes, by a link pressed or
a code confirmed; otherwise it is refused 403 PUBLIC_CODE_STEP_UP. It empties the columns the
identity’s forget names and writes its stamp. The row keeps its key, so the bookings and tickets
that point at it stay. With forget.links, every own link of the person’s rows is made again
first. If one cannot be, the call is refused 409 PUBLIC_WRITE_REFUSED and nothing is
forgotten. See Delete my details.
A device whose session was ended this way is told why on its next request. That reply carries
x-adminium-session-ended: elsewhere or forgotten, once, and the session is gone after it
(sessionEnded() in the client). The header is exposed to cross-origin pages.
The level a shared link opens
Section titled “The level a shared link opens”POST /public/claim/token takes { "token": "…" }, the code from a shared link’s fragment, and
answers { "data": { "session", "expiresAt", "level"? } }. On a key whose claim is a row’s own
link (own: true), the session is verified and the reply says level: "verified". Any other
shared link opens a lookup session, and the reply carries no level. An unknown code is 404
PUBLIC_REF_NOT_FOUND; a link stopped or expired is 410 LINK_EXPIRED.
Typed codes
Section titled “Typed codes”A code a guest types for a read travels in the x-adminium-code header (64 characters at most),
never in a URL, where logs and proxies would keep it. ?code= on a list or on availability is
refused 400 PUBLIC_QUERY_REFUSED. A write carries its typed code in its values.
An entry marked unlock: true in the config shows its rows only with the code that unlocks them.
Without a code a list is empty, and a code that unlocks nothing is a spent guess. Once a visitor’s
guesses are spent, a typed code is refused 429 PUBLIC_RATE_LIMITED before anything is looked
up. Availability counts the rows a code unlocks too. In the client, list and get take
{ code }, and so does parentAvailability. See
Codes that unlock rows.
Availability by kind
Section titled “Availability by kind”GET /public/availability/{ref} answers an availability entry. refs[ref].capacity in the
config says its kind, and the entry’s rule says which of the table’s limits it answers. A
parameter the kind does not take is refused 400 PUBLIC_QUERY_REFUSED, never ignored.
An app installed by an earlier Adminium answers its entries without capacity until the app is
next updated (an update to the same version will do). Read such an entry over a limit as slot,
the only kind there was.
| Kind | Asks | Answers data as |
|---|---|---|
slot |
date, or from and days (up to 31); party |
[{ time, state }], free, full or paused; per day [{ date, open, state }], open, full or closed |
parent |
under, date, qty (1 to 50), exclude; a code in the header |
[{ id, state, left? }], on, soon, ended or soldout |
night |
from and to (up to 31 nights), guests (1 to 50), earliest (1 to 90 days), exclude |
[{ pool, state, left?, earliest? }], open, full or closed |
- Slot.
partyis never asked for more than one row may hold. - Parent. The rows the limit is held on (an event’s ticket types) that a plain public read of
their table shows, at most 200. On an entry with
under, only the rows under that value, and none without it.dateis a day for a limit that counts by day, today on the venue’s clock when absent. A row issoldoutwhen fewer are left thanqty(parentAvailabilityin the client). - Night. Each pool (a room type) over the nights
fromtoto, leaving out the pools that sleep fewer thanguests. Withearliest, each pool also gives its first arrival of the same length with room, and the reply’s top-levelearliestgives the house’s (nightAvailabilityin the client).
GET /api/v1/public/availability/room-types?from=2026-08-03&to=2026-08-06&guests=2&earliest=14{ "data": [ { "pool": "2", "state": "full", "earliest": "2026-08-05" }, { "pool": "3", "state": "open", "left": 1, "earliest": null } ], "earliest": "2026-08-05"}left is said only where the entry declares showLeft ({ "below": n } or
{ "belowShare": percent }), and only when fewer are left than that. A page may ask a parent
limit for a qty no higher than that count and one order’s most; without showLeft, it asks for
one. exclude names a row of the session’s own, such as a booking being moved or the order a
checkout already holds, and leaves its places out of the count. An id the session does not reach
is ignored. See Availability.
Pictures
Section titled “Pictures”GET /public/pictures/{keyId}/{ref}/{rowId}/{column}/{fileId} serves an image column any visitor
may see, with no key header, so an <img> can load it. The config’s pictures gives the base. See
Pictures.
The server’s clock
Section titled “The server’s clock”GET /public/config carries now, the server’s time as an ISO instant, and answers with
cache-control: no-store. The staff and customer surface-config.json carry now the same way.
A page on a device whose clock is wrong still asks for the venue’s today. The client’s now()
applies the difference the config showed.
Realtime
Section titled “Realtime”/ws |
WebSocket |
/api/v1/events |
SSE fallback |
Behind a reverse proxy the WebSocket needs Upgrade/Connection headers and a
long read timeout, or the UI goes stale without erroring:
Behind a reverse proxy.
Errors
Section titled “Errors”Every error shares one envelope, with a stable machine-readable code. Branch on
code, not on the message — messages are for humans and may be reworded or
localized.
Validation failures identify the offending path: every external input is schema-validated before a handler sees it.
A path parameter or a query parameter holding the character U+0000 (%00, or
\u0000 in the JSON of a where filter) is refused 400 VALIDATION_FAILED
before anything reads it, on every database: details.in is params or
querystring, and details.issues[0] names the parameter with the code
invalid-character. The same holds for a request body (details.in is body, the
path names the field: name, ids.0), except a row’s values on their way to your
database (a record’s values, a child row’s, a public entry’s): those are judged
by the write that would store them, 422 naming the column. A body nested more than
64 levels deep is refused 400 VALIDATION_FAILED with the code too-deep,
unread. A list’s cursor that holds the character is the list’s own 422
malformed cursor.
Every write refusal, its status and what its details carry, and the public API’s own codes, are
listed in Error codes.
Off by default: the dashboard is served by the same process as the API, so it is same-origin.
For a split deployment, ADMINIUM_CORS_ORIGINS takes a CSV of exact origins.
* is rejected — responses are credentialed.