Export Mi Fit and Zepp workout data
Introduction
The two most popular companion applications for the Mi Smart Band 5 are Mi Fit and Zepp. Both applications support the tracking of different kinds of workouts, however they do not allow the user to export the collected data for further analysis. Due to the General Data Protection Regulation (GDPR) users may download their personal data from both applications, but the resulting archive does not contain workout specific data. The Zepp application also allows the user to export workouts one-by-one in .gpx format, but it does not contain all the available data and it is infeasible to export thousands of workouts manually.
API
Both applications synchronize the workouts to the cloud, which should make it easy to investigate the HTTP requests and responses. My favorite web debugging proxy tool is Telerik’s Fiddler which is able to decrypt HTTPS traffic originating from mobile devices. There is also a good tutorial available which explains how to set up your devices.
Application token
The endpoints of the API require a user context for which an authorization token is needed. There are at least two ways to extract the token, for which you will need to log in to the application first.
If you have root access on your Android device, you can find the token at /data/data/com.xiaomi.hm.health/shared_prefs/hm_id_sdk_android.xml
(Mi Fit) or /data/data/com.huami.watch.hmwatchmanager/shared_prefs/hm_id_sdk_android.xml
(Zepp). These files can be accessed via a file manager, or via ADB shell (Android 11+).
If you do not have root access, you can use Fiddler or HTTP Toolkit to analyze the requests sent by the application, which contain the exact same apptoken
header, which is required by the endpoints discussed below.
Workout history
By analyzing the traffic of the application, it turned out the following endpoint returns the metadata of all workouts:
https://api-mifit-de2.huami.com/v1/sport/run/history.json
It requires the following header:
Key | Example value |
---|---|
apptoken | DQVBQE…WHtrY |
And the following GET parameter:
Key | Example value |
---|---|
source | “run.mifit.huami.com” |
In Python:
def get_history():
r = requests.get('https://api-mifit-de2.huami.com/v1/sport/run/history.json', headers={
'apptoken': token
}, params={
'source': 'run.mifit.huami.com',
})
r.raise_for_status()
return r.json()
The response is a list of metadata corresponding to the workouts that were recorded in the requested interval. The format of a workout metadata is as follows (the values were redacted):
{
"code": 1,
"message": "success",
"data": {
"summary": [
{
"trackid": "1234567890",
"source": "run.mifit.huami.com",
"dis": "0.0",
"calorie": "0.0",
"end_time": "0",
"run_time": "0",
"avg_pace": "0.0",
"avg_frequency": "0.0",
"avg_heart_rate": "0.0",
"type": 0,
"location": "",
"city": "",
"forefoot_ratio": "",
"bind_device": "",
"max_pace": 0,
"min_pace": 0,
"version": 0,
"altitude_ascend": 0,
"altitude_descend": 0,
"total_step": 0,
"avg_stride_length": 0,
"max_frequency": 0,
"max_altitude": 0,
"min_altitude": 0,
"lap_distance": 0,
"sync_to": "",
"distance_ascend": 0,
"max_cadence": 0,
"avg_cadence": 0,
"landing_time": 0,
"flight_ratio": 0,
"climb_dis_descend": 0,
"climb_dis_ascend_time": 0,
"climb_dis_descend_time": 0,
"child_list": "",
"parent_trackid": 0,
"max_heart_rate": 0,
"min_heart_rate": 0,
"swolf": 0,
"total_strokes": 0,
"total_trips": 0,
"avg_stroke_speed": 0,
"max_stroke_speed": 0,
"avg_distance_per_stroke": 0,
"swim_pool_length": 0,
"te": 0,
"swim_style": 0,
"unit": 0,
"add_info": "",
"sport_mode": 0,
"downhill_num": 0,
"downhill_max_altitude_desend": 0
}
]
}
}
Workout detail
For each workout metadata the corresponding detail can be queried by using the following endpoint:
https://api-mifit-de2.huami.com/v1/sport/run/detail.json
It also requires the apptoken
header and the above-mentioned GET parameter with an additional identifier:
Key | Example value |
---|---|
trackid | 123456789 |
In Python:
def get_detail(track_id, source):
r = requests.get('https://api-mifit-de2.huami.com/v1/sport/run/detail.json', headers={
'apptoken': token
}, params={
'trackid': track_id,
'source': source,
})
r.raise_for_status()
return r.json()
The response contains the detail of the requested workout, including the location information. The format of a detailed workout is as follows (the values were redacted):
{
"code": 1,
"message": "success",
"data": {
"trackid": 1234567890,
"source": "run.mifit.huami.com",
"longitude_latitude": "0,0;...",
"altitude": "0;...",
"accuracy": "0;...",
"time": "0;...",
"gait": "0,0,0,0;...",
"pace": "0.0;...",
"pause": "",
"spo2": "",
"flag": "0;...",
"kilo_pace": "",
"mile_pace": "",
"heart_rate": "0,0;...",
"version": 0,
"provider": "",
"speed": "0,0.0;...",
"bearing": "",
"distance": "0,0;...",
"lap": "",
"air_pressure_altitude": "",
"course": "",
"correct_altitude": "",
"stroke_speed": "",
"cadence": "",
"daily_performance_info": "",
"rope_skipping_frequency": "",
"weather_info": "",
"coaching_segment": ""
}
}
Summary
An example Python implementation can be found on GitHub.