diff --git a/README.md b/README.md
index 04239df32..d342f07b8 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,7 @@
Self-hosted health tracker. Weight, blood pressure, glucose, mood, medications.
- Withings and Apple Health sync, multi-provider AI Insights you own, doctor-report PDF.
+ Withings, WHOOP, and Apple Health sync, multi-provider AI Insights you own, doctor-report PDF.
@@ -36,7 +36,7 @@
## What it is
-HealthLog is a self-hosted personal health tracker that runs from a single `docker compose up`. It covers the metrics most people actually log -- weight, blood pressure, pulse, body composition, blood glucose, sleep, mood, and medication compliance -- and brings them together in one dashboard with reference ranges from ESH 2023, ADA 2024, and NICE NG115. Withings devices sync automatically; an `export.zip` import folds your full Apple Health history into the same timeline; a native SwiftUI iOS client (public-beta via [TestFlight](https://testflight.apple.com/join/bucuTBpa)) streams HealthKit live; multi-provider AI Insights (BYOK or local) explain what the numbers mean; a doctor-report PDF generates client-side. EN/DE end-to-end. AGPL-3.0.
+HealthLog is a self-hosted personal health tracker that runs from a single `docker compose up`. It covers the metrics most people actually log -- weight, blood pressure, pulse, body composition, blood glucose, sleep, mood, and medication compliance -- and brings them together in one dashboard with reference ranges from ESH 2023, ADA 2024, and NICE NG115. Withings and WHOOP devices sync automatically over OAuth2; an `export.zip` import folds your full Apple Health history into the same timeline; a native SwiftUI iOS client (public-beta via [TestFlight](https://testflight.apple.com/join/bucuTBpa)) streams HealthKit live; multi-provider AI Insights (BYOK or local) explain what the numbers mean; a doctor-report PDF generates client-side. EN/DE end-to-end. AGPL-3.0.
> **Status**: active. New releases roughly weekly -- see [CHANGELOG](CHANGELOG.md). Current line: v1.10 — a derived-metrics tier (fitness-age, vascular-age delta, HRV balance, sleep score, readiness and recovery scores, an early-strain flag), each computed from your own measurements and citing its inputs, on top of the v1.7 health-record export (PDF + FHIR R4), flexible medication schedules, and full HealthKit metric coverage. The native iOS client is public-beta via [TestFlight](https://testflight.apple.com/join/bucuTBpa).
@@ -98,7 +98,7 @@ HealthLog is the right fit when you want your wearable and clinical numbers in o
**Apple Health import** -- Drop your iOS `export.zip` on the import page. A streaming parser handles multi-gigabyte archives (Zip64), folds every ``, ``, ``, and `` into the same timeline as your other metrics, and stays idempotent on re-upload. Per-type ingestion stats plus a live status endpoint so you can watch the progress on a long historical drain.
-**AI Coach + Insights** -- A conversational Coach grounded in your own data, a daily briefing, a weekly report, and a Health Score tile on the dashboard. Pick OpenAI, Anthropic Claude, ChatGPT via Codex device-OAuth (no API key needed), or any OpenAI-compatible local endpoint (Ollama, LM Studio, vLLM). BYOK or admin-shared. Feed the Coach a chosen set of data clusters (cardiovascular, body composition, activity, workouts, sleep, mood, glucose, medication, mobility, environment) with a soft budget cap that degrades the lowest-signal clusters first. Every claim links back to the measurements that produced it. Local endpoints keep all data on your network.
+**AI Coach + Insights** -- A conversational Coach grounded in your own data, a daily briefing, a weekly report, and a Health Score tile on the dashboard. Pick OpenAI, Anthropic Claude, ChatGPT via Codex device-OAuth (no API key needed), or any OpenAI-compatible local endpoint (Ollama, LM Studio, vLLM). BYOK or admin-shared. Feed the Coach a chosen set of data clusters (cardiovascular, body composition, activity, workouts, sleep, mood, glucose, medication, mobility, environment) with a soft budget cap that degrades the lowest-signal clusters first. Every claim links back to the measurements that produced it. The Coach now reads your history longitudinally — a narrative of how a metric has moved and a per-metric trajectory framing — so an answer reflects the direction of travel rather than only the latest reading. Local endpoints keep all data on your network.
**Derived wellness metrics** -- A transparent metrics tier computed from your own measurements: a fitness-age band from VO₂max, a vascular-age delta, an HRV-balance band, a sleep score, a daily readiness and a stored recovery score, plus a coincident-deviation early-strain flag. Each one re-frames or blends signals you already recorded against age/sex norms or your personal baseline, cites the inputs and the published method behind it, and returns "not enough data" rather than a fabricated value when its minimum inputs are missing. The recovery score persists as a daily 0–100 series the dashboard, charts, and native client read without recomputing. These are descriptive wellness framings — not clinical or training-grade assessments.
@@ -106,6 +106,8 @@ HealthLog is the right fit when you want your wearable and clinical numbers in o
**Health-record export (PDF + FHIR R4)** -- `POST /api/export/health-record` produces a selectable export: an enriched clinical PDF, a machine-readable HL7 FHIR R4 document bundle (LOINC-coded observations, a BP panel, medication statements, a diagnostic report), or both packaged as one zip. The selection chooses date range and per-domain sections; both formats read the same aggregator so they describe identical numbers, and the optional AI summary is an explicit opt-in section marked as not clinically validated. Optional patient identity (name, insurer, insurance number) on Account feeds the report cover and the FHIR `Patient`.
+**Clinician-grade FHIR API + shareable record** -- A read-only HL7 FHIR R4 REST API (`GET /api/fhir/{metadata,Patient,Observation,MedicationStatement,MedicationAdministration,$everything}`) lets a clinician system pull your record in a standard shape, gated behind a dedicated `fhir:read` token scope. Alongside it, a shareable clinician record: create a scoped, time-limited share link, hand a clinician the read-only `/c/` view, and revoke it when the visit is over. The two are not yet wired together — a share link does not expose the FHIR API (`capabilities.share.fhirApi=false`) — but each stands on its own.
+
**Built-in Feedback** -- Send bug reports and feature requests from inside the app. Stored in your HealthLog database — no GitHub config required. Optional GitHub escalation for admins.
**PWA with Offline Support** -- Installable on iOS and Android. Service worker with intelligent caching strategies for reliable offline access. A paginated, opaque-cursor sync delta feed (`GET /api/sync/changes`) with measurement tombstones lets native and offline clients reconcile against the server incrementally.
@@ -213,6 +215,8 @@ HealthLog is designed for people who take data ownership seriously.
| `WITHINGS_CLIENT_SECRET` | Withings OAuth2 client secret |
| `WITHINGS_REDIRECT_URI` | OAuth callback URL |
| `WITHINGS_WEBHOOK_SECRET` | Webhook URL hardening secret |
+| `WHOOP_REDIRECT_URI` | WHOOP OAuth callback URL (client id/secret set in Settings) |
+| `WHOOP_WEBHOOK_SECRET` | HMAC secret for WHOOP webhook signature verification |
| `TELEGRAM_WEBHOOK_SECRET` | Telegram bot webhook secret |
Telegram bot token, ntfy settings, Web Push VAPID keys, Umami, and GlitchTip URLs are configured in the **Admin Panel** and stored encrypted in the database.
@@ -437,6 +441,24 @@ All mutations require authentication via session cookie. External ingest uses Be
+
+FHIR + clinician sharing (v1.11)
+
+Read-only HL7 FHIR R4 REST API, gated behind the `fhir:read` token scope:
+
+| Method | Endpoint | Description |
+| ------ | -------------------------------------- | ---------------------------------------------------- |
+| `GET` | `/api/fhir/metadata` | FHIR CapabilityStatement |
+| `GET` | `/api/fhir/Patient` | Patient resource for the token's user |
+| `GET` | `/api/fhir/Observation` | Vitals + labs as LOINC-coded observations |
+| `GET` | `/api/fhir/MedicationStatement` | Medication regimen |
+| `GET` | `/api/fhir/MedicationAdministration` | Logged intake events |
+| `GET` | `/api/fhir/$everything` | Bundle of every resource above |
+
+Clinician share-link lifecycle — create a scoped, time-limited link, hand a clinician the read-only `/c/` view, and revoke it after the visit. Share links do not expose the FHIR API (`capabilities.share.fhirApi=false`).
+
+
+
---
## Integrations
@@ -444,6 +466,7 @@ All mutations require authentication via session cookie. External ingest uses Be
| Integration | Setup | Purpose |
| --------------- | ------------- | ---------------------------------------- |
| **Withings** | Env vars | Auto-sync weight, BP, and activity |
+| **WHOOP** | User Settings | OAuth2 sync of recovery, strain, and sleep (BYO-keys) |
| **Telegram** | Admin Panel | Medication reminders with inline buttons |
| **ntfy** | User Settings | Self-hosted push notifications |
| **Web Push** | Admin Panel | Browser-native VAPID notifications |
@@ -508,6 +531,8 @@ For a single-process default the same container hosts both the web and worker (`
| **v1.8 – v1.10** (current) | Insights redesign with selectable time ranges (v1.8 – v1.9), then a derived-metrics tier (v1.10): fitness-age, vascular-age delta, HRV balance, sleep score, readiness and recovery scores, and a coincident-deviation early-strain flag — each computed from your own measurements, each citing its inputs and degrading to "not enough data" rather than fabricating a value. |
| **v2.x** (planned) | Multi-tenant hardening, expanded device passthrough (Garmin / Polar), opt-in cross-user aggregate research mode (off by default; never enabled without explicit consent). |
+Two known limitations carry forward from v1.11: dedup across two sources of the same standard vital is deferred (both rows persist, source priority decides display), and the coach's durable conversation-summary / remembered-facts layer is deferred — each answer reasons from your data afresh rather than from a running memory.
+
The detailed changelog lives in [`CHANGELOG.md`](CHANGELOG.md).
---
diff --git a/docs/integrations/whoop.md b/docs/integrations/whoop.md
new file mode 100644
index 000000000..10a87f629
--- /dev/null
+++ b/docs/integrations/whoop.md
@@ -0,0 +1,71 @@
+# WHOOP integration
+
+HealthLog connects to WHOOP via OAuth2 plus HMAC-signed webhooks for
+near-real-time sync of recovery, strain, sleep, and the underlying
+heart-rate / HRV signals. Unlike Withings, WHOOP credentials are
+brought per user: each user (or the operator on their behalf)
+registers their own WHOOP developer app and pastes the client
+id/secret into Settings.
+
+## Why bring your own keys
+
+WHOOP caps the number of users a single developer app may authorise.
+Per-user BYO-keys sidestep that per-app cap: every user authorises
+against their own WHOOP app, so a shared HealthLog instance never
+runs into one app's user ceiling. It also keeps each user's WHOOP
+grant scoped to credentials they control.
+
+## 1. Register a WHOOP developer app
+
+1. Sign in at and create a new app.
+2. **Redirect URI:** `https://your-instance.example.com/api/whoop/callback`
+ (matches `WHOOP_REDIRECT_URI`, below). Add one entry per
+ environment you connect from.
+3. Request the scopes for recovery, cycle/strain, sleep, and the
+ workout/heart-rate reads the sync uses.
+4. Save the app. WHOOP issues a **Client ID** and **Client Secret** —
+ keep the secret out of any chat log; it grants read access to the
+ linked account's full WHOOP history.
+
+## 2. Wire the credentials in Settings → Integrations
+
+WHOOP credentials are stored per user, not in `.env`. Sign in as the
+target user, open **Settings → Integrations → WHOOP**, and paste the
+client ID and secret. They are encrypted AES-256-GCM at rest.
+
+## 3. Connect
+
+Click **Connect WHOOP**. The flow:
+
+1. The Connect button redirects to WHOOP's OAuth authorize endpoint.
+2. The user signs in on WHOOP and grants the requested scopes.
+3. WHOOP redirects back to `/api/whoop/callback` with an auth code.
+4. HealthLog exchanges the code for an access + refresh token, stores
+ them encrypted, registers the webhook, and triggers an initial
+ sync of recent history.
+5. Subsequent syncs run on every webhook delivery plus a safety-net
+ cron pull.
+
+## Optional instance env vars
+
+Two server-level env vars tune the OAuth callback and webhook
+verification. They are optional — the per-user client id/secret in
+Settings drive the connection itself.
+
+```env
+WHOOP_REDIRECT_URI="https://your-instance.example.com/api/whoop/callback"
+WHOOP_WEBHOOK_SECRET="$(openssl rand -hex 32)"
+```
+
+- `WHOOP_REDIRECT_URI` — the OAuth callback URL handed to WHOOP. Must
+ match a redirect URI registered in the WHOOP developer app.
+- `WHOOP_WEBHOOK_SECRET` — the secret HealthLog verifies the WHOOP
+ webhook HMAC signature against. WHOOP signs each webhook body; the
+ handler validates the signature before processing. Generate a fresh
+ random value and restart the `app` container after setting it.
+
+## Disconnect
+
+The Settings page **Disconnect** button revokes the stored tokens and
+removes the connection. Existing measurements synced from WHOOP stay
+in the database — disconnect does not delete data.
diff --git a/docs/ops/deploy.md b/docs/ops/deploy.md
index e22a1d7f7..822748a72 100644
--- a/docs/ops/deploy.md
+++ b/docs/ops/deploy.md
@@ -30,6 +30,20 @@ step 2 is redundant in normal Coolify flow. It's listed explicitly for
the manual case so an operator can verify the migration result before
flipping traffic.
+### v1.11.0 notes
+
+Migrations `0110`–`0114` are additive (new tables / columns for the
+WHOOP provider hub, the longitudinal coach, and the clinician FHIR
+record). They require no special steps — `prisma migrate deploy` runs
+them in order on boot like any other.
+
+WHOOP introduces two optional instance env vars, `WHOOP_REDIRECT_URI`
+and `WHOOP_WEBHOOK_SECRET` (HMAC secret for WHOOP webhook signature
+verification). Per-user WHOOP client id/secret live in Settings, not
+in the environment. See `docs/integrations/whoop.md`. Add the two vars
+to the compose `environment:` whitelist if you set them — vars not on
+the whitelist never reach the container.
+
### What goes wrong if you reverse it
If the new app container starts before migrations run, the legacy
diff --git a/docs/self-hosting/getting-started.md b/docs/self-hosting/getting-started.md
index 8fc7244de..e3d02362e 100644
--- a/docs/self-hosting/getting-started.md
+++ b/docs/self-hosting/getting-started.md
@@ -151,6 +151,7 @@ NEXT_PUBLIC_DASHBOARD_SNAPSHOT=false pnpm build
| Off-host encrypted backups to S3/R2/B2 | `docs/ops/backup-restore.md` |
| Encryption-key rotation procedure | `docs/ops/encryption-key-rotation.md` |
| Withings device sync | `docs/integrations/withings.md` |
+| WHOOP device sync (per-user BYO-keys) | `docs/integrations/whoop.md` |
| Apple Health `export.zip` import | `docs/integrations/apple-health.md` |
| AI provider setup (OpenAI / Anthropic / local / ChatGPT OAuth) | `docs/integrations/ai-providers.md` |