Skip to main content

Sync Eufy smart scale body composition data to Garmin Connect and Strava

Project description

eufy-sync

PyPI Downloads Python License

Syncs body composition data from a Eufy smart scale to Garmin Connect, Strava, and Zwift. Weight, body fat %, muscle mass, bone mass, hydration, BMR, visceral fat, and metabolic age all come through to Garmin. Strava and Zwift get weight updates.

macOS only. Requires Python 3.12+ and a terminal. Setup is guided - you just answer a few prompts.

Sync targets

Target What gets synced API stability
Garmin Connect Full body composition (FIT upload) Stable
Strava Current weight (PUT /athlete) Stable
Zwift Current weight (PUT /api/profiles/me) Unofficial, may break

Zwift has no public API for third-party tools. eufy-sync uses a community-reverse-engineered endpoint that could change with any Zwift release. If Zwift breaks, the other two targets keep working.

The problem

Eufy scales sync to Apple Health, Fitbit, and Google Fit - but not Garmin or Strava. If you use either for training, your body comp data is stuck in a separate app. This fixes that.

How Garmin login works

Garmin has no official API for writing body composition into Connect. In March 2026 it put Cloudflare in front of its login, which broke the Python libraries that talked to it; garth was deprecated and stays that way.

eufy-sync logs in through python-garminconnect, which gets past Cloudflare without a browser. On first run you enter your Garmin email and password in the terminal, and a code if your account uses two-factor. If that direct login is rate-limited or temporarily blocked, eufy-sync falls back to a one-time browser login: a Chromium window opens, you sign in, and it continues. Either way the tokens are saved to your system keychain and refresh on their own, so later runs need no login. Headless setups (a server or scheduled job) use the direct login only, since there is no screen for a browser.

Install

You need Python 3.12+, a Eufy scale with cloud sync, and a Garmin Connect and/or Strava account.

First, install pipx if you don't have it:

brew install pipx

Or if you don't use Homebrew: pip3 install pipx

Then install and run:

pipx install eufy-sync
eufy-sync

Python older than 3.12? eufy-sync needs Python 3.12+. The simplest path is uv: uv tool install eufy-sync fetches a compatible Python for you, with no changes to your system Python. Or install 3.12 with brew install python@3.12 and run pipx install --python python3.12 eufy-sync.

Setup is guided on first run - choose your sync targets (Garmin, Strava, or both), enter your credentials, and your data syncs automatically.

Note: If you've cloned this repo, run pipx commands from outside the repo directory to avoid path conflicts (e.g., cd /tmp && pipx install eufy-sync).

Usage

eufy-sync                      # sync new measurements to all configured targets
eufy-sync --status           # check last sync + token health
eufy-sync --dry-run          # preview without uploading
eufy-sync --setup-strava     # connect Strava (add to existing setup)
eufy-sync --setup-zwift      # connect Zwift (add to existing setup)
eufy-sync --select-profile   # choose which Eufy profile to sync (shared scale)
eufy-sync --reauth           # re-login to all targets
eufy-sync --reauth garmin    # re-login to Garmin only
eufy-sync --reauth strava    # re-authorize Strava only
eufy-sync --update-password  # change stored passwords
eufy-sync --backfill-days 30 # sync last 30 days
eufy-sync --verbose          # show detailed sync logs
eufy-sync --install-agent   # set up automatic sync
eufy-sync --uninstall-agent # remove automatic sync
eufy-sync --uninstall       # remove all data and clean up

Updating

The tool checks for updates weekly and will let you know when a new version is available. To update:

pipx install --force eufy-sync

Automatic sync (macOS)

On first run, you'll be asked if you want to sync automatically every 4 hours. If you say yes, a macOS Launch Agent is installed that runs in the background - weigh yourself, open your laptop later, and it syncs on its own.

Logs go to ~/.garmin-sync/sync.log. You get a macOS notification if something fails.

To disable: eufy-sync --uninstall-agent

Adding Strava

If you already have Garmin set up and want to add Strava:

  1. Create a Strava API application at https://www.strava.com/settings/api
  2. Set 'Authorization Callback Domain' to localhost
  3. Run eufy-sync --setup-strava and enter your Client ID and Secret
  4. Authorize in the browser when it opens

Future syncs will update both Garmin and Strava automatically. Strava receives weight only (no body composition - Strava's API limitation).

How it works

                                                  /--> garmin_client.py --> Garmin Connect
Eufy Cloud API --> eufy_client.py --> transform.py     (FIT file + upload)
(fetch history)    (auth + pull)     (filter, dedup) \
                                          |           \--> strava_client.py --> Strava
                                      state.db              (weight update)
                                   (sync watermark)
  1. Authenticate to Eufy cloud API, pull measurement history
  2. Check local SQLite DB for what's already been synced (per target)
  3. For Garmin: check for existing entries on the same date (multi-machine dedup)
  4. Generate a FIT binary file and upload to Garmin Connect
  5. Update athlete weight on Strava
  6. Record syncs in DB

Security

Your passwords and OAuth tokens are stored in your system keychain (macOS Keychain) - not in plaintext files. Config files in ~/.garmin-sync/ only contain email addresses and Strava API app credentials, with 600 permissions. Credentials are only sent to Eufy, Garmin, and Strava's own servers over HTTPS. They are never logged, uploaded, or transmitted anywhere else. The only other outbound call is a weekly version check to pypi.org (no credentials sent). You can verify this yourself - the codebase is small and the outbound calls are in eufy_client.py, garmin_auth.py, strava_client.py, and the update checker in cli.py.

On systems without keychain support (headless Linux), credentials fall back to file-based storage with 600 permissions.

Known quirks

The Eufy cloud API returns weight at ~0.05 kg resolution, which can differ from what the Eufy app shows (the app may read from Bluetooth with higher precision). Most days match within 0.1 lbs, but some readings can be off by up to ~0.5 lbs. Displaying in lbs on Garmin adds a bit more rounding from the kg conversion.

If more than one person uses the same Eufy account, the tool asks which profile is yours during setup, so only your weigh-ins sync. If you set it up before this was added, run eufy-sync --select-profile once. Until you choose, a sync that sees several profiles stops and shows them rather than guessing whose weight to upload. If you are setting your profile after an earlier version already synced someone else's weight, run eufy-sync --backfill-days 30 once afterward to pull any of your own weigh-ins that were skipped.

The Eufy cloud only returns a weigh-in after the Eufy phone app has processed it. If you step on the scale but a sync finds nothing, open the Eufy app once so it uploads to the cloud, then run eufy-sync again. The tool cannot trigger that upload on its own, so this shows up most on headless or scheduled setups where the app is rarely opened.

Tests

pytest tests/ -v

Disclaimer

Uses unofficial APIs for Eufy and Garmin, and the official Strava API. Could break if any of these companies change things. Use at your own risk.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

eufy_sync-1.7.7.tar.gz (53.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

eufy_sync-1.7.7-py3-none-any.whl (41.6 kB view details)

Uploaded Python 3

File details

Details for the file eufy_sync-1.7.7.tar.gz.

File metadata

  • Download URL: eufy_sync-1.7.7.tar.gz
  • Upload date:
  • Size: 53.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for eufy_sync-1.7.7.tar.gz
Algorithm Hash digest
SHA256 9756edbaa99173fc8682522208bd8d42bd8ea3ae3b2374dfb214c7d35132acdf
MD5 2337c279b93fde0a1d0da1d344a5b3fc
BLAKE2b-256 2d24f0bfaac72029c9cab84d2a0a81ee1cd91bdd22f7d027a73c86d1bb86fb53

See more details on using hashes here.

Provenance

The following attestation bundles were made for eufy_sync-1.7.7.tar.gz:

Publisher: publish.yml on sturimcode/eufy-sync

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file eufy_sync-1.7.7-py3-none-any.whl.

File metadata

  • Download URL: eufy_sync-1.7.7-py3-none-any.whl
  • Upload date:
  • Size: 41.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for eufy_sync-1.7.7-py3-none-any.whl
Algorithm Hash digest
SHA256 04a633a240ebd716162c95c97166ea24f54be5c062f2e6ffd3466c7b65755674
MD5 20951c65042651ba059eeacf5c323fad
BLAKE2b-256 959c5f494d1ff0d0e5ff5bed5fddb89fb10f8eea7edd1c97455af200bf1fc43c

See more details on using hashes here.

Provenance

The following attestation bundles were made for eufy_sync-1.7.7-py3-none-any.whl:

Publisher: publish.yml on sturimcode/eufy-sync

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page