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.
Declare what the API exposes
Section titled “Declare what the API exposes”| 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
Section titled “Tokens”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.
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:00ZA 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.
Serve it over HTTP
Section titled “Serve it over HTTP”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).
Use your App from Claude Code
Section titled “Use your App from Claude Code”--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:
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-stringGive 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 the contract
Section titled “Export the contract”--export openapi prints the document without starting a server:
tablewalk --app ./ledger --config ./tablewalk.json --commands ./ledger/commands.ts \ --export openapi --api-integers decimal-string > current-api.jsontablewalk compare-openapi previous-api.json current-api.jsoncompare-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.
Tenant Apps
Section titled “Tenant Apps”A tenant App’s token binds one membership:
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:00ZServe 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.
Not yet
Section titled “Not yet”Row-policy Apps, several sources, form commands, serving beyond loopback, remote MCP clients and composite keys.