An unofficial Python CLI for accessing Mi Fitness workout data and manually syncing activities that failed to reach Strava.
Mi Fitness is the Android app used by Xiaomi wearable devices. When a workout is recorded on the watch, the data is synced to the phone over Bluetooth/BLE and then uploaded to the Mi Fitness cloud under the user's Xiaomi account.
Mi Fitness officially supports third-party integrations such as Strava. In practice, workout activities are sometimes not uploaded to Strava even though they appear inside Mi Fitness.
I created this project because I wanted a way to manually sync workouts that were recorded in Mi Fitness but never made it to Strava.
This is mainly useful if you want to:
- Confirm that your workouts really exist in Mi Fitness cloud storage
- Inspect recent activities when Mi Fitness fails to push them to Strava
- Pull activity data yourself instead of waiting for the official sync to work
- Export workouts to GPX, TCX, or FIT for manual backup or re-upload
- Upload workouts directly to Strava with duplicate detection
Python 3.12+ is required.
Install in editable mode:
python -m pip install -e .Then run with:
mi-fitness-sync --helpOr without installing:
python main.py --helpBefore using any Strava commands you need a Strava API application:
- Go to https://www.strava.com/settings/api and create an application
- Set the Authorization Callback Domain to
localhost(just the domain — no protocol, no port, no path)
The CLI uses http://localhost:{port}/callback as the redirect URI. Strava only matches on the domain portion, so the callback domain setting must be exactly localhost.
# 1. Log in to your Mi / Xiaomi account (prompts for email and password)
mi-fitness-sync login
# 2. Check auth status
mi-fitness-sync auth-status
# 3. List recent workouts
mi-fitness-sync list-activities --limit 10
# 4. List activities and show which ones are already on Strava
mi-fitness-sync list-activities --limit 10 --strava
# 5. View normalized detail for one activity
mi-fitness-sync activity-detail sid:key:1717200000 --json
# 6. Export a workout
mi-fitness-sync export-activity sid:key:1717200000 --format gpx --output run.gpx
# 7. Authenticate with Strava (prompts for client ID and secret)
mi-fitness-sync strava-login
# 8. Upload a workout to Strava
mi-fitness-sync upload-to-strava sid:key:1717200000All credentials are entered via CLI arguments or interactive prompts — there are no environment variables.
Commands that access Mi Fitness accept --state-path to override the default auth state file location. Commands that access Strava accept --strava-token-path to override the default Strava token file location.
Logs into Xiaomi Passport for the Mi Fitness service and saves the auth state locally. Email and password can be passed as arguments or entered interactively when omitted. The password prompt does not echo input.
If Xiaomi requires extra verification, the CLI supports the interactive flows it can complete locally:
- Captcha challenges download an image to the local captcha directory and try to open it with the OS default image viewer
- Step-2 verification prompts for the email/SMS code in the terminal
- Browser or app notification approval is not automated; the CLI prints the verification URL when Xiaomi requires it
Examples:
mi-fitness-sync login
mi-fitness-sync login --email you@example.com --password your-passwordFlags:
| Flag | Description |
|---|---|
--email |
Mi / Xiaomi account email (prompted if omitted) |
--password |
Mi / Xiaomi account password (prompted securely if omitted) |
--state-path |
Override the persisted auth state file path |
Deletes the saved local Mi Fitness auth state.
mi-fitness-sync logoutFlags:
| Flag | Description |
|---|---|
--state-path |
Override the persisted auth state file path |
Shows the currently saved Mi Fitness auth state.
mi-fitness-sync auth-status
mi-fitness-sync auth-status --jsonFlags:
| Flag | Description |
|---|---|
--json |
Print the full auth state as JSON |
--state-path |
Override the persisted auth state file path |
Lists workout activities from the Mi Fitness cloud.
mi-fitness-sync list-activities --limit 10
mi-fitness-sync list-activities --since 2024-01-01 --json
mi-fitness-sync list-activities --since 2026-03-20 --strava
mi-fitness-sync list-activities --since 1717200000 --until 1719800000 --limit 50Flags:
| Flag | Description |
|---|---|
--since |
Inclusive start time (unix seconds or ISO-8601) |
--until |
Inclusive end time (unix seconds or ISO-8601) |
--limit |
Maximum activities to return (default: 20) |
--category |
Mi Fitness category filter string |
--country-code |
Two-letter country override (e.g. ID, GB, US); mapped to the Mi Fitness region automatically |
--strava |
Show whether each activity is already uploaded to Strava |
--strava-token-path |
Override the Strava token file path (used with --strava) |
--json |
Print activities as JSON |
--verbose |
Enable debug logging |
--state-path |
Override the persisted auth state file path |
When --country-code is omitted, the CLI uses automatic Mi Fitness region detection.
Fetches the normalized detail payload for one activity.
mi-fitness-sync activity-detail sid:key:1717200000
mi-fitness-sync activity-detail sid:key:1717200000 --country-code ID --jsonFlags:
| Flag | Description |
|---|---|
activity_id |
Activity ID from list-activities (positional, required) |
--country-code |
Two-letter country override |
--json |
Print normalized detail as JSON |
--no-cache |
Disable the local FDS binary cache |
--cache-dir |
Override the local FDS cache directory |
--verbose |
Enable debug logging |
--state-path |
Override the persisted auth state file path |
Detail lookup behavior:
- Prefers the richer workout JSON payload when Mi Fitness exposes it
- Checks the Mi Fitness cloud for downloadable binary workout data (FDS files containing GPS tracks, heart rate, etc.)
- Can synthesize detail from FDS sport samples or GPS track points when the JSON payload is missing
- Attaches recoverable FDS sport-report and recovery-rate data (not consumed by exporters)
- Fails if neither the JSON detail nor FDS binary data is available
Exports one activity to GPX, TCX, or FIT.
mi-fitness-sync export-activity sid:key:1717200000 --format gpx --output run.gpx
mi-fitness-sync export-activity sid:key:1717200000 --format tcx --output run.tcx.gz --gzip
mi-fitness-sync export-activity sid:key:1717200000 --format gpx --smooth-mode full --outlier-speed 7:30
mi-fitness-sync export-activity sid:key:1717200000 --format fitFlags:
| Flag | Description |
|---|---|
activity_id |
Activity ID from list-activities (positional, required) |
--format |
Export format: gpx, tcx, or fit (required) |
--output |
Destination file path (default: ~/.mi_fitness_sync/exports/<sanitized_title>_<local_start_time>.<format>) |
--gzip |
Gzip-compress the output |
--country-code |
Two-letter country override |
--no-cache |
Disable the local FDS binary cache |
--cache-dir |
Override the local FDS cache directory |
--no-smooth |
Disable GPS smoothing entirely |
--outlier-speed |
Max plausible speed for outlier detection, as km/h (for example 180 or 180kmh) or pace (for example 7:30 or 7:30/km) |
--smooth-mode |
Smoothing strategy: match (default, converge toward the Mi Fitness summary distance) or full (apply the strongest smoothing pass) |
--verbose |
Enable debug logging |
--state-path |
Override the persisted auth state file path |
Export behavior:
- GPX requires GPS track points in the detail payload
- TCX and FIT prefer track points but fall back to timestamped sport samples without coordinates
- GPS smoothing is enabled by default for exports that include GPS coordinates; it only adjusts latitude/longitude and leaves timestamps, heart rate, cadence, and other fields unchanged
matchmode tries to bring the smoothed GPS distance closer to the Mi Fitness summary distance, whilefullapplies the largest valid smoothing window after outlier cleanup- FIT generation is best-effort — only fields present in the Mi Fitness source are included
Authenticates with Strava using the OAuth2 authorization code flow. Opens a browser for authorization, then stores the tokens locally. Client ID and secret can be passed as arguments or entered interactively.
mi-fitness-sync strava-login
mi-fitness-sync strava-login --client-id YOUR_ID --client-secret YOUR_SECRETFlags:
| Flag | Description |
|---|---|
--client-id |
Strava API client ID (prompted if omitted) |
--client-secret |
Strava API client secret (prompted if omitted) |
--port |
Local port for the OAuth callback server (default: 5478) |
--strava-token-path |
Override the Strava token file path |
The flow:
- Starts a temporary local HTTP server on
localhost:{port} - Opens the Strava authorization page in your browser
- After authorization, Strava redirects to the local server with an auth code
- Exchanges the code for access and refresh tokens
- Saves tokens to
~/.mi_fitness_sync/strava/tokens.json
The CLI refreshes Strava access tokens automatically using the stored refresh token whenever the saved access token is near expiry.
Shows the currently saved Strava auth state.
mi-fitness-sync strava-statusFlags:
| Flag | Description |
|---|---|
--strava-token-path |
Override the Strava token file path |
Revokes the Strava access token via the Strava API and deletes the local token file. Exits cleanly if no tokens exist. Deletes local tokens even if remote revocation fails.
mi-fitness-sync strava-logoutFlags:
| Flag | Description |
|---|---|
--strava-token-path |
Override the Strava token file path |
Uploads one Mi Fitness activity to Strava as a FIT file. The FIT file is also saved locally.
mi-fitness-sync upload-to-strava sid:key:1717200000
mi-fitness-sync upload-to-strava sid:key:1717200000 --output run.fit
mi-fitness-sync upload-to-strava sid:key:1717200000 --smooth-mode full --outlier-speed 180kmh
mi-fitness-sync upload-to-strava sid:key:1717200000 --skip-duplicate-checkFlags:
| Flag | Description |
|---|---|
activity_id |
Activity ID from list-activities (positional, required) |
--output |
Local FIT file save path (default: ~/.mi_fitness_sync/exports/<sanitized_title>_<local_start_time>.fit) |
--country-code |
Two-letter country override |
--skip-duplicate-check |
Skip checking Strava for existing activities with a similar start time |
--strava-token-path |
Override the Strava token file path |
--no-cache |
Disable the local FDS binary cache |
--cache-dir |
Override the local FDS cache directory |
--no-smooth |
Disable GPS smoothing entirely before generating the FIT upload |
--outlier-speed |
Max plausible speed for outlier detection, as km/h or pace |
--smooth-mode |
Smoothing strategy: match (default) or full |
--verbose |
Enable debug logging |
--state-path |
Override the persisted Mi Fitness auth state file path |
Upload behavior:
- Fetches the activity detail from Mi Fitness
- Renders to FIT format with the same optional smoothing controls as
export-activity, then saves the file locally - Checks Strava for existing activities within ±5 minutes of the start time (unless
--skip-duplicate-checkis set) - If potential duplicates are found, prompts for confirmation before uploading
- Uploads the FIT file to Strava and polls until processing completes
- Prints the Strava activity URL on success
- The sport type is mapped from Mi Fitness to the closest Strava equivalent; unmapped types let Strava auto-detect
src/mi_fitness_sync/
activity/ Activity listing, detail, region routing, normalization
auth/ Xiaomi Passport auth and persistence
cli/ Command-line entry point
export/ GPX, TCX, and FIT rendering
fds/ Mi Fitness FDS binary parsing and caching
strava/ Strava OAuth2 auth and upload
tests/ Per-module test files mirroring the package structure
Example:
python -m mi_fitness_sync logoutBy default, Mi Fitness auth state is stored in the user profile under ~/.mi_fitness_sync/auth/auth.json.
You can override that location with --state-path.
Strava tokens are stored under ~/.mi_fitness_sync/strava/tokens.json. You can override that location with --strava-token-path.
When Xiaomi requires a captcha, the image is saved under ~/.mi_fitness_sync/captcha/ and the CLI tries to open it with your OS default image app. The file is deleted on a best-effort basis after you respond; if deletion fails, the CLI prints the saved path so you can remove it manually.
Both files contain sensitive credentials and should not be shared or committed to version control.
- This is an unofficial project and is not affiliated with Xiaomi, Mi Fitness, or Strava.
- Parts of the Xiaomi login flow had to be pieced together from app behavior and decompiled code.
- Xiaomi may change endpoints, cookies, signatures, or response formats at any time.
- Some accounts may require captcha or notification approval flows that are not fully automated here. Step-2 verification (SMS/email code) is supported interactively.
- Detail retrieval currently depends on the raw
huami_sport_recordpayload shape used by the Android app; Xiaomi may change that format without notice. - FDS download-url metadata does not guarantee the corresponding cloud object is still downloadable, so some workouts still cannot be enriched from FDS.
- GPX export is unavailable for workouts where Mi Fitness does not expose GPS track points.
- TCX and FIT require timestamped export points. The exporter uses GPS track points directly when they are present in the normalized detail and otherwise falls back to timestamped sport samples; if neither source exists, those exports are unavailable.
- FIT export is best-effort and may omit fields when Mi Fitness does not provide enough source data for a fuller activity file.
- The CLI accepts Mi account and Strava client credentials either on the command line or via interactive prompts.
- Command-line arguments may be persisted in shell history or process listings depending on your environment, so interactive prompts are safer on shared machines.
- The persisted Mi Fitness auth state and Strava token files contain sensitive session data and should be protected.
If you use this on a shared machine, treat the local auth state like a credential.
This project is licensed under the MIT License. See the LICENSE file for details.