mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 14:32:03 +00:00
Merge main into release/v1.27.0
Reconciles the v1.26.1 Fitbit setup-guide fix with the Google Health release: google-health.md stays the full runbook (with a pointer to the classic fitbit.md guide), the doc-parity guard now asserts the Google Health page against the google-health client's endpoints and scopes, and the changelog carries both sections.
This commit is contained in:
commit
5b63cb9ac9
6 files changed
+181
-11
No files matched your search
@@ -0,0 +1,99 @@
|
||||
# Fitbit integration (Fitbit & Pixel)
|
||||
|
||||
> **Experimental.** Field mapping is still being verified against live accounts,
|
||||
> so some values may be missing or change in a future update.
|
||||
|
||||
HealthLog reads Fitbit and Pixel Watch data through the **classic Fitbit Web
|
||||
API** (`api.fitbit.com`). The OAuth app is a **Fitbit developer app registered
|
||||
at [dev.fitbit.com](https://dev.fitbit.com/apps/new)** — **not** a Google Cloud
|
||||
OAuth client. Like WHOOP, credentials are brought per user: each user (or the
|
||||
operator on their behalf) registers their own Fitbit app and pastes the client
|
||||
id/secret into Settings. The internal integration key is `fitbit`.
|
||||
|
||||
> **Common mistake:** the field wants a **Fitbit** Client ID (a short numeric
|
||||
> id from dev.fitbit.com), **not** a Google Cloud client id ending in
|
||||
> `.apps.googleusercontent.com`. A Google Cloud client fails at the Fitbit
|
||||
> consent screen with `unauthorized_client — Invalid client_id`, because Fitbit
|
||||
> and Google Cloud are separate client registries.
|
||||
|
||||
> **Heads-up — September 2026 sunset.** Google is retiring the classic Fitbit
|
||||
> Web API in **September 2026** and moving to the Google Health API behind Google
|
||||
> sign-in. That replacement currently requires Google Restricted-scope brand
|
||||
> verification plus an annual third-party CASA security assessment, which does
|
||||
> not fit a self-hosted bring-your-own-credentials model, so the path beyond the
|
||||
> sunset is still being worked out. Until then the setup below is the supported
|
||||
> way to connect Fitbit.
|
||||
|
||||
## 1. Register a Fitbit developer app
|
||||
|
||||
1. Sign in at <https://dev.fitbit.com/apps/new> and register a new application.
|
||||
2. **OAuth 2.0 Application Type:**
|
||||
- **Personal** — the app only ever sees the **owner's own** Fitbit account,
|
||||
and gets intraday (per-minute) heart-rate/steps automatically. This is the
|
||||
right choice for a single-person self-host.
|
||||
- **Server** (or **Client**) — needed when the instance connects **other
|
||||
people's** accounts. Intraday/heart-rate access is then granted case by
|
||||
case via Fitbit's Intraday Data Access request form.
|
||||
3. **Callback URL:** `https://your-instance.example.com/api/fitbit/callback`
|
||||
— absolute, **HTTPS**, and matching the `redirect_uri` HealthLog sends (see
|
||||
the redirect-URI note below). One entry per environment you connect from.
|
||||
4. **Scopes:** tick the ones HealthLog requests (next section).
|
||||
5. Save. Fitbit issues a **Client ID** (short, numeric) and a **Client Secret**
|
||||
— keep the secret out of any chat log.
|
||||
|
||||
## 2. Scopes
|
||||
|
||||
HealthLog requests exactly these read scopes (space-separated on the wire):
|
||||
|
||||
```
|
||||
activity cardio_fitness heartrate oxygen_saturation profile respiratory_rate sleep weight
|
||||
```
|
||||
|
||||
The `temperature` scope is intentionally omitted — the classic skin-temperature
|
||||
reading is a baseline delta rather than an absolute value, so it has no honest
|
||||
canonical slot yet. The authorization uses the Authorization Code Grant **with
|
||||
PKCE (S256)**, which Fitbit recommends; HealthLog mints the `code_verifier`
|
||||
server-side and never exposes it to the browser.
|
||||
|
||||
## 3. Wire the credentials in Settings → Integrations
|
||||
|
||||
Credentials are stored per user, not in `.env`. Sign in as the target user, open
|
||||
**Settings → Integrations → Fitbit**, and paste the Client ID and Client Secret.
|
||||
They are encrypted AES-256-GCM at rest.
|
||||
|
||||
## 4. Connect
|
||||
|
||||
Click **Connect with Fitbit**. HealthLog redirects to the Fitbit consent screen
|
||||
(`www.fitbit.com/oauth2/authorize`), exchanges the returned code for an access +
|
||||
refresh token at `api.fitbit.com/oauth2/token`, stores them encrypted, and runs
|
||||
an initial sync. There is **no webhook** — Fitbit syncs on the safety-net cron
|
||||
pull. Classic Fitbit refresh tokens rotate (one-time use); HealthLog replaces the
|
||||
stored token on every refresh.
|
||||
|
||||
## Redirect URI is validated strictly
|
||||
|
||||
The resolved `redirect_uri` is asserted at connect time to be absolute, `https`
|
||||
(or `http` on localhost), to target exactly `/api/fitbit/callback`, and — when
|
||||
`FITBIT_REDIRECT_URI` is set alongside `NEXT_PUBLIC_APP_URL` — to share its
|
||||
origin. A misconfigured value fails closed rather than redirecting elsewhere.
|
||||
|
||||
## Optional instance env var
|
||||
|
||||
```env
|
||||
# Only when the callback origin differs from NEXT_PUBLIC_APP_URL:
|
||||
FITBIT_REDIRECT_URI="https://your-instance.example.com/api/fitbit/callback"
|
||||
```
|
||||
|
||||
The Fitbit client id/secret are per-user (Settings), not env vars.
|
||||
|
||||
## Source overlap
|
||||
|
||||
If Fitbit and another source (Withings, WHOOP, Apple Health) both supply the same
|
||||
vital — resting heart rate, blood oxygen, body temperature or respiratory rate —
|
||||
you may see both values until a future update settles on a single preferred
|
||||
source.
|
||||
|
||||
## Disconnect
|
||||
|
||||
The Settings **Disconnect** button revokes the stored tokens and removes the
|
||||
connection. Previously synced measurements stay in the database.
|
||||
@@ -10,7 +10,8 @@ The OAuth app is a **Google Cloud** OAuth client, not a Fitbit developer-console
|
||||
app. Credentials are brought **per user**: each user registers their own Google
|
||||
Cloud OAuth client and pastes the client id/secret into Settings. The internal
|
||||
integration key is `google-health`; it runs alongside the classic `fitbit`
|
||||
integration without touching it.
|
||||
integration without touching it. For the classic connection (a developer app
|
||||
registered on the Fitbit side), see [fitbit.md](./fitbit.md).
|
||||
|
||||
> **Beta.** Field mapping is still being verified against live Google Health
|
||||
> accounts, so some values may be missing or shift in a later update.
|
||||
|
||||
Reference in new issue
Block a user