Skip to content

App API

The App API serves what an App’s api block declares to other programs, over loopback HTTP or as MCP tools, with scoped tokens (preview, SQLite and PostgreSQL).

import { expose } from 'tablewalk/app';
api: {
version: 1,
roles: ['operator'],
resources: { item: expose(['id', 'revision', 'name'], { filters: ['name'], sorts: ['id'] }) },
views: ['open-items'],
commands: [{ id: 'change', version: 1, target: 'item', versionField: 'revision', input: {} }],
},

Nothing is served until you ask for it, and only on loopback.

Key Says
version The API version, in every route (/v1).
roles The App roles a token may carry. Never more than they already grant.
resources expose(fields, { filters?, sorts? }) per resource: exact columns; new database columns are never added.
views List views served as filtered reads.
commands Targeted commands (a target and versionField), registered in the App’s --commands module. Form commands are refused.

The declaration is checked against the catalog and the App’s grants and recorded in authority.lock. Features may add resources, views and commands. Row-policy Apps, several sources and composite keys are not supported.

Tokens live in the App’s native auth store; cookies never authenticate the API. tablewalk api-token --help lists the commands: provision (once per store), issue (prints the secret once), inspect and revoke.

Terminal window
tablewalk api-token issue --app ./ledger --config ./tablewalk.json --store-id <uuid> \
--commands ./ledger/commands.ts --subject <account-id> --actor <account-id> \
--role operator --label "Ledger automation" --expires-at 2030-01-01T00:00:00Z

A token carries exact roles, an expiry and optionally the operations it may use (--operation item:item, --operation command:change). Every request checks it again, so a revocation applies to the next one.

Terminal window
tablewalk --app ./ledger --config ./tablewalk.json --commands ./ledger/commands.ts \
--host 127.0.0.1 --port 4111 \
--api-reads --api-integers decimal-string --api-requests-per-minute 120

--api-integers (decimal-string or number) and the request budget have no defaults. Leave out --commands when the App exposes none. Routes live under /api/apps/<app-id>/v<version>:

Request Does
GET /<resource> Lists exposed rows; pass a non-null next back as cursor
GET /<resource>/<key> Reads one row
GET /views/<view-id> Applies an exposed list view
POST /<resource>/<key>/commands/<id>/v<n> Runs a command, with If-Match: "<revision>" and an Idempotency-Key
POST /<resource>/commands/<id>/v<n> Runs a targets: 'many' command: { "targets": [{ "key", "version" }], "input" }
GET /commands/<id>/v<n>/receipts/<idempotency-key> Recovers a receipt
GET /$openapi The OpenAPI document, narrowed to this token

A command answers { "receipt": { "id", "status", "replayed" }, "result" }; the same Idempotency-Key replays the receipt instead of running again. Refusals are problem details with a code and an outcome (not_applied, or unknown).

--app-mcp speaks the same API as MCP tools over stdio. Reads are get_…, list_… and view_… tools; commands are run_… (runMany_…) and recover_…. The token comes from TABLEWALK_API_TOKEN, never the command line:

Terminal window
claude mcp add ledger -e TABLEWALK_API_TOKEN=<token> -- \
tablewalk --app /srv/ledger --config /srv/tablewalk.json \
--commands /srv/ledger/commands.ts --app-mcp --api-integers decimal-string

Give it a token issued for this use alone, with the fewest operations and a near expiry, and do not add it with --scope project, which would write the token into the repository.

--export openapi prints the document without starting a server:

Terminal window
tablewalk --app ./ledger --config ./tablewalk.json --commands ./ledger/commands.ts \
--export openapi --api-integers decimal-string > current-api.json
tablewalk compare-openapi previous-api.json current-api.json

compare-openapi reports changes between two exported files: exit 0 when the version requirement is met, 1 when a needed version increase is missing, 2 for invalid input. It is a review aid, not a compatibility proof.

A tenant App’s token binds one membership:

Terminal window
tablewalk api-token issue --app ./depot --config ./depot.json --store-id <uuid> \
--tenant north --policy ./policy.mjs --commands ./commands.ts \
--subject <account> --actor <operator> --role member \
--label "North API" --expires-at 2026-10-01T00:00:00Z

Serve it with --policy beside --api-reads or --app-mcp. The token reads and runs only that tenant’s rows; a changed membership or suspended tenant is refused on the next call. Platform-realm views, commands and sources cannot be exposed. A tenant token has no receipt recovery: retry with the same Idempotency-Key.

Row-policy Apps, several sources, form commands, serving beyond loopback, remote MCP clients and composite keys.