Files
HealthLog/docs/integrations/strava.md
T
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

3.6 KiB

Strava integration

HealthLog reads your Strava activities and folds them into the same workout history as every other source. Strava is a workout source only — its API is activity-centric and exposes no sleep, recovery, body-composition or glucose data, so connecting it adds runs, rides, swims and the rest to your workout surface and nothing else.

The OAuth app is brought per user: each self-hoster registers their own Strava API application and pastes the client id/secret into Settings. Strava caps every newly-created app at "single-player mode" (an athlete capacity of 1) until you request a capacity increase — perfectly fine for a personal self-host, and the reason a single shared app cannot serve many operators.

What you get — and what you don't

Covered (one Workout row per activity): sport type, start and end time, moving duration, distance, elevation gain, average and maximum heart rate (when the activity recorded it), and calories. Power, cadence, the "trainer"/"commute" flags and the activity name are kept on the row's metadata for provenance but are not surfaced as training analytics — HealthLog treats Strava as another workout source, not a training-load platform.

Not covered: Strava exposes no daily metrics (steps, sleep, resting heart rate, weight, …). Those come from your wearable's own integration or from Apple Health / Google Health Connect.

Duplicate activities are collapsed automatically

If you record a run on an Apple Watch and it also lands in Strava, the same run arrives twice — once via Apple Health, once via Strava. HealthLog keeps one canonical row per logical workout using your source-priority ladder: by default the device-native capture (Apple Watch, WHOOP, Withings) wins over the Strava copy, because a Strava activity is often a re-upload with lower-fidelity or missing heart-rate data. A run you record only on Strava (phone GPS) survives untouched. You can promote Strava above the wearables in Settings → Sources if you prefer the Strava copy.

1. Register a Strava API application

  1. Sign in at https://www.strava.com/settings/api.
  2. Create an application. For Authorization Callback Domain enter the host of your HealthLog instance (the domain only, no scheme or path).
  3. Copy the Client ID and Client Secret.

2. Paste the credentials into HealthLog

  1. Open Settings → Integrations and find the Strava card.
  2. Paste the Client ID and Client Secret into the API Credentials fields and save. (An operator can instead configure a shared app via the STRAVA_CLIENT_ID / STRAVA_CLIENT_SECRET environment variables; per-user credentials always take precedence.)
  3. Click Connect Strava and approve the access request. HealthLog requests the activity:read_all scope so it can read every activity, including those you marked "only me".

3. First sync

The first history import runs in the background shortly after you connect; afterwards an hourly poll pulls new activities. Strava rotates its refresh token on every refresh, and HealthLog persists the rotated token each time, so the connection stays healthy without you re-authorising.

Disconnecting

Disconnecting revokes the grant at Strava and clears the stored tokens. Activities already imported stay in your history; reconnecting resumes the sync.