Skip to content

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 of CHANGELOG.md, and the shipped v0.30.0 changelog. (The original docs/committed/todo/TODO 0.30.0 API Cleanup.md has 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-keyed skills/feats/powers/attacks/attributes via 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 whole skills/feats/powers/attacks/attributes). See the migration checklist in api-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 /artwork while tokens use plural /artworks — standardize when the character artwork PATCH lands.
  • metadata blobs 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 exempt metadata.
  • 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):

  • parseListParams exists and is fully featured + unit-tested (utils/listQuery.ts:106) — supports limit/offset/sort/order/fields + a filterable allow-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/tokens requires mapId and hard-caps ?limit at 5000 (tokens.ts:409-411,430) — closes the "dump the whole table" hole.

Pending ⚠︎:

  • Adopt parseListParams across 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

  1. ⚠︎ parseListParams is dead code — decide adoption plan (wrap all list handlers) or the cleanup's centerpiece stays theoretical.
  2. ⚠︎ patchGuard is referenced but does not exist — a docstring in listQuery.ts:6 cites a patchGuard sibling helper that was never written; reject-unknown-fields is unenforced (only drawings.ts uses zod .strict()). Either build patchGuard or drop the reference.
  3. ⚠︎ 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.
  4. ⚠︎ Two overlapping deletion subsystems (/api/soft-deleted/* vs /api/deleted-items/*) — clarify which governs which entity.
  5. Tracking is stale relative to code: Workstream A server routes shipped, but the "API Cleanup" checkboxes in CHANGELOG.md don't reflect it. (The former docs/committed/todo/TODO 0.30.0 API Cleanup.md has been removed/consolidated.)