Files
HealthLog/docs/api
Marc-André Bombeck 75eb0637e6 feat(strava): add per-user BYO-OAuth workout source + Garmin indirect-path docs
Add Strava as a per-user BYO-OAuth workout source, mirroring the shipped
WHOOP/Oura/Polar pattern. Strava caps every new app at single-player mode, so
each self-hoster registers their own app and pastes the client id/secret into
Settings (falling back to a shared env app). Strava is a workout source only —
it exposes no daily metrics and no training-depth analytics are derived.

- Schema (migration 0233): per-user encrypted client id/secret + access/refresh
  tokens, plaintext athlete id, incremental cursor, and backfill marker on
  `User`; `STRAVA` added to `MeasurementSource`.
- Token model follows Oura's reactive-refresh-on-401 with a compare-and-set on
  the stored refresh ciphertext, because Strava rotates its refresh token on
  every refresh.
- Client over safeFetch: authorization-code + refresh, activity list + budget-
  aware per-activity detail (for calories/HR), honouring the 200/15min cap; each
  activity maps to one `Workout` row. Activity name/description stay in metadata
  as data, never surfaced or interpolated.
- Dedup reuses the existing source-agnostic two-layer picker: `STRAVA` slots
  into the workout ladder below the device-native wearables and above
  MANUAL/IMPORT, so an Apple-Watch + Strava twin collapses to one canonical row.
- Poll-cohort hourly sync (:17) + self-converging boot backfill, wired into the
  integration-sync registrar. Webhook deferred.
- OAuth routes (connect/callback/credentials/disconnect/status/test) with signed
  state, cross-account re-auth rejection, and a surgical `www.strava.com`
  connect-src gated to the Strava surfaces.
- Settings card + consolidated status envelope entry; env + compose whitelist;
  i18n across all six locales.
- Garmin: no direct connector is possible (business-partner-only). Add
  docs/integrations/garmin.md documenting the Apple Health / Google Health
  Connect path and Garmin's withheld proprietary scores, cross-linked from both
  platform docs, plus a non-connector info note in the Connections panel.

Regenerate docs/api/openapi.yaml for the new `STRAVA` MeasurementSource value.
2026-07-09 21:25:25 +02:00
..

HealthLog API Spec

openapi.yaml is the canonical OpenAPI 3.1 description for the HealthLog Next.js API. It is the source of truth for the native iOS client's DTO codegen.

The MCP server is documented separately — it is a distinct contract over JSON-RPC, not part of the REST OpenAPI spec:

Preview locally

Open an interactive Redoc preview in your browser:

npx @redocly/cli preview docs/api/openapi.yaml

Generate static HTML for sharing:

npx @redocly/cli build-docs docs/api/openapi.yaml --output docs/api/index.html

Validate

npx @redocly/cli lint docs/api/openapi.yaml

The spec is expected to be lint-clean (errors = 0). Some warnings are intentional:

  • no-server-example.com — http://localhost:3000 is the dev server.
  • operation-2xx-response / operation-4xx-response — webhook + redirect endpoints intentionally omit one or the other.
  • no-unused-components — ReminderPhase is exposed for client codegen even though no path response references it directly.

Layout

  • info — title, version (mirrored from package.json), description.
  • servers — production https://healthlog.bombeck.io plus http://localhost:3000 for local development.
  • tags — domain groupings (Auth, Measurements, Medications, Mood, Dashboard, Insights, Achievements, DoctorReport, Integrations, User, Tokens, Notifications, Onboarding, Health, Settings, Analytics, Export, AuditLog, Monitoring, Webhooks, Ingest, Admin).
  • securitySchemes:
    • bearerAuth — long-lived hlk_* API tokens issued via POST /api/tokens. Required on /api/ingest/medication.
    • sessionCookie — HttpOnly healthlog_session cookie set by the password and passkey login flows. Used by the web app and any iOS client running inside a web view.
  • components.schemas — DTOs for all routes plus the underlying Prisma models (e.g. Measurement, Medication, MedicationIntakeEvent, MoodEntry, User, DashboardLayout, ApiEnvelope, ApiError).
  • paths — all routes from src/app/api/**/route.ts, including the /api/admin/* family which is tagged Admin and marked internal.

Conventions

  • Successful responses use the envelope { data, error: null }.
  • Error responses use the envelope { data: null, error: '<message>' }.
  • Pagination uses limit + offset query parameters and a meta: { total, limit, offset } block on the response.
  • All timestamps are ISO 8601 / RFC 3339 (format: date-time) in UTC.
  • IDs are CUIDs (opaque strings).

Updating the spec

When adding or changing routes:

  1. Update the relevant route handler under src/app/api/**/route.ts.
  2. Mirror the change in openapi.yaml. Keep operationId camelCase and stable — the iOS codegen keys off it.
  3. Run npx @redocly/cli lint docs/api/openapi.yaml and resolve all errors. Warnings are tolerated when documented above.
  4. Bump info.version if the change is breaking for clients.