--- # pages-kns6 title: Pages and users JSON API status: completed type: feature priority: normal created_at: 2026-07-13T02:09:09Z updated_at: 2026-07-13T16:47:43Z parent: pages-77lk blocked_by: - pages-zd46 --- Implement design doc §4.1 API rows + §4.6 upload pipeline. src/routes/api.rs (wasm-only): GET /api/me; GET /api/pages (public); POST /api/pages?name= — auth (401), can_upload (403), Origin check vs BASE_URL (403), slug validation/reserved (400) via core, quota 50 pages/user (403), Content-Length + buffered size cap 10 MiB (413), core::upload::build_manifest by Content-Type text/html vs application/zip (400 on other types), D1 insert first (409 NameTaken), then R2 puts under slug/ with best-effort rollback on failure (500); DELETE /api/pages/:slug — owner or admin, delete R2 prefix (list+delete loop) then D1 row; GET /api/users + PATCH /api/users/:id {can_upload} — admin only. All errors JSON {"error": "..."} with correct status. Wire into router. Keep handlers thin; any new pure logic goes in core with native tests. - [x] /api/me, GET /api/pages - [x] POST /api/pages full pipeline in §4.6 order - [x] DELETE /api/pages/:slug - [x] admin user endpoints - [x] Origin-check helper + native tests where pure - [x] full validation suite passes ## Summary of Changes Full JSON API by a Sonnet agent; Opus review APPROVE with fixes applied: - src/routes/api.rs: /api/me, GET/POST /api/pages (§4.6 pipeline: auth, can_upload, Origin, slug, quota, size caps, manifest, D1-first slug reservation, R2 puts with rollback), DELETE with paginated R2 prefix cleanup, admin /api/users GET/PATCH. - src/core/origin.rs: origin_allowed + can_delete pure helpers (12 tests + 2 doctests). - Review fixes: DefaultBodyLimit::max(10 MiB) instead of disable() (memory bounded at body-collection time); DELETE Origin check hoisted before DB lookups per §4.6. 122 unit tests + 16 doctests green; both targets clippy-clean.