Worker API & Clerk Auth
The HTTP API is a Cloudflare Worker built with Hono and @hono/zod-openapi, assembled by createApp() in src/api/index.ts and mounted under /api.
The factory wires a fixed middleware chain ahead of the per-feature route registrars:
flowchart LR
W["src/worker.ts<br/>Worker entry"] --> APP["createApp()<br/>src/api/index.ts"]
APP --> SEC["secureHeaders<br/>(API CSP)"]
SEC --> OC["originCheck<br/>origin-check.ts"]
OC --> ERR["onError + problem()<br/>error.ts (RFC 9457)"]
ERR --> CLERK["clerkAuth<br/>clerk-auth.ts"]
CLERK --> CK{{"Clerk<br/>session verify"}}
ERR --> ROUTES["register*Routes(app)<br/>lists / tasks / steps / ..."]
APP --> DOC["doc('/v1/openapi.json')<br/>OpenAPI 3.1"]
Construction
Section titled “Construction”createApp() returns an OpenAPIHono instance with .basePath('/api'). A defaultHook turns Zod validation failures into a uniform RFC 9457 422 validation_error (ADR-002) rather than Hono’s default 400. Routes are registered per feature by register*Routes(app) functions (registerListsRoutes, registerGroupsRoutes, registerTasksRoutes, registerStepsRoutes, registerMyDayRoutes, registerListMembersRoutes, registerSyncRoutes, registerMeRoute) and are versioned under /v1 (ADR-001).
Middleware chain
Section titled “Middleware chain”secureHeaders— applied to every response with an enforcingdefault-src 'none'CSP (API responses are JSON/problem+json with no subresources). The WebSocket upgrade (GETwithUpgrade: websocket) is exempted, because its immutable101response cannot carry headers (src/api/index.ts).originCheck— cross-origin guard (src/api/middleware/origin-check.ts).- error middleware —
onError+problem()produce the RFC 9457 envelope (src/api/middleware/error.ts, ADR-002). - Clerk — authentication is the single boundary (constitution §7.12); the user identity is derived from the verified session, never from client input. Auth UI uses Clerk prebuilt components (ADR-010).
A request that fails validation short-circuits at the defaultHook; one that fails auth short-circuits in clerkAuth; only a verified request reaches the route handler:
sequenceDiagram
participant Client
participant App as "createApp() (OpenAPIHono)"
participant Clerk as "clerkAuth"
participant Backend as "Clerk backend"
participant Route as "route handler"
Client->>App: POST /api/v1/...
App->>App: secureHeaders, originCheck
App->>Clerk: clerkAuth
Clerk->>Backend: authenticateRequest(req.raw)
alt no auth.userId
Backend-->>Clerk: unauthenticated
Clerk-->>Client: 401 unauthorized (RFC 9457)
else verified
Backend-->>Clerk: auth.userId
Clerk->>Route: c.set('auth'), next()
Route-->>Client: 2xx JSON (or 422 validation_error)
end
Contract-first
Section titled “Contract-first”Routes are declared with createRoute({ ... }) and Zod schemas, so the OpenAPI document is generated from the code (ADR-003) — it is the single source of truth shared by all clients and is rendered as this site’s API Reference. scripts/build-openapi.mjs emits it to openapi.json at build time.
Related: DB Schema & Notify-Affected (what the routes write and who they notify), SyncInbox Durable Object (the upgrade route’s target).