fragJulia
Dev

API Documentation

17 route domains, auth patterns, rate limiting, tier-gating matrix, mobile-app surface, streaming pointer, security headers. No per-endpoint table — see §9.

Status: Current as of 2026-04-18 (repo commit 06d84cb). Audience: New contributors, mobile team, third-party integrator, BfArM reviewer. Authoritative source: apps/web/app/api/**. This doc describes patterns and domains, not every route — a hand-maintained per-endpoint table would drift on the first PR. §9 proposes auto-generation.


1. Domains

17 top-level route directories under apps/web/app/api/:

DomainPurposeAuth pattern
admin/Admin-only actions (moderation, user management)Supabase JWT + admin role check
chat/Julia AI chat — main + anon, history, export, cleanup (cron)Mixed (public anon, JWT auth, CRON_SECRET)
community/Forum posts, comments, categoriesSupabase JWT
documents/PHI document CRUD, Textract, simplification, export, shareSupabase JWT + document_storage feature gate
emails/Transactional email sends (Resend)Service-role / internal
feature-flags/Client fetches enabled flagsPublic or JWT (role-aware payload)
inbox/User notifications + messagesSupabase JWT
kliniken/Breast-center finder (DKG data)Supabase JWT + klinik_finder (Premium)
matchmaking/Breastfriend peer matchingSupabase JWT + breastfriend_matching (Premium)
moderation/Admin moderation flows + content analyzeSupabase JWT + admin role
newsletter/Subscribe / unsubscribe / confirm double-opt-inPublic + token-signed links
profil/User profile read/updateSupabase JWT (self)
stripe/Checkout, webhook, portalMixed (webhook = Stripe signature, others = JWT)
studies/Clinical trial matching + health (data staleness)Supabase JWT + studien_matching (Premium), health public
timeline/Behandlungszeitstrahl eventsSupabase JWT + behandlungszeitstrahl (Premium)
unterlagen/Document sharing layer on top of documents/Supabase JWT + document_storage
voice/LiveKit token mint + session metadataSupabase JWT

(Quilt §7 counted 14 domains. Delta: emails/, feature-flags/ are new; moderation/ was folded into admin/ in quilt.)

Total route files as of 2026-04-18: on the order of 55–60 across these directories. The exact count moves on every feature PR — trust git ls-files 'apps/web/app/api/**/route.ts' | wc -l over this doc.


2. Auth patterns

Four distinct patterns. Knowing which pattern a route uses tells you how to call it.

2.1 Supabase JWT Bearer (most common)

Authorization: Bearer <supabase-access-token>
  • Token obtained from Supabase Auth session on the client
  • Server-side helper: createClient() in apps/web/lib/supabase/server.tsgetUser()
  • Middleware: apps/web/lib/supabase/middleware.ts refreshes session + sets security headers
  • On missing / invalid: return 401 with {error: "Nicht authentifiziert"} (German messaging is consistent)

2.2 Admin-only

  • Still uses Supabase JWT, but route additionally checks a role in user_roles (or similar) table
  • Pattern: if (!isAdmin(user)) return 403
  • Current admin surfaces: admin/, moderation/, admin branches of community/

2.3 Public + Turnstile (planned, partial)

  • chat/anon/ uses a session cookie (fj_anon_chat_sid) + IP-based quotas, no JWT
  • Turnstile bot-check is planned per #358 — not yet in .env.example or route handlers
  • Once landed: public routes get a cf-turnstile-response body field verified server-side

2.4 CRON_SECRET

  • chat/cleanup (invoked by Vercel cron at 03:00 UTC)
  • Guard: Authorization: Bearer ${CRON_SECRET} — Vercel sets this automatically
  • Any new cron route must include this guard — don't rely on "nobody knows the path"

2.5 Stripe webhook signature (special)

  • stripe/webhook verifies Stripe-Signature header against STRIPE_WEBHOOK_SECRET
  • Returns 400 if signature invalid — never processes the event payload before verification

3. Rate limiting

Implementation: Upstash Redis sliding-window via apps/web/lib/rate-limit.ts.

Standard envelope on rate-limited routes:

headers:
  X-RateLimit-Limit:     <N>
  X-RateLimit-Remaining: <N - 1>
  X-RateLimit-Reset:     <unix-seconds>

Concrete limits (verified):

RouteLimitWindowKeyed on
POST /api/chat2060 sclient IP
POST /api/chat/anon10 per session + 3 sessions/IP/daysession + 24 hcookie + IP

Most other routes rely on Vercel edge + Supabase's built-in abuse protection. Formal rate limits are not yet applied to document CRUD, community, or inbox — flag as a hardening item.

Failure mode: Upstash outage → rate limiter fails open (documented in architecture §5). Trade-off: availability over strict enforcement. Not a DiGA blocker, but surface if asked.


4. Tier gating matrix

Which domains' routes call checkFeatureAccess() (server-side) — derived from apps/web/lib/subscription/types.ts:

FeatureRequired tierEnforcement site
chat_unlimitedFreeNo gate (available to all authed users)
chat_history_7d / _fullFree / Pluschat/history response filtering
document_storagePlusdocuments/*, chat (master.md injection), unterlagen/*
arztbrief_simplifyPlusdocuments/simplify
pdf_exportPlusdocuments/export, chat/export
community_fullPluscommunity/* write endpoints (read is free)
breastfriend_matchingPremiummatchmaking/*
klinik_finderPremiumkliniken/*
studien_matchingPremiumstudies/* (except studies/health public)
behandlungszeitstrahlPremiumtimeline/*

Full mechanics in subscription-auth-system.


5. Mobile app surface

The Expo app (apps/mobile/) currently uses only a small subset of the endpoints above. Known consumers:

  • voice/* — LiveKit token mint + session metadata
  • Selected chat/* — via the shared API client in packages/shared/

The shared createApiClient in packages/shared/ currently wraps ~7 endpoints. The remaining ~48 are web-only.

Gap to close if mobile feature-parity is a goal:

  • Community posting
  • Document upload (needs native file picker integration)
  • Timeline, kliniken, studien
  • Inbox

Track each as a separate issue — this doc just names the gap, doesn't schedule the work.


6. Request / response conventions

  • Content type: JSON for everything except streaming (text/event-stream for /api/chat)
  • Error envelope: { "error": "Human-readable German message" } for user-facing 4xx, generic { "error": "Ein Fehler ist aufgetreten" } for 5xx
  • Success envelope: resource-shaped (no global wrapper)
  • Pagination: cursor-based where needed, ?cursor=...&limit=... (enumerate as per-endpoint comes up)
  • Idempotency: not systematically enforced — document per-route as gotchas appear

7. Streaming (/api/chat)

Separate doc covers this in full: streaming.

Quick pointer:

  • AI SDK v6 streamText via DefaultChatTransport
  • SSE response (result.toUIMessageStreamResponse())
  • Client consumes via useChat from @ai-sdk/react
  • Server onFinish hook is currently stubbed — persistence is client-side. Tab-close during stream = messages lost.

8. Security headers

Applied by middleware (apps/web/lib/supabase/middleware.ts):

  • Strict-Transport-Security
  • X-Content-Type-Options: nosniff
  • X-Frame-Options: DENY
  • Referrer / Permissions-Policy baseline

Specific endpoints may layer additional headers (CSP is configured at the Vercel / Next config level — verify against next.config.mjs when hardening).


9. Why there's no per-endpoint table here

A hand-maintained table of 55+ endpoints with method / path / auth / rate-limit columns would be wrong on day one and get worse every PR. Two better options:

  • Introduce zod schemas for request / response of each route (many already exist for validation)
  • Use zod-to-openapi to produce /api/openapi.json at build time
  • Serve Swagger UI at /api/docs (internal auth gate — admin-only)
  • One source of truth: the route code itself. No drift.
  • Estimated effort: ~1 week to retrofit the top 15 routes, incremental after

Option B — Filesystem scanner

  • Script walks apps/web/app/api/**/route.ts, extracts exported HTTP verbs, returns a tabular MD block
  • Committed as docs/dev/api-routes.generated.md
  • Pre-commit hook regenerates on touch to app/api/**
  • Simpler than Option A, but doesn't give you request / response shapes

Recommendation: Option A. Zod schemas already exist for several routes; formalizing them delivers validation + docs in one pass.


#Relevance
#579Parent docs epic
#586Pillar B parent
#358Adds Turnstile + planned guardrail_events route
architectureHigher-level system view
subscription-auth-systemSource for §4 tier matrix
audit-systemWill wrap most routes in §3 audit events
apps/web/app/api/Authoritative route tree
apps/web/lib/rate-limit.ts§3 implementation
apps/web/lib/supabase/middleware.ts§2.1 + §8

Changelog

  • 2026-04-18 — Initial version. 17 domains identified (quilt §7 said 14; new: emails/, feature-flags/; moderation/ split out from admin/). Per-endpoint table intentionally omitted in favor of OpenAPI auto-gen recommendation (§9). Rate-limit config verified for /api/chat (20/60s); other routes unbounded.

On this page