REST API Cleanup — Draft Specification (committed changes to REST/HTTP API 0.1)
Status: DRAFT / COMMITTED — partially shipped. This is the committed set of changes to the shipped REST API. It consolidates two workstreams from the v0.30.0 "API Cleanup" project and records — code-verified on branch
version/0.40.0— what has shipped vs. what remains. Incompleteness / inconsistency flagged ⚠︎.Sources:
../requirements/api-usage-refactor.md, the "API Cleanup" section ofCHANGELOG.md, and the shipped v0.30.0 changelog. (The originaldocs/committed/todo/TODO 0.30.0 API Cleanup.mdhas since been removed/consolidated.)
Workstream A — Write granularity (PATCH + sub-entity routes)
Goal: stop sending whole objects/arrays on every edit. Replace full-object PUTs with minimal PATCH + addressable sub-entity routes (read-modify-write of the parent's JSON column).
Shipped ✅ (verified):
- Character sub-entity routes:
POST/PATCH/DELETE /api/characters/:id/actions/:actionId(characters.ts:938,986,1037); slug-keyedskills/feats/powers/attacks/attributesvia a factory (characters.ts:1109-1189); artwork + versions routes. → resolves the cross-cutting "name-keyed addressing" decision in favor of slug (not a numeric id backfill). - Token auras/conditions sub-routes:
POST/PATCH/DELETE /api/tokens/:id/auras/:auraId(tokens.ts:797,833,872);POST /:id/conditions+DELETE /:id/conditions/:conditionId(tokens.ts:913,949).
Pending ⚠︎:
- Client migration: several call sites still send whole arrays (
updateToken(id, {actions}),{artworks},{conditions},{auras}; character patch builder still ships wholeskills/feats/powers/attacks/attributes). See the migration checklist inapi-usage-refactor.md(Token/Character "client call sites to migrate"). The server routes exist; the client isn't fully using them yet. - Naming inconsistency ⚠︎: characters expose singular
/artworkwhile tokens use plural/artworks— standardize when the character artwork PATCH lands. metadatablobs are still re-sent whole on each property change (accepted; per-key routes are overkill) — but the "changes aren't too large" guard must size nested objects or exemptmetadata.- Legitimate bulk writes that a "too large" guard must not break:
handleSheetImport,derive-power-actions,useRefreshTokenFromCharacter— route through dedicated endpoints or exempt explicitly.
Workstream B — List-endpoint standardization
Goal: common middleware so every list route supports ?limit&offset&sort&order
(default limit 50, never infinite), ?fields= sparse fieldsets, and
permission-filtering at the query level.
Built but NOT wired ⚠︎ (the single biggest gap):
parseListParamsexists and is fully featured + unit-tested (utils/listQuery.ts:106) — supportslimit/offset/sort/order/fields+ afilterableallow-list — but has zero callers. No route imports it; no client sends?fields=.- Today only two routes paginate at all (hand-rolled):
ledger.ts,library.ts. The only live sparse-response mechanisms are bespoke: characters' stub-column projection and tokens'?omitActions=1.
Shipped ✅ (a related defense-in-depth item):
GET /api/tokensrequiresmapIdand hard-caps?limitat 5000 (tokens.ts:409-411,430) — closes the "dump the whole table" hole.
Pending ⚠︎:
- Adopt
parseListParamsacross all list routes (server) and send?fields=on hot paths (client) — e.g. character/token list loads should request only render fields. - Permissions placeholder (from the API Cleanup TODOs in
CHANGELOG.md): filter unauthorized entities at the query level rather than client-side. Not started.
Incompleteness / inconsistencies to resolve
- ⚠︎
parseListParamsis dead code — decide adoption plan (wrap all list handlers) or the cleanup's centerpiece stays theoretical. - ⚠︎
patchGuardis referenced but does not exist — a docstring inlistQuery.ts:6cites apatchGuardsibling helper that was never written; reject-unknown-fields is unenforced (onlydrawings.tsuses zod.strict()). Either buildpatchGuardor drop the reference. - ⚠︎ PUT ≡ PATCH across 24 route files — the cleanup should decide whether to keep the merge-both semantics (and document it) or make PUT a true replace.
- ⚠︎ Two overlapping deletion subsystems (
/api/soft-deleted/*vs/api/deleted-items/*) — clarify which governs which entity. - Tracking is stale relative to code: Workstream A server routes shipped, but
the "API Cleanup" checkboxes in
CHANGELOG.mddon't reflect it. (The formerdocs/committed/todo/TODO 0.30.0 API Cleanup.mdhas been removed/consolidated.)