Self-hosted service
arpanghosh8453/fitbit-grafana avatar
arpanghosh8453/fitbit-grafana

fitbit-grafana: piping Fitbit health data into InfluxDB and Grafana

A script to fetch data from Fitbit servers using their API and store the data in a local influxdb database and visualize them with Grafana

927 stars82 forksPythonBSD-4-Clause

At a glance

What is it?
A Python script that polls the Fitbit API on a schedule, writes heart rate, sleep, steps and SpO2 readings into InfluxDB, and serves them through importable Grafana dashboards. The build is Docker-first and still leans on a Fitbit API that Google is replacing.
Who is it for?
fitbit-grafana is a good fit for a self-hosting tinkerer who wants one person's Fitbit history in a queryable local database and does not mind running two services and dealing with uid mismatches. It is the wrong choice for a multi-user product, for anyone who needs a supported data path after the Fitbit API is retired, or for anyone unwilling to hold OAuth client secrets and a refresh token.
Can I use it commercially?
Yes. BSD-4-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One Python entry point between the Fitbit API and InfluxDB

The tree is small on purpose: Fitbit_Fetch.py, compose.yml, a Dockerfile, a requirements.txt, a Grafana_Dashboard folder and an extra folder for supporting documents. The Dockerfile copies that single script into /app and starts it with CMD ["python", "Fitbit_Fetch.py"], so the collection logic is one file rather than a package with plugins.

The data flow is straightforward: the script queries the Fitbit servers through their API, writes the results into a local InfluxDB, and Grafana reads that database through a datasource. The dashboard is downstream of the database, not of the API. Nothing polls Fitbit at query time, so a Grafana panel only renders what the script has already fetched.

The collection side is scheduled, not manual. The feature list names automatic data collection, automated token refresh, historical data backfilling and rate limit aware data collection, and the pinned dependency schedule==1.2.0 in requirements.txt is what makes the recurring loop possible. The metrics named cover heart rate including intraday, hourly steps heatmap data, daily step count, sleep data and patterns, sleep regularity, SpO2, breathing rate, HRV, activity minutes and device battery level.

A measurement and schema reference lives in extra/influxdb_schema.md, while the dashboard JSON files are hosted in a separate public-fitbit-projects repository. That split keeps the main tree compact, but an operator has to pull dashboards from a second location before anything renders.

Three InfluxDB clients in one image, with uneven version support

The compose file accepts INFLUXDB_VERSION=1 and its comment says it supports 1 or 2 or 3, yet the feature list qualifies this as support for both InfluxDB 1.x and 2.x with limited support for 2.x. That caveat is the most useful warning in the setup notes, because the version number in the compose file looks more authoritative than it is.

The pinned dependencies explain why. influxdb==5.3.1 is the InfluxDB 1.x client, influxdb_client==1.39.0 is the 2.x client, and influxdb3-python==0.12.0 adds the 3.x path. Carrying three clients means the script chooses a backend at runtime rather than speaking a single wire protocol, and the compose file has to branch on version specific settings.

Version 3 has its own credential step. The admin access token goes into INFLUXDB_V3_ACCESS_TOKEN, and it is generated inside the container:

bash
docker exec influxdb influxdb3 create token --admin

The notes are blunt about what follows: the token cannot be viewed again because InfluxDB stores only a hash of it. An operator who loses that value has to issue a new one rather than recover the old one, which is why the step is worth doing once and storing carefully.

The uid mismatch between the fetch container and InfluxDB 1.11

The install steps are Docker-first. A folder named fitbit-fetch-data is created, a compose.yml is written into it, and two host folders named logs and tokens are mounted so the script can write its log file and its token file.

Ownership is where most installs stall. The fetch container runs its script as uid 1000, so the logs and tokens folders must be chowned to 1000 or the script hits read/write permission denied errors. The InfluxDB side has a different owner: the official image for v1.11.x runs under UID/GID 1500 and needs its own directory chowned accordingly.

bash
chown -R 1500:1500 influxdb

Two different uids for two containers in one stack is an ordinary operational cost, but the documentation is inconsistent about it. The install notes tell you to chown logs and tokens to uid 1000, while the compose file comments describe the same mounted folders as owned by user id 1500 for influxdb v1.11. One of those comments describes the InfluxDB directory rather than the mounted folders, and the result is that the same sentence in the file points at the wrong id. Anyone who follows the comments literally will get permission errors.

The compose file also states plainly that it creates an open read/write access InfluxDB database with no authentication, with INFLUXDB_ADMIN_ENABLED, INFLUXDB_ADMIN_USER and INFLUXDB_ADMIN_PASSWORD offered as optional extras following InfluxDB 1.8 guidelines. Health data on an open port is the default state, not the exception.

OAuth setup: a personal application and a one-time token bootstrap

Data collection starts with credentials, and the credential rules are strict. The Fitbit OAuth 2.0 Application Type must be personal, otherwise intraday heart rate and intraday steps fail with KeyError: 'activities-heart-intraday'. The Default Access Type should be Read Only, and the privacy and terms URLs only need to be valid URLs since they are not checked.

The redirect URL needs care. It should point somewhere that does not redirect to an existing page, because the redirected URL carries tokens. The docs suggest a dummy address such as http://localhost:8888 or http://localhost:8000, and the OAuth 2.0 tutorial is where the refresh token is collected.

That token is entered once into a throwaway container run, which writes the access and refresh token pair into the mapped tokens directory:

bash
docker pull thisisarpanghosh/fitbit-fetch-data:latest && docker compose run --rm fitbit-fetch-data

You paste the refresh token, wait for successful api requests in the stdout log, then exit with ctrl + c, which also removes the orphan container. After that the full stack starts detached and you watch it with:

bash
docker compose up -d
docker compose logs --follow

A subtlety worth knowing: the first two setup steps in the README are struck through, so the Fitbit application path is presented as legacy rather than recommended.

Datasource defaults and a heatmap panel that needs a second plugin

Once the stack runs, Grafana is reached at localhost:3000 and the database is added as a datasource. Because both services share the same compose network, the address to enter is http://influxdb:8086 rather than a host port. With the default compose settings the username is fitbit_user, the password is fitbit_password, and the database name is FitbitHealthStats. The connection test succeeds when it finds the measurements.

Dashboards are not bundled into the container image. You either download the JSON files from the Grafana_Dashboard folder of the separate public-fitbit-projects repository, with separate versions for InfluxDB v1 and v2, or you import them from Grafana dashboard cloud using import code 23088 for influxdb-v1 or 23090 for influxdb-v2. Picking the wrong code for your backend is the easiest way to end up with empty panels.

The heatmap panels need an extra plugin that is not in the base image. It can be requested at container creation time with the GF_PLUGINS_PREINSTALL environment variable:

bash
GF_PLUGINS_PREINSTALL=marcusolsson-hourly-heatmap-panel

The same value can be applied afterwards with a Grafana CLI plugin install inside the running container. Without it, the hourly steps and sleep regularity panels simply do not render.

A Google Health API provider mode sitting behind a deprecated Fitbit API

The largest strategic risk in this project is not in the code. The documentation states that the Fitbit Web API is being deprecated in favour of the Google Health API, and the project has been adapted with a Google provider mode and an OAuth token flow for migration.

The switch is an environment variable:

bash
HEALTH_API_PROVIDER=google

The compose file ships with HEALTH_API_PROVIDER=fitbit and the comment Keep fitbit until Google endpoint migration is complete, so migration is opt-in rather than automatic. The Google path needs a GOOGLE_CLIENT_ID and a GOOGLE_CLIENT_SECRET from Google Cloud setup, and a refresh token obtained from the parity tool Settings screen. The full procedure lives in extra/google-migration.md, which also covers historical fetch validation and troubleshooting.

The advice given for anyone starting now is to follow the Google migration guide first and use Google credentials instead of creating a legacy Fitbit-only OAuth app. That is a reasonable instruction, and it also means anyone who follows the older Fitbit path is signing up for credentials whose retirement is already announced. The Codeberg mirror and an alternative container image on Codeberg packages are offered as backup, which helps with access but does not change the API dependency.

Release history, licence and the real cost of keeping this running

Three tagged releases are visible: v1.0.1 on 2024-02-27, v1.1.0 on 2025-04-04 and v2.0.0 on 2026-04-20. The intervals between those dates are long and uneven, so nothing in the history supports a claim of a predictable release rhythm. The last push was on 2026-07-30, which is recent enough that the tree still receives work, and the migration work behind the Google provider mode is consistent with that.

The licence field on the repository says BSD-4-Clause. That is a permissive licence, though the four-clause variant retains the advertising clause and the no-endorsement clause, which is worth a legal review before any commercial redistribution rather than an assumption. The project also states that it is independent and not affiliated with, endorsed by, sponsored by or approved by Google LLC or Grafana Labs, and that Fitbit and Grafana are trademarks of their respective owners.

The upgrade cost sits in configuration, not code. Moving between InfluxDB major versions means changing a variable and accepting that 2.x support is explicitly limited. Moving providers means replacing your OAuth client credentials. Every one of those transitions is described in short documents under extra/, and the pinned Python dependencies are old enough that a rebuild against current releases would need testing that the release history does not record.

Editorial conclusion

fitbit-grafana is a good fit for a self-hosting tinkerer who wants one person's Fitbit history in a queryable local database and does not mind running two services and dealing with uid mismatches. It is the wrong choice for a multi-user product, for anyone who needs a supported data path after the Fitbit API is retired, or for anyone unwilling to hold OAuth client secrets and a refresh token. Before adopting it, check the Google Health API migration guide at extra/google-migration.md, confirm your InfluxDB major version against the limited 2.x support, and decide whether the dashboard import codes 23088 or 23090 match your backend.

Frequently asked questions

What does fitbit-grafana need before it can collect any data?

A Fitbit OAuth 2.0 application registered with the Application Type set to personal, otherwise intraday heart rate and steps requests fail with KeyError: 'activities-heart-intraday'. You also need a refresh token entered during the first container run, and Docker Compose to bring up the fetch script, InfluxDB and Grafana.

Which InfluxDB versions does fitbit-grafana support?

The feature list describes support for both InfluxDB 1.x and 2.x, with limited support for 2.x. The compose file accepts INFLUXDB_VERSION values of 1, 2 or 3, and the image ships three clients, influxdb 5.3.1, influxdb_client 1.39.0 and influxdb3-python 0.12.0. Dashboard import code 23088 targets InfluxDB v1 and 23090 targets v2.

Is fitbit-grafana affected by the deprecation of the Fitbit Web API?

Yes, and the project has adapted. It now offers a Google provider mode with an OAuth token flow, enabled by setting HEALTH_API_PROVIDER=google, with GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET and a refresh token from the parity tool Settings screen. New setups are advised to use the Google credentials path described in extra/google-migration.md.

Can I run fitbit-grafana without Docker?

The documented path is Docker. The image is built from python:3.10-slim, installs requirements.txt with python -m pip install -r requirements.txt, copies Fitbit_Fetch.py into /app and runs it as uid 1000. The setup notes do not give a standalone install procedure for running the script directly on a host.

Official sources

  1. arpanghosh8453/fitbit-grafana on GitHub
  2. Issues
  3. License: BSD-4-Clause
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/arpanghosh8453-fitbit-grafana.svg)](https://hysenlabs.com/projects/arpanghosh8453-fitbit-grafana)

Was this useful?