{ "_meta": { "updated_at": "2026-06-09T18:56:37.459Z", "commit": "99f59c3999f4ef992f01ef8f8a38c1d86d2f2a0f", "version": 3 }, "entries": { "GET /health": { "method": "GET", "path": "/health", "auth": "none", "file": "apps/api/src/routes/health.ts", "description": "DB liveness probe. Returns { ok: true, db: 'up' } or 503." }, "GET /callback": { "method": "GET", "path": "/callback", "auth": "none (OIDC callback — must be before auth guard)", "file": "apps/api/src/index.ts", "description": "OIDC authorization-code callback. Processed by @hono/oidc-auth processOAuthCallback." }, "GET /api/login": { "method": "GET", "path": "/api/login", "auth": "oidcAuthMiddleware", "file": "apps/api/src/index.ts", "description": "Auth entry point. Redirects to / after successful OIDC login. PWA navigates here for re-auth." }, "GET /api/me": { "method": "GET", "path": "/api/me", "auth": "oidcAuthMiddleware", "file": "apps/api/src/routes/me.ts", "response": "{ user: { id: number, displayName: string|null, color: string } }", "description": "Returns authenticated member's identity and assigned color. Upserts user row on first call." }, "GET /api/events": { "method": "GET", "path": "/api/events", "auth": "oidcAuthMiddleware", "params": [ "start (YYYY-MM-DD, required)", "end (YYYY-MM-DD, required)" ], "file": "apps/api/src/routes/events.ts", "response": "{ occurrences: CalendarOccurrence[] }", "description": "Windowed calendar events. Max 90-day window. Reads only from MariaDB cache; never hits Fastmail. Expands RRULEs server-side." }, "POST /api/events/create": { "method": "POST", "path": "/api/events/create", "auth": "oidcAuthMiddleware", "body": "CreateEventPayload (title, allDay, start, end, recurrence?, location?, description?, calendarUrl?)", "file": "apps/api/src/routes/events.ts", "response": "202 { uid: string }", "description": "Enqueues create to calendarOutbox. Async CalDAV write-back via outbox worker. Returns uid immediately." }, "PATCH /api/events/:uid/edit": { "method": "PATCH", "path": "/api/events/:uid/edit", "auth": "oidcAuthMiddleware", "params": [ "uid (path)" ], "body": "CreateEventPayload", "file": "apps/api/src/routes/events.ts", "response": "202 { uid: string }", "description": "Enqueues update (or delete+create pair for calendar-move) to calendarOutbox. Async write-back." }, "DELETE /api/events/:uid": { "method": "DELETE", "path": "/api/events/:uid", "auth": "oidcAuthMiddleware", "params": [ "uid (path)" ], "file": "apps/api/src/routes/events.ts", "response": "202 { uid: string }", "description": "Enqueues delete to calendarOutbox with cached etag (If-Match). Async write-back." }, "GET /api/events/sync-status": { "method": "GET", "path": "/api/events/sync-status", "auth": "oidcAuthMiddleware", "params": [ "uid (query, required)" ], "file": "apps/api/src/routes/events.ts", "response": "{ uid: string, status: 'pending'|'done'|'failed'|'dead', error?: string }", "description": "Outbox status poll for a given event UID, scoped to current member. Used by SyncStateToast." }, "GET /api/events/writable-calendars": { "method": "GET", "path": "/api/events/writable-calendars", "auth": "oidcAuthMiddleware", "file": "apps/api/src/routes/events.ts", "response": "{ calendars: [{ url, displayName, color, isShared }] }", "description": "Authoritative D-03 writable set: member's own calendars + shared Family calendar. Client never derives this itself." }, "GET /api/sse/heartbeat": { "method": "GET", "path": "/api/sse/heartbeat", "auth": "oidcAuthMiddleware", "file": "apps/api/src/routes/sse.ts", "response": "text/event-stream — event: heartbeat, data: { ts, id } every 10s", "description": "SSE smoke-test endpoint for Pangolin tunnel validation." }, "GET /api/sse/lists": { "method": "GET", "path": "/api/sse/lists", "auth": "oidcAuthMiddleware", "file": "apps/api/src/routes/sse.ts", "response": "text/event-stream — events: item:added | item:updated | item:deleted | list:updated | list:deleted, plus heartbeat every 30s", "description": "Scoped live-list fan-out stream (LIST-04, D-04). Subscribes only to list channels accessible to the caller (owner + shares). Event payload triggers client-side query invalidation (D-10). 30s keepalive heartbeat for Pangolin tunnel." }, "GET /api/lists": { "method": "GET", "path": "/api/lists", "auth": "oidcAuthMiddleware", "file": "apps/api/src/routes/lists.ts", "response": "{ lists: [{ id, name, isShared, ownerId, activeCount, doneCount, createdAt, updatedAt }] }", "description": "Scoped list index. Returns only lists the caller owns or has a list_shares row for (T-04-02, D-04). Includes per-list item counts." }, "POST /api/lists": { "method": "POST", "path": "/api/lists", "auth": "oidcAuthMiddleware", "body": "{ name: string, isShared?: boolean (default true) }", "file": "apps/api/src/routes/lists.ts", "response": "201 { id, name, isShared, ownerId, activeCount, doneCount, createdAt, updatedAt }", "description": "Create a named list. isShared=true (default) auto-inserts list_shares for all other members (D-01/D-02). Shares are server-managed only — no client shares endpoint (T-04-08)." }, "PATCH /api/lists/:id": { "method": "PATCH", "path": "/api/lists/:id", "auth": "oidcAuthMiddleware", "params": [ "id (path)" ], "body": "{ name?: string, isShared?: boolean } — at least one field required", "file": "apps/api/src/routes/lists.ts", "response": "{ id, name, isShared, ownerId, createdAt, updatedAt }", "description": "Update name and/or isShared. Caller must be owner or sharee. isShared mutations owner-only (T-04-07/T-04-08). Visibility change reconciles list_shares (false→true inserts; true→false deletes all non-owner shares)." }, "DELETE /api/lists/:id": { "method": "DELETE", "path": "/api/lists/:id", "auth": "oidcAuthMiddleware", "params": [ "id (path)" ], "file": "apps/api/src/routes/lists.ts", "response": "{ id }", "description": "Owner-only delete. Cascade via FK onDelete:cascade removes items and shares. Non-owner sharees receive 403." }, "GET /api/lists/:id/items": { "method": "GET", "path": "/api/lists/:id/items", "auth": "oidcAuthMiddleware", "params": [ "id (path)" ], "file": "apps/api/src/routes/lists.ts", "response": "{ items: [{ id, listId, text, checked, rank, createdAt, updatedAt }] }", "description": "Returns all items for the list ordered by rank ASC. Access-gated: owner or sharee only (T-04-05)." }, "POST /api/lists/:id/items": { "method": "POST", "path": "/api/lists/:id/items", "auth": "oidcAuthMiddleware", "params": [ "id (path)" ], "body": "{ text: string (1..500) }", "file": "apps/api/src/routes/lists.ts", "response": "201 { id, listId, text, checked, rank, createdAt, updatedAt }", "description": "Add item to list. Rank assigned via rankForAppend(lastActiveRank) — appends after last unchecked item. Access-gated (T-04-05). publishListEvent item:added fan-out." }, "PATCH /api/list-items/:itemId": { "method": "PATCH", "path": "/api/list-items/:itemId", "auth": "oidcAuthMiddleware", "params": [ "itemId (path)" ], "body": "exactly one of: { checked: boolean } | { text: string } | { position: string }", "file": "apps/api/src/routes/lists.ts", "response": "{ id, listId, text, checked, rank, createdAt, updatedAt }", "description": "Per-field last-write-wins item update (D-08). Exactly one field enforced by Zod (T-04-07). uncheck (checked:false) recomputes rank to active-bottom. 404 if item missing (T-04-09, no upsert). publishListEvent item:updated fan-out." }, "DELETE /api/list-items/:itemId": { "method": "DELETE", "path": "/api/list-items/:itemId", "auth": "oidcAuthMiddleware", "params": [ "itemId (path)" ], "file": "apps/api/src/routes/lists.ts", "response": "{ id }", "description": "Delete item instantly (D-06). Delete-wins semantics (D-09): no rollback path. Access-gated: owner or sharee. publishListEvent item:deleted fan-out." } } }