API conventions
URL shape
/api/v1/<resource> for stable endpoints. Versioned via the path. Sub-resources nest: /api/v1/pages/<id>/blocks.
HTTP verbs
GET— idempotent readPOST— createPATCH— partial update (default for edits)PUT— full replace (rare, use sparingly)DELETE— soft delete (setsdeleted_at); pass?permanent=truefor hard delete
Error shape
Every error response:
json{
"error": "human-readable message",
"code": "machine_code",
"details": {}
}
Status codes used: 400 (bad input), 401 (no auth), 403 (auth but no permission), 404 (not found), 409 (conflict), 410 (gone — expired), 422 (validation), 429 (rate limit), 500 (us).
Authentication
Two paths, in order of precedence:
Authorization: Bearer <jwt>— for signed-in users (preferred)x-edit-token: <token>— for anonymous edits (per-doc capability)
Server middleware (lib/verify-auth.ts) returns {userId, email}or null. Routes that allow either signed-in or token holders should check both.
Rate limiting
Sliding window, IP-based, 60 req/min default. Override per-route in middleware.ts. Returns 429 with Retry-After header.
Pagination
Cursor-based, not offset. ?cursor=<opaque>&limit=20. Response includes nextCursor (or null if last page).
Things to avoid
- Don't put service-role key calls behind the public API. Use server-component reads or the cron path.
- Don't return more than 200 rows in one response. Paginate.
- Don't emit raw Postgres errors to clients — sanitize via
mapPgError.