Inspect and operate the app
View as MarkdownThe task app now contains generated CRUD routes, a complete-task transition, and a project-progress report. This page uses the live OpenAPI document and the project-local CLI to discover and call all three kinds of surface. You will also create a revocable agent token and prove that non-browser callers receive the same abilities and workspace row visibility as the represented user.
Walk through the running task app as an operator. Discover its table and app routes, call one generated query and one domain endpoint, check the progress report, and prove a scoped token cannot see another workspace.Treat the running API as the inventory
Section titled “Treat the running API as the inventory”Source files show intended contracts. The mounted server shows which contracts are actually available. Start the app and inspect its OpenAPI-backed inventory before making a direct call:
pnpm devpnpm exec sapporta tables listpnpm exec sapporta endpoints listpnpm exec sapporta endpoints show "GET /api/tables/tasks"pnpm exec sapporta endpoints show "PUT /api/tables/tasks/{id}"pnpm exec sapporta endpoints show "POST /api/tasks/{id}/complete"pnpm exec sapporta endpoints show "GET /api/reports/project-progress"The selectors use mounted runtime URLs, including /api. The shared ts-rest
contracts for the complete-task and report routes omit that prefix. Generated
row updates use PUT; a discovered PATCH call is not a substitute.
The OpenAPI document is protected in an authenticated app. A discovery failure
can therefore mean that caller authority is missing, not that the endpoint was
never registered. If one app-owned route is absent while the other routes are
visible, check both app.route() and app.extend() in packages/api/app.ts.
Use the highest-level operation that fits
Section titled “Use the highest-level operation that fits”The generated row commands fit ordinary record reads and writes. Query the open tasks and raise one task’s priority without sending a workspace column:
pnpm exec sapporta rows list tasks \ --where '{"status":{"eq":"open"}}' \ --sort "due_date" \ --output jsonpnpm exec sapporta rows update tasks 1 \ --values '{"priority":"high"}'pnpm exec sapporta --output json rows get tasks 1The equivalent generated HTTP query and representative body are:
GET /api/tables/tasks?filter[status][eq]=open&sort=due_date HTTP/1.1Host: localhost:3000{ "data": [ { "id": 1, "project_id": 1, "title": "Publish launch checklist", "status": "open", "priority": "high", "due_date": "2026-08-01" } ], "meta": { "total": 1, "page": 1, "limit": 50, "pages": 1 }}Completing a task is not an ordinary update. It changes the task and inserts a history row in one transaction, so call the app-owned transition and then read back its effects:
pnpm exec sapporta api post /api/tasks/1/complete --body '{}'pnpm exec sapporta --output json rows get tasks 1pnpm exec sapporta rows list task_events \ --where '{"task_id":{"eq":1}}' \ --sort=-occurred_at \ --output jsonpnpm exec sapporta api get /api/reports/project-progressA successful transition returns the domain result declared by the shared contract:
{ "task_id": 1, "event_id": 6, "status": "completed"}The follow-up reads supply the task and event details. They are observable proof
that the status, immutable history, and report totals changed; a successful
200 alone does not prove those effects.
Give a non-browser caller one revocable boundary
Section titled “Give a non-browser caller one revocable boundary”Open /account/profile while Workspace A is active and create an agent access
token. The raw spat_... value appears once. Store it in a secret manager or a
disposable environment variable. Do not paste it into a prompt, project file,
documentation capture, or command-line argument that will be logged.
export SAPPORTA_API_URL="http://localhost:3000"read -s SAPPORTA_API_TOKENexport SAPPORTA_API_TOKEN
pnpm exec sapporta endpoints show "GET /api/reports/project-progress"pnpm exec sapporta rows list tasks --output jsonpnpm exec sapporta api get /api/reports/project-progressThe bearer equivalent is a normal authenticated API request:
GET /api/reports/project-progress HTTP/1.1Host: localhost:3000Authorization: Bearer spat_<token-id>_<secret>The token represents one user in the workspace that was active at creation.
Authentication establishes that identity. Feature abilities decide whether the
caller may read project_progress or complete a task. Row visibility limits
which projects, tasks, and events those permitted operations can reach. A
client-supplied workspace_id cannot switch any of those boundaries.
Seed a clearly named task in Workspace B. The Workspace A token’s task list and report must omit it. An attempt to complete a Workspace B task ID should have the same missing-row behavior as an ID that does not exist; invisible and missing rows must not disclose different information.
Revoke the credential and test the next request
Section titled “Revoke the credential and test the next request”Return to /account/profile, revoke the token, and repeat a harmless list. The
next request should fail with the documented token_revoked authentication
response. Removing a shell variable prevents local reuse; revocation changes
server behavior.
pnpm exec sapporta rows list tasks --limit 1unset SAPPORTA_API_TOKEN{ "error": "Agent access token has been revoked", "code": "token_revoked"}The browser, CLI, and bearer caller now exercise one mounted API under the same authorization model. Discovery supplies the exact route and method. Generated commands cover ordinary records, while app endpoints cover transitions and reports. Continue with Build, test, and choose the next guide for the final validation pass, or use the guides for OpenAPI discovery, the agent data console, and scoped tokens.