Generated table APIs
View as MarkdownThis page uses the generated HTTP and CLI surfaces for ordinary record work. You will inspect a mounted route, list and update tasks, read the response envelopes, and identify the point where a custom endpoint becomes appropriate. These routes can power generated screens, scripts, integrations, and agent-driven data work without a second CRUD implementation.
Use the generated tasks API to list open tasks and raise one task's priority. Inspect the mounted routes first, and keep workspace fields server-controlled.Inspect before calling
Section titled “Inspect before calling”Every registered table receives list, get, create, update, delete, lookup,
count, and export routes under /api/tables/<table>. Inspect the mounted
contract before composing a direct HTTP request:
pnpm exec sapporta endpoints show "GET /api/tables/tasks"pnpm exec sapporta endpoints show "PUT /api/tables/tasks/{id}"The command shows the actual method, path, auth boundary, request shape, and
declared responses. Generated updates use PUT, not PATCH.
For routine data work, the CLI handles authentication and the response envelope:
pnpm exec sapporta rows list tasks --where '{"status":{"eq":"open"}}'pnpm exec sapporta rows update tasks 1 --values '{"priority":"high"}'The equivalent list request is:
GET /api/tables/tasks?filter[status][eq]=openIts response is an object with rows and pagination metadata:
{ "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 }}Single-row reads and writes return { "data": row }. Create and update bodies
contain API-writable domain values only. They omit generated primary keys,
workspace_id, scoped_to_user_id, apiWritable: false columns, and
server-authored references because the server derives or supplies those values.
Generated OpenAPI and clients expose the same caller-controlled field set, and
request-time policy rejects prohibited fields when a caller bypasses them.
Generated request bodies preserve each column’s semantic JSON value. Select-backed text stays a string, numbers and booleans stay JSON primitives, foreign keys retain the target primary-key type, and dates and timestamps use canonical strings. Parse Temporal values only at a declared application boundary.
The request schema is not the final insert shape. Row security first merges
trusted workspace, user-scope, and server-authored values and verifies reference
visibility. The save pipeline then performs authoritative structural parsing,
canonicalizes date and timestamp values, runs the table’s top-level validate()
callback, and writes the parsed output. This order lets a required scope field
remain absent from the public body without weakening write validation.
Lookup, count, and CSV export apply the same row-access predicates as the list route. A project lookup therefore returns only visible projects:
{ "entries": [{ "value": 1, "label": "Website Relaunch" }]}Know when CRUD is no longer the operation
Section titled “Know when CRUD is no longer the operation”Generated routes fit one-table record operations. Completing a task may need to
update tasks, insert an immutable task_events row, and return a
domain-specific result in one transaction. That is one app-owned endpoint with a
shared ts-rest contract, not two client-coordinated CRUD calls.
The task app now has a programmatic surface that matches its generated UI and row boundary. Use it for ordinary record reads and writes. Move to an app-owned endpoint when the operation spans tables, coordinates external effects, or needs a domain response shape. The next guide makes generated queries bookmarkable and deterministic.