API
Typed handlers, canonical envelope, OpenAPI export
Response envelope
Every app API responds with one consistent envelope:
{
"success": true,
"error": null,
"data": {},
"pagination": { "page": 1, "perPage": 20, "total": 0 },
"requestId": "req_8f2k1c",
"eventType": "project.created",
"serverTime": "2026-06-10T00:00:00.000Z",
"statePatch": {},
"events": []
}requestIdalways uses thereq_prefix.pagination.perPagedefaults to 20 for every list endpoint.statePatchandeventsare required on mutation responses and may be omitted on reads.
API groups
Route handlers are grouped by resource: auth/session, accounts, projects, tasks, users and invitations, teams, entitlements and access keys, notifications, mail events and templates, files and signed URLs, admin-scoped endpoints, docs search, and contact. The typed client is generated from the same definitions that produce the OpenAPI document, so handlers and client cannot drift.
Errors
Error behavior is fixed per status code and reuses one copy table:
| Code | Behavior |
|---|---|
| 401 | full-page redirect to /login?reason=session-expired |
| 403 | product-native permission state — "You don't have permission to do this." |
| 404 | "We couldn't find that record." |
| 409 | "This was changed elsewhere. Review your input and try again." (input preserved) |
| 422 | field-level validation messages; fallback "Please fix the highlighted fields." |
| 429 | "Too many requests. Try again in a moment." |
| 500 | "Something went wrong. Retry. (ref: req_...)" with a retry affordance |
In demo mode, a read-only demo admin calling a mutation endpoint receives a 403 envelope; the corresponding buttons render disabled.
List defaults
All list endpoints share the same defaults — no per-screen overrides:
- Ordering:
createdAtdescending (static catalogs keep seed order). - Pagination:
perPage20,pagestarts at 1. - Filters: every filter select defaults to All (no filter applied).
- Search: query param
q, prefix + substring matching, case-insensitive.
OpenAPI export
The OpenAPI document is downloadable from the admin API explorer (/admin/api), where you can also send live requests against the product API and inspect the envelope, error examples, and pagination behavior.