跳到內容

HTTP API surface

The web UI normally does not go through HTTP

Section titled “The web UI normally does not go through HTTP”

Ordinary browser reads and writes use TanStack Server Functions, not a fetch to /api. src/server/*.functions.ts is the real interface for saves, collection and workspace membership, documents, and profiles. File upload is the deliberate exception: the browser sends its native File in FormData to one authenticated Server Route.

HTTP endpoints exist only for callers that cannot invoke a Server Function: the external MCP clients, <img>/<video> tags, and the auth library. Adding one for the web UI means adding a second implementation of something the Server Function already does.

This is not hypothetical. #242 built a full REST surface for saves and membership so the CLI, MCP, and browser could share it. The CLI was retired, MCP calls the domain services directly, and the browser kept its Server Functions — so every one of those endpoints ended up with zero callers and was removed. Reach for a Server Function first.

Each private endpoint enforces its own request boundary. /api/upload combines route-level CSRF middleware with an in-handler session check; media delivery routes perform their own authorization before returning a blob or redirect.

Endpoint Method Caller
/api/auth/* better-auth (sessions, OAuth, email OTP)
/api/mcp POST Anonymous external MCP clients; public search/read only
/api/upload POST Browser FormData upload — the route authenticates before parsing and enforces the exact native File.size
/api/media/asset/* GET <img src> for R2-backed blobs
/api/media/og/* GET <img src> for rehosted OG images
/api/media/video/* GET <video src>

The public MCP server is stateless and does not open a server-sent event stream. GET /api/mcp therefore returns 405 Method Not Allowed; clients send every JSON-RPC message as a new POST request.

jsonApiError in src/server/api/responses.ts is the single error shape:

{ "success": false, "error": { "message": "", "code": "OPTIONAL_CODE" } }

It sets Cache-Control: no-store. Successful responses get the same header via the global auth middleware in src/start.ts, which runs on every authenticated request.