OAuth2 flow with all 11 Google Fitness scopes (activity, body, sleep, heart rate, nutrition, location, blood glucose/pressure/temperature, oxygen saturation, reproductive health). Stores access + refresh tokens; auto-refreshes on expiry. GoogleHealthSignalSource fetches steps, sleep sessions, active minutes, calories, and heart rate from the Fit aggregate + sessions APIs. Signals flow into both the tip orchestrator and the health-vitals pre-compute agent, which generates prompt snippets about step progress, sleep deficit, sedentary time, and elevated heart rate. Signal.kind extended with 'health'; IntegrationProvider extended with 'google-health'. Agent compute signal mapping enriched to include source, kind, and all features so health-vitals can filter its own signals. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
services/api
Express BFF that serves all client-facing routes, manages sessions, runs background signal sync, and proxies admin calls to ml/serving.
Contract
GET /health { ok: true }
POST /api/auth/login → redirect to Google OAuth
GET /api/auth/callback OAuth return URL
POST /api/auth/logout
GET /api/auth/session → { user? }
POST /api/auth/token { token } → set sid cookie (ADMIN_TOKEN auth)
GET /api/integrations list connected integrations
POST /api/integrations/todoist/connect start Todoist OAuth
GET /api/integrations/todoist/callback
DELETE /api/integrations/:provider disconnect
POST /api/recommend → { tip }
POST /api/tip/:id/feedback { action } → { ok }
GET /api/user/profile
DELETE /api/user account deletion
POST /api/push/subscribe
DELETE /api/push/subscribe
GET /api/admin/stats DAU/WAU, feedback breakdown
GET /api/admin/users user list with pagination
GET /api/user/:id user detail, consents, integrations
GET /api/admin/events recent event stream (ring buffer or NATS JetStream)
GET /api/admin/events/history historical event query (time range, filters)
GET /api/admin/sim/runs offline sim run list
POST /api/admin/sim/run launch offline sim with policy/judge params
GET /api/admin/sim/runs/:id/output tail sim stdout
GET /api/admin/features/:userId per-user profile features + freshness
GET /api/admin/features/:userId/context context features for last score call
POST /api/admin/policies list shadow policies + active policy
POST /api/admin/policies/:name/toggle enable/disable shadow policy
POST /api/admin/users/:id/actions revoke-integration, reset-bandit, rebuild-profile
GET /api/admin/health system health: api, ml/serving, db, bus, mlflow
GET /api/admin/docs admin documentation index
GET /api/ml/* admin-only proxy to ml/serving
Middleware stack (request order)
cors— origin limited toWEB_BASE_URLtracingMiddleware— reads or generates W3Ctraceparent; setsreq.traceId+req.traceparentpinoHttp— structured JSON request/response logs withtraceIdfield;/healthsuppressedexpress.json()/cookieParsersessionMiddleware— validatessidcookie, attachesreq.userId
Observability
Logs are structured JSON via pino. Every line includes traceId (extracted from the incoming W3C traceparent header, or generated fresh). The same traceparent is forwarded on all outbound HTTP calls to ml/serving so traces correlate end-to-end.
Sentry error capture is active when SENTRY_DSN is set.
Background tasks
- Todoist sync scheduler — runs every
TODOIST_SYNC_INTERVAL_MS(default 15 min); starts 10 s after boot to avoid startup surge. - Retention purge — deletes
tipScoresandtipFeedbackrows older than 30 days; runs on boot and daily. - Profile TTL invalidation — listens to
signals.task.syncedandsignals.tip.feedbackon the in-process Bus; invalidates cached user-level profile features so the next/recommendgets fresh values.
Config
| Env var | Default | Description |
|---|---|---|
PORT |
3001 |
Listen port |
NODE_ENV |
development |
Environment label |
DATABASE_PATH |
./data/oo.db |
SQLite file |
SESSION_SECRET |
required | Cookie signing secret |
GOOGLE_CLIENT_ID/SECRET |
required | OAuth |
TODOIST_CLIENT_ID/SECRET |
required | OAuth |
API_BASE_URL |
http://localhost:3001 |
Self-referential redirect URI |
WEB_BASE_URL |
http://localhost:3000 |
CORS + post-login redirect |
ML_SERVING_URL |
http://localhost:8000 |
ml/serving base URL |
NATS_URL |
`` | NATS broker; empty = in-process bus only |
TODOIST_SYNC_INTERVAL_MS |
900000 |
Background sync cadence |
TIP_PROMPT_VERSION |
`` | Prompt variant(s) for /generate |
LOG_LEVEL |
info |
pino log level |
SENTRY_DSN |
`` | Sentry DSN; empty = Sentry disabled |
VAPID_* |
Web push keys | |
ADMIN_TOKEN |
`` | Static token for service/Playwright admin auth; empty = disabled |
Health story
GET /health returns { ok: true }. No dependency checks — upstream deps (ml/serving, NATS) have their own health endpoints checked separately.
Extraction criteria
Extract to its own host when:
- Auth session management needs a dedicated Redis/PG session store, or
- Background sync load (Todoist, future connectors) displaces API serving on the shared host, or
- Team boundary emerges between auth/BFF and recommender orchestration.