# Garth
[](
https://github.com/matin/garth/actions/workflows/ci.yml?query=event%3Apush+branch%3Amain+workflow%3ACI)
[](
https://codecov.io/gh/matin/garth)
[](
https://pypi.org/project/garth/)
[](
https://pypistats.org/packages/garth)
Garmin SSO auth + Connect Python client
## Garmin Connect MCP Server
[`garth-mcp-server`](https://github.com/matin/garth-mcp-server) is in early development.
Contributions are greatly appreciated.
To generate your `GARTH_TOKEN`, use `uvx garth login`.
For China, do `uvx garth --domain garmin.cn login`.
## Google Colabs
### [Stress: 28-day rolling average](https://colab.research.google.com/github/matin/garth/blob/main/colabs/stress.ipynb)
Stress levels from one day to another can vary by extremes, but there's always
a general trend. Using a scatter plot with a rolling average shows both the
individual days and the trend. The Colab retrieves up to three years of daily
data. If there's less than three years of data, it retrieves whatever is
available.

### [Sleep analysis over 90 days](https://colab.research.google.com/github/matin/garth/blob/main/colabs/sleep.ipynb)
The Garmin Connect app only shows a maximum of seven days for sleep
stages—making it hard to see trends. The Connect API supports retrieving
daily sleep quality in 28-day pages, but that doesn't show details. Using
`SleedData.list()` gives us the ability to retrieve an arbitrary number of
day with enough detail to product a stacked bar graph of the daily sleep
stages.

One specific graph that's useful but not available in the Connect app is
sleep start and end times over an extended period. This provides context
to the sleep hours and stages.

### [ChatGPT analysis of Garmin stats](https://colab.research.google.com/github/matin/garth/blob/main/colabs/chatgpt_analysis_of_stats.ipynb)
ChatGPT's Advanced Data Analysis took can provide incredible insight
into the data in a way that's much simpler than using Pandas and Matplotlib.
Start by using the linked Colab to download a CSV of the last three years
of your stats, and upload the CSV to ChatGPT.
Here's the outputs of the following prompts:
How do I sleep on different days of the week?
<img width="600" alt="image" src="https://github.com/matin/garth/assets/98985/b7507459-2482-43d6-bf55-c3a1f756facb">
On what days do I exercise the most?
<img width="600" alt="image" src="https://github.com/matin/garth/assets/98985/11294be2-8e1a-4fed-a489-13420765aada">
Magic!
## Background
Garth is meant for personal use and follows the philosophy that your data is
your data. You should be able to download it and analyze it in the way that
you'd like. In my case, that means processing with Google Colab, Pandas,
Matplotlib, etc.
There are already a few Garmin Connect libraries. Why write another?
### Authentication and stability
The most important reasoning is to build a library with authentication that
works on [Google Colab](https://colab.research.google.com/) and doesn't require
tools like Cloudscraper. Garth, in comparison:
1. Uses OAuth1 and OAuth2 token authentication after initial login
1. OAuth1 token survives for a year
1. Supports MFA
1. Auto-refresh of OAuth2 token when expired
1. Works on Google Colab
1. Uses Pydantic dataclasses to validate and simplify use of data
1. Full test coverage
### JSON vs HTML
Using `garth.connectapi()` allows you to make requests to the Connect API
and receive JSON vs needing to parse HTML. You can use the same endpoints the
mobile app uses.
This also goes back to authentication. Garth manages the necessary Bearer
Authentication (along with auto-refresh) necessary to make requests routed to
the Connect API.
## Instructions
### Install
```bash
python -m pip install garth
```
### Clone, setup environment and run tests
```bash
gh repo clone matin/garth
cd garth
make install
make
```
Use `make help` to see all the options.
### Authenticate and save session
```python
import garth
from getpass import getpass
email = input("Enter email address: ")
password = getpass("Enter password: ")
# If there's MFA, you'll be prompted during the login
garth.login(email, password)
garth.save("~/.garth")
```
### Custom MFA handler
By default, MFA will prompt for the code in the terminal. You can provide your
own handler:
```python
garth.login(email, password, prompt_mfa=lambda: input("Enter MFA code: "))
```
For advanced use cases (like async handling), MFA can be handled separately:
```python
result1, result2 = garth.login(email, password, return_on_mfa=True)
if result1 == "needs_mfa": # MFA is required
mfa_code = "123456" # Get this from your custom MFA flow
oauth1, oauth2 = garth.resume_login(result2, mfa_code)
```
### Configure
#### Set domain for China
```python
garth.configure(domain="garmin.cn")
```
#### Proxy through Charles
```python
garth.configure(proxies={"https": "http://localhost:8888"}, ssl_verify=False)
```
### Attempt to resume session
```python
import garth
from garth.exc import GarthException
garth.resume("~/.garth")
try:
garth.client.username
except GarthException:
# Session is expired. You'll need to log in again
```
## Connect API
### Daily details
```python
sleep = garth.connectapi(
f"/wellness-service/wellness/dailySleepData/{garth.client.username}",
params={"date": "2023-07-05", "nonSleepBufferMinutes": 60},
)
list(sleep.keys())
```
```json
[
"dailySleepDTO",
"sleepMovement",
"remSleepData",
"sleepLevels",
"sleepRestlessMoments",
"restlessMomentsCount",
"wellnessSpO2SleepSummaryDTO",
"wellnessEpochSPO2DataDTOList",
"wellnessEpochRespirationDataDTOList",
"sleepStress"
]
```
### Stats
```python
stress = garth.connectapi("/usersummary-service/stats/stress/weekly/2023-07-05/52")
```
```json
{
"calendarDate": "2023-07-13",
"values": {
"highStressDuration": 2880,
"lowStressDuration": 10140,
"overallStressLevel": 33,
"restStressDuration": 30960,
"mediumStressDuration": 8760
}
}
```
## Upload
```python
with open("12129115726_ACTIVITY.fit", "rb") as f:
uploaded = garth.client.upload(f)
```
Note: Garmin doesn't accept uploads of _structured_ FIT files as outlined in
[this conversation](https://github.com/matin/garth/issues/27). FIT files
generated from workouts are accepted without issues.
```python
{
'detailedImportResult': {
'uploadId': 212157427938,
'uploadUuid': {
'uuid': '6e56051d-1dd4-4f2c-b8ba-00a1a7d82eb3'
},
'owner': 2591602,
'fileSize': 5289,
'processingTime': 36,
'creationDate': '2023-09-29 01:58:19.113 GMT',
'ipAddress': None,
'fileName': '12129115726_ACTIVITY.fit',
'report': None,
'successes': [],
'failures': []
}
}
```
## Stats resources
### Stress
Daily stress levels
```python
DailyStress.list("2023-07-23", 2)
```
```python
[
DailyStress(
calendar_date=datetime.date(2023, 7, 22),
overall_stress_level=31,
rest_stress_duration=31980,
low_stress_duration=23820,
medium_stress_duration=7440,
high_stress_duration=1500
),
DailyStress(
calendar_date=datetime.date(2023, 7, 23),
overall_stress_level=26,
rest_stress_duration=38220,
low_stress_duration=22500,
medium_stress_duration=2520,
high_stress_duration=300
)
]
```
Weekly stress levels
```python
WeeklyStress.list("2023-07-23", 2)
```
```python
[
WeeklyStress(calendar_date=datetime.date(2023, 7, 10), value=33),
WeeklyStress(calendar_date=datetime.date(2023, 7, 17), value=32)
]
```
### Body Battery
Daily Body Battery and stress data
```python
garth.DailyBodyBatteryStress.get("2023-07-20")
```
```python
DailyBodyBatteryStress(
user_profile_pk=2591602,
calendar_date=datetime.date(2023, 7, 20),
start_timestamp_gmt=datetime.datetime(2023, 7, 20, 6, 0),
end_timestamp_gmt=datetime.datetime(2023, 7, 21, 5, 59, 59, 999000),
start_timestamp_local=datetime.datetime(2023, 7, 19, 23, 0),
end_timestamp_local=datetime.datetime(2023, 7, 20, 22, 59, 59, 999000),
max_stress_level=85,
avg_stress_level=25,
stress_chart_value_offset=0,
stress_chart_y_axis_origin=0,
stress_values_array=[
[1689811800000, 12], [1689812100000, 18], [1689812400000, 15],
[1689815700000, 45], [1689819300000, 85], [1689822900000, 35],
[1689826500000, 20], [1689830100000, 15], [1689833700000, 25],
[1689837300000, 30]
],
body_battery_values_array=[
[1689811800000, 'charging', 45, 1.0], [1689812100000, 'charging', 48, 1.0],
[1689812400000, 'charging', 52, 1.0], [1689815700000, 'charging', 65, 1.0],
[1689819300000, 'draining', 85, 1.0], [1689822900000, 'draining', 75, 1.0],
[1689826500000, 'draining', 65, 1.0], [1689830100000, 'draining', 55, 1.0],
[1689833700000, 'draining', 45, 1.0], [1689837300000, 'draining', 35, 1.0],
[1689840900000, 'draining', 25, 1.0]
]
)
# Access derived properties
daily_bb = garth.DailyBodyBatteryStress.get("2023-07-20")
daily_bb.current_body_battery # 25 (last reading)
daily_bb.max_body_battery # 85
daily_bb.min_body_battery # 25
daily_bb.body_battery_change # -20 (45 -> 25)
# Access structured readings
for reading in daily_bb.body_battery_readings:
print(f"Level: {reading.level}, Status: {reading.status}")
# Level: 45, Status: charging
# Level: 48, Status: charging
# ... etc
for reading in daily_bb.stress_readings:
print(f"Stress: {reading.stress_level}")
# Stress: 12
# Stress: 18
# ... etc
```
Body Battery events (sleep events)
```python
garth.BodyBatteryData.get("2023-07-20")
```
```python
[
BodyBatteryData(
event=BodyBatteryEvent(
event_type='sleep',
event_start_time_gmt=datetime.datetime(2023, 7, 19, 21, 30),
timezone_offset=-25200000,
duration_in_milliseconds=28800000,
body_battery_impact=35,
feedback_type='good_sleep',
short_feedback='Good sleep restored your Body Battery'
),
activity_name=None,
activity_type=None,
activity_id=None,
average_stress=15.5,
stress_values_array=[
[1689811800000, 12], [1689812100000, 18], [1689812400000, 15]
],
body_battery_values_array=[
[1689811800000, 'charging', 45, 1.0],
[1689812100000, 'charging', 48, 1.0],
[1689812400000, 'charging', 52, 1.0],
[1689840600000, 'draining', 85, 1.0]
]
)
]
# Access convenience properties on each event
events = garth.BodyBatteryData.get("2023-07-20")
event = events[0]
event.current_level # 85 (last reading)
event.max_level # 85
event.min_level # 45
```
### Hydration
Daily hydration data
```python
garth.DailyHydration.list(period=2)
```
```python
[
DailyHydration(
calendar_date=datetime.date(2024, 6, 29),
value_in_ml=1750.0,
goal_in_ml=2800.0
)
]
```
### Steps
Daily steps
```python
garth.DailySteps.list(period=2)
```
```python
[
DailySteps(
calendar_date=datetime.date(2023, 7, 28),
total_steps=6510,
total_distance=5552,
step_goal=8090
),
DailySteps(
calendar_date=datetime.date(2023, 7, 29),
total_steps=7218,
total_distance=6002,
step_goal=7940
)
]
```
Weekly steps
```python
garth.WeeklySteps.list(period=2)
```
```python
[
WeeklySteps(
calendar_date=datetime.date(2023, 7, 16),
total_steps=42339,
average_steps=6048.428571428572,
average_distance=5039.285714285715,
total_distance=35275.0,
wellness_data_days_count=7
),
WeeklySteps(
calendar_date=datetime.date(2023, 7, 23),
total_steps=56420,
average_steps=8060.0,
average_distance=7198.142857142857,
total_distance=50387.0,
wellness_data_days_count=7
)
]
```
### Intensity Minutes
Daily intensity minutes
```python
garth.DailyIntensityMinutes.list(period=2)
```
```python
[
DailyIntensityMinutes(
calendar_date=datetime.date(2023, 7, 28),
weekly_goal=150,
moderate_value=0,
vigorous_value=0
),
DailyIntensityMinutes(
calendar_date=datetime.date(2023, 7, 29),
weekly_goal=150,
moderate_value=0,
vigorous_value=0
)
]
```
Weekly intensity minutes
```python
garth.WeeklyIntensityMinutes.list(period=2)
```
```python
[
WeeklyIntensityMinutes(
calendar_date=datetime.date(2023, 7, 17),
weekly_goal=150,
moderate_value=103,
vigorous_value=9
),
WeeklyIntensityMinutes(
calendar_date=datetime.date(2023, 7, 24),
weekly_goal=150,
moderate_value=101,
vigorous_value=105
)
]
```
### HRV
Daily HRV
```python
garth.DailyHRV.list(period=2)
```
```python
[
DailyHRV(
calendar_date=datetime.date(2023, 7, 28),
weekly_avg=39,
last_night_avg=36,
last_night_5_min_high=52,
baseline=HRVBaseline(
low_upper=36,
balanced_low=39,
balanced_upper=51,
marker_value=0.25
),
status='BALANCED',
feedback_phrase='HRV_BALANCED_2',
create_time_stamp=datetime.datetime(2023, 7, 28, 12, 40, 16, 785000)
),
DailyHRV(
calendar_date=datetime.date(2023, 7, 29),
weekly_avg=40,
last_night_avg=41,
last_night_5_min_high=76,
baseline=HRVBaseline(
low_upper=36,
balanced_low=39,
balanced_upper=51,
marker_value=0.2916565
),
status='BALANCED',
feedback_phrase='HRV_BALANCED_8',
create_time_stamp=datetime.datetime(2023, 7, 29, 13, 45, 23, 479000)
)
]
```
Detailed HRV data
```python
garth.HRVData.get("2023-07-20")
```
```python
HRVData(
user_profile_pk=2591602,
hrv_summary=HRVSummary(
calendar_date=datetime.date(2023, 7, 20),
weekly_avg=39,
last_night_avg=42,
last_night_5_min_high=66,
baseline=Baseline(
low_upper=36,
balanced_low=39,
balanced_upper=52,
marker_value=0.25
),
status='BALANCED',
feedback_phrase='HRV_BALANCED_7',
create_time_stamp=datetime.datetime(2023, 7, 20, 12, 14, 11, 898000)
),
hrv_readings=[
HRVReading(
hrv_value=54,
reading_time_gmt=datetime.datetime(2023, 7, 20, 5, 29, 48),
reading_time_local=datetime.datetime(2023, 7, 19, 23, 29, 48)
),
HRVReading(
hrv_value=56,
reading_time_gmt=datetime.datetime(2023, 7, 20, 5, 34, 48),
reading_time_local=datetime.datetime(2023, 7, 19, 23, 34, 48)
),
# ... truncated for brevity
HRVReading(
hrv_value=38,
reading_time_gmt=datetime.datetime(2023, 7, 20, 12, 9, 48),
reading_time_local=datetime.datetime(2023, 7, 20, 6, 9, 48)
)
],
start_timestamp_gmt=datetime.datetime(2023, 7, 20, 5, 25),
end_timestamp_gmt=datetime.datetime(2023, 7, 20, 12, 9, 48),
start_timestamp_local=datetime.datetime(2023, 7, 19, 23, 25),
end_timestamp_local=datetime.datetime(2023, 7, 20, 6, 9, 48),
sleep_start_timestamp_gmt=datetime.datetime(2023, 7, 20, 5, 25),
sleep_end_timestamp_gmt=datetime.datetime(2023, 7, 20, 12, 11),
sleep_start_timestamp_local=datetime.datetime(2023, 7, 19, 23, 25),
sleep_end_timestamp_local=datetime.datetime(2023, 7, 20, 6, 11)
)
```
### Sleep
Daily sleep quality
```python
garth.DailySleep.list("2023-07-23", 2)
```
```python
[
DailySleep(calendar_date=datetime.date(2023, 7, 22), value=69),
DailySleep(calendar_date=datetime.date(2023, 7, 23), value=73)
]
```
Detailed sleep data
```python
garth.SleepData.get("2023-07-20")
```
```python
SleepData(
daily_sleep_dto=DailySleepDTO(
id=1689830700000,
user_profile_pk=2591602,
calendar_date=datetime.date(2023, 7, 20),
sleep_time_seconds=23700,
nap_time_seconds=0,
sleep_window_confirmed=True,
sleep_window_confirmation_type='enhanced_confirmed_final',
sleep_start_timestamp_gmt=datetime.datetime(2023, 7, 20, 5, 25, tzinfo=TzInfo(UTC)),
sleep_end_timestamp_gmt=datetime.datetime(2023, 7, 20, 12, 11, tzinfo=TzInfo(UTC)),
sleep_start_timestamp_local=datetime.datetime(2023, 7, 19, 23, 25, tzinfo=TzInfo(UTC)),
sleep_end_timestamp_local=datetime.datetime(2023, 7, 20, 6, 11, tzinfo=TzInfo(UTC)),
unmeasurable_sleep_seconds=0,
deep_sleep_seconds=9660,
light_sleep_seconds=12600,
rem_sleep_seconds=1440,
awake_sleep_seconds=660,
device_rem_capable=True,
retro=False,
sleep_from_device=True,
sleep_version=2,
awake_count=1,
sleep_scores=SleepScores(
total_duration=Score(
qualifier_key='FAIR',
optimal_start=28800.0,
optimal_end=28800.0,
value=None,
ideal_start_in_seconds=None,
deal_end_in_seconds=None
),
stress=Score(
qualifier_key='FAIR',
optimal_start=0.0,
optimal_end=15.0,
value=None,
ideal_start_in_seconds=None,
ideal_end_in_seconds=None
),
awake_count=Score(
qualifier_key='GOOD',
optimal_start=0.0,
optimal_end=1.0,
value=None,
ideal_start_in_seconds=None,
ideal_end_in_seconds=None
),
overall=Score(
qualifier_key='FAIR',
optimal_start=None,
optimal_end=None,
value=68,
ideal_start_in_seconds=None,
ideal_end_in_seconds=None
),
rem_percentage=Score(
qualifier_key='POOR',
optimal_start=21.0,
optimal_end=31.0,
value=6,
ideal_start_in_seconds=4977.0,
ideal_end_in_seconds=7347.0
),
restlessness=Score(
qualifier_key='EXCELLENT',
optimal_start=0.0,
optimal_end=5.0,
value=None,
ideal_start_in_seconds=None,
ideal_end_in_seconds=None
),
light_percentage=Score(
qualifier_key='EXCELLENT',
optimal_start=30.0,
optimal_end=64.0,
value=53,
ideal_start_in_seconds=7110.0,
ideal_end_in_seconds=15168.0
),
deep_percentage=Score(
qualifier_key='EXCELLENT',
optimal_start=16.0,
optimal_end=33.0,
value=41,
ideal_start_in_seconds=3792.0,
ideal_end_in_seconds=7821.0
)
),
auto_sleep_start_timestamp_gmt=None,
auto_sleep_end_timestamp_gmt=None,
sleep_quality_type_pk=None,
sleep_result_type_pk=None,
average_sp_o2_value=92.0,
lowest_sp_o2_value=87,
highest_sp_o2_value=100,
average_sp_o2_hr_sleep=53.0,
average_respiration_value=14.0,
lowest_respiration_value=12.0,
highest_respiration_value=16.0,
avg_sleep_stress=17.0,
age_group='ADULT',
sleep_score_feedback='NEGATIVE_NOT_ENOUGH_REM',
sleep_score_insight='NONE'
),
sleep_movement=[
SleepMovement(
start_gmt=datetime.datetime(2023, 7, 20, 4, 25),
end_gmt=datetime.datetime(2023, 7, 20, 4, 26),
activity_level=5.688743692980419
),
SleepMovement(
start_gmt=datetime.datetime(2023, 7, 20, 4, 26),
end_gmt=datetime.datetime(2023, 7, 20, 4, 27),
activity_level=5.318763075304898
),
# ... truncated for brevity
SleepMovement(
start_gmt=datetime.datetime(2023, 7, 20, 13, 10),
end_gmt=datetime.datetime(2023, 7, 20, 13, 11),
activity_level=7.088729101943337
)
]
)
```
List sleep data over several nights.
```python
garth.SleepData.list("2023-07-20", 30)
```
### Weight
Retrieve the latest weight measurement and body composition data for a given
date.
**Note**: Weight, weight delta, bone mass, and muscle mass values are measured
in grams
```python
garth.WeightData.get("2025-06-01")
```
```python
WeightData(
sample_pk=1749996902851,
calendar_date=datetime.date(2025, 6, 15),
weight=59720,
source_type='INDEX_SCALE',
weight_delta=200.00000000000284,
timestamp_gmt=1749996876000,
datetime_utc=datetime.datetime(2025, 6, 15, 14, 14, 36, tzinfo=TzInfo(UTC)),
datetime_local=datetime.datetime(
2025, 6, 15, 8, 14, 36,
tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=64800))
),
bmi=22.799999237060547,
body_fat=19.3,
body_water=58.9,
bone_mass=3539,
muscle_mass=26979,
physique_rating=None,
visceral_fat=None,
metabolic_age=None
)
```
Get weight entries for a date range.
```python
garth.WeightData.list("2025-06-01", 30)
```
```python
[
WeightData(
sample_pk=1749307692871,
calendar_date=datetime.date(2025, 6, 7),
weight=59189,
source_type='INDEX_SCALE',
weight_delta=500.0,
timestamp_gmt=1749307658000,
datetime_utc=datetime.datetime(2025, 6, 7, 14, 47, 38, tzinfo=TzInfo(UTC)),
datetime_local=datetime.datetime(
2025, 6, 7, 8, 47, 38,
tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=64800))
),
bmi=22.600000381469727,
body_fat=20.0,
body_water=58.4,
bone_mass=3450,
muscle_mass=26850,
physique_rating=None,
visceral_fat=None,
metabolic_age=None
),
WeightData(
sample_pk=1749909217098,
calendar_date=datetime.date(2025, 6, 14),
weight=59130,
source_type='INDEX_SCALE',
weight_delta=-100.00000000000142,
timestamp_gmt=1749909180000,
datetime_utc=datetime.datetime(2025, 6, 14, 13, 53, tzinfo=TzInfo(UTC)),
datetime_local=datetime.datetime(
2025, 6, 14, 7, 53,
tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=64800))
),
bmi=22.5,
body_fat=20.3,
body_water=58.2,
bone_mass=3430,
muscle_mass=26840,
physique_rating=None,
visceral_fat=None,
metabolic_age=None
),
WeightData(
sample_pk=1749948744411,
calendar_date=datetime.date(2025, 6, 14),
weight=59500,
source_type='MANUAL',
weight_delta=399.9999999999986,
timestamp_gmt=1749948725175,
datetime_utc=datetime.datetime(
2025, 6, 15, 0, 52, 5, 175000, tzinfo=TzInfo(UTC)
),
datetime_local=datetime.datetime(
2025, 6, 14, 18, 52, 5, 175000,
tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=64800))
),
bmi=None,
body_fat=None,
body_water=None,
bone_mass=None,
muscle_mass=None,
physique_rating=None,
visceral_fat=None,
metabolic_age=None
),
WeightData(
sample_pk=1749996902851,
calendar_date=datetime.date(2025, 6, 15),
weight=59720,
source_type='INDEX_SCALE',
weight_delta=200.00000000000284,
timestamp_gmt=1749996876000,
datetime_utc=datetime.datetime(2025, 6, 15, 14, 14, 36, tzinfo=TzInfo(UTC)),
datetime_local=datetime.datetime(
2025, 6, 15, 8, 14, 36,
tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=64800))
),
bmi=22.799999237060547,
body_fat=19.3,
body_water=58.9,
bone_mass=3539,
muscle_mass=26979,
physique_rating=None,
visceral_fat=None,
metabolic_age=None
)
]
```
## User
### UserProfile
```python
garth.UserProfile.get()
```
```python
UserProfile(
id=3154645,
profile_id=2591602,
garmin_guid="0690cc1d-d23d-4412-b027-80fd4ed1c0f6",
display_name="mtamizi",
full_name="Matin Tamizi",
user_name="mtamizi",
profile_image_uuid="73240e81-6e4d-43fc-8af8-c8f6c51b3b8f",
profile_image_url_large=(
"https://s3.amazonaws.com/garmin-connect-prod/profile_images/"
"73240e81-6e4d-43fc-8af8-c8f6c51b3b8f-2591602.png"
),
profile_image_url_medium=(
"https://s3.amazonaws.com/garmin-connect-prod/profile_images/"
"685a19e9-a7be-4a11-9bf9-faca0c5d1f1a-2591602.png"
),
profile_image_url_small=(
"https://s3.amazonaws.com/garmin-connect-prod/profile_images/"
"6302f021-0ec7-4dc9-b0c3-d5a19bc5a08c-2591602.png"
),
location="Ciudad de México, CDMX",
facebook_url=None,
twitter_url=None,
personal_website=None,
motivation=None,
bio=None,
primary_activity=None,
favorite_activity_types=[],
running_training_speed=0.0,
cycling_training_speed=0.0,
favorite_cycling_activity_types=[],
cycling_classification=None,
cycling_max_avg_power=0.0,
swimming_training_speed=0.0,
profile_visibility="private",
activity_start_visibility="private",
activity_map_visibility="public",
course_visibility="public",
activity_heart_rate_visibility="public",
activity_power_visibility="public",
badge_visibility="private",
show_age=False,
show_weight=False,
show_height=False,
show_weight_class=False,
show_age_range=False,
show_gender=False,
show_activity_class=False,
show_vo_2_max=False,
show_personal_records=False,
show_last_12_months=False,
show_lifetime_totals=False,
show_upcoming_events=False,
show_recent_favorites=False,
show_recent_device=False,
show_recent_gear=False,
show_badges=True,
other_activity=None,
other_primary_activity=None,
other_motivation=None,
user_roles=[
"SCOPE_ATP_READ",
"SCOPE_ATP_WRITE",
"SCOPE_COMMUNITY_COURSE_READ",
"SCOPE_COMMUNITY_COURSE_WRITE",
"SCOPE_CONNECT_READ",
"SCOPE_CONNECT_WRITE",
"SCOPE_DT_CLIENT_ANALYTICS_WRITE",
"SCOPE_GARMINPAY_READ",
"SCOPE_GARMINPAY_WRITE",
"SCOPE_GCOFFER_READ",
"SCOPE_GCOFFER_WRITE",
"SCOPE_GHS_SAMD",
"SCOPE_GHS_UPLOAD",
"SCOPE_GOLF_API_READ",
"SCOPE_GOLF_API_WRITE",
"SCOPE_INSIGHTS_READ",
"SCOPE_INSIGHTS_WRITE",
"SCOPE_PRODUCT_SEARCH_READ",
"ROLE_CONNECTUSER",
"ROLE_FITNESS_USER",
"ROLE_WELLNESS_USER",
"ROLE_OUTDOOR_USER",
"ROLE_CONNECT_2_USER",
"ROLE_TACX_APP_USER",
],
name_approved=True,
user_profile_full_name="Matin Tamizi",
make_golf_scorecards_private=True,
allow_golf_live_scoring=False,
allow_golf_scoring_by_connections=True,
user_level=3,
user_point=118,
level_update_date="2020-12-12T15:20:38.0",
level_is_viewed=False,
level_point_threshold=140,
user_point_offset=0,
user_pro=False,
)
```
### UserSettings
```python
garth.UserSettings.get()
```
```python
UserSettings(
id=2591602,
user_data=UserData(
gender="MALE",
weight=83000.0,
height=182.0,
time_format="time_twenty_four_hr",
birth_date=datetime.date(1984, 10, 17),
measurement_system="metric",
activity_level=None,
handedness="RIGHT",
power_format=PowerFormat(
format_id=30,
format_key="watt",
min_fraction=0,
max_fraction=0,
grouping_used=True,
display_format=None,
),
heart_rate_format=PowerFormat(
format_id=21,
format_key="bpm",
min_fraction=0,
max_fraction=0,
grouping_used=False,
display_format=None,
),
first_day_of_week=FirstDayOfWeek(
day_id=2,
day_name="sunday",
sort_order=2,
is_possible_first_day=True,
),
vo_2_max_running=45.0,
vo_2_max_cycling=None,
lactate_threshold_speed=0.34722125000000004,
lactate_threshold_heart_rate=None,
dive_number=None,
intensity_minutes_calc_method="AUTO",
moderate_intensity_minutes_hr_zone=3,
vigorous_intensity_minutes_hr_zone=4,
hydration_measurement_unit="milliliter",
hydration_containers=[],
hydration_auto_goal_enabled=True,
firstbeat_max_stress_score=None,
firstbeat_cycling_lt_timestamp=None,
firstbeat_running_lt_timestamp=1044719868,
threshold_heart_rate_auto_detected=True,
ftp_auto_detected=None,
training_status_paused_date=None,
weather_location=None,
golf_distance_unit="statute_us",
golf_elevation_unit=None,
golf_speed_unit=None,
external_bottom_time=None,
),
user_sleep=UserSleep(
sleep_time=80400,
default_sleep_time=False,
wake_time=24000,
default_wake_time=False,
),
connect_date=None,
source_type=None,
)
```
## Star History
<a href="https://www.star-history.com/#matin/garth&Date">
<picture>
<source
media="(prefers-color-scheme: dark)"
srcset="https://api.star-history.com/svg?repos=matin/garth&type=Date&theme=dark"
/>
<source
media="(prefers-color-scheme: light)"
srcset="https://api.star-history.com/svg?repos=matin/garth&type=Date"
/>
<img
alt="Star History Chart"
src="https://api.star-history.com/svg?repos=matin/garth&type=Date" />
</picture>
</a>
Raw data
{
"_id": null,
"home_page": null,
"name": "garth",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": "garmin, garmin api, garmin connect, garmin sso",
"author": null,
"author_email": "Matin Tamizi <mtamizi@duck.com>",
"download_url": "https://files.pythonhosted.org/packages/37/ca/6a2b9dc95fd36bb098ec6318c21fd8e2419e5b770bd8c618b9d2d1077362/garth-0.5.17.tar.gz",
"platform": null,
"description": "# Garth\n\n[](\n https://github.com/matin/garth/actions/workflows/ci.yml?query=event%3Apush+branch%3Amain+workflow%3ACI)\n[](\n https://codecov.io/gh/matin/garth)\n[](\n https://pypi.org/project/garth/)\n[](\n https://pypistats.org/packages/garth)\n\nGarmin SSO auth + Connect Python client\n\n## Garmin Connect MCP Server\n\n[`garth-mcp-server`](https://github.com/matin/garth-mcp-server) is in early development.\nContributions are greatly appreciated.\n\nTo generate your `GARTH_TOKEN`, use `uvx garth login`.\nFor China, do `uvx garth --domain garmin.cn login`.\n\n## Google Colabs\n\n### [Stress: 28-day rolling average](https://colab.research.google.com/github/matin/garth/blob/main/colabs/stress.ipynb)\n\nStress levels from one day to another can vary by extremes, but there's always\na general trend. Using a scatter plot with a rolling average shows both the\nindividual days and the trend. The Colab retrieves up to three years of daily\ndata. If there's less than three years of data, it retrieves whatever is\navailable.\n\n\n\n### [Sleep analysis over 90 days](https://colab.research.google.com/github/matin/garth/blob/main/colabs/sleep.ipynb)\n\nThe Garmin Connect app only shows a maximum of seven days for sleep\nstages\u2014making it hard to see trends. The Connect API supports retrieving\ndaily sleep quality in 28-day pages, but that doesn't show details. Using\n`SleedData.list()` gives us the ability to retrieve an arbitrary number of\nday with enough detail to product a stacked bar graph of the daily sleep\nstages.\n\n\n\nOne specific graph that's useful but not available in the Connect app is\nsleep start and end times over an extended period. This provides context\nto the sleep hours and stages.\n\n\n\n### [ChatGPT analysis of Garmin stats](https://colab.research.google.com/github/matin/garth/blob/main/colabs/chatgpt_analysis_of_stats.ipynb)\n\nChatGPT's Advanced Data Analysis took can provide incredible insight\ninto the data in a way that's much simpler than using Pandas and Matplotlib.\n\nStart by using the linked Colab to download a CSV of the last three years\nof your stats, and upload the CSV to ChatGPT.\n\nHere's the outputs of the following prompts:\n\nHow do I sleep on different days of the week?\n\n<img width=\"600\" alt=\"image\" src=\"https://github.com/matin/garth/assets/98985/b7507459-2482-43d6-bf55-c3a1f756facb\">\n\nOn what days do I exercise the most?\n\n<img width=\"600\" alt=\"image\" src=\"https://github.com/matin/garth/assets/98985/11294be2-8e1a-4fed-a489-13420765aada\">\n\nMagic!\n\n## Background\n\nGarth is meant for personal use and follows the philosophy that your data is\nyour data. You should be able to download it and analyze it in the way that\nyou'd like. In my case, that means processing with Google Colab, Pandas,\nMatplotlib, etc.\n\nThere are already a few Garmin Connect libraries. Why write another?\n\n### Authentication and stability\n\nThe most important reasoning is to build a library with authentication that\nworks on [Google Colab](https://colab.research.google.com/) and doesn't require\ntools like Cloudscraper. Garth, in comparison:\n\n1. Uses OAuth1 and OAuth2 token authentication after initial login\n1. OAuth1 token survives for a year\n1. Supports MFA\n1. Auto-refresh of OAuth2 token when expired\n1. Works on Google Colab\n1. Uses Pydantic dataclasses to validate and simplify use of data\n1. Full test coverage\n\n### JSON vs HTML\n\nUsing `garth.connectapi()` allows you to make requests to the Connect API\nand receive JSON vs needing to parse HTML. You can use the same endpoints the\nmobile app uses.\n\nThis also goes back to authentication. Garth manages the necessary Bearer\nAuthentication (along with auto-refresh) necessary to make requests routed to\nthe Connect API.\n\n## Instructions\n\n### Install\n\n```bash\npython -m pip install garth\n```\n\n### Clone, setup environment and run tests\n\n```bash\ngh repo clone matin/garth\ncd garth\nmake install\nmake\n```\n\nUse `make help` to see all the options.\n\n### Authenticate and save session\n\n```python\nimport garth\nfrom getpass import getpass\n\nemail = input(\"Enter email address: \")\npassword = getpass(\"Enter password: \")\n# If there's MFA, you'll be prompted during the login\ngarth.login(email, password)\n\ngarth.save(\"~/.garth\")\n```\n\n### Custom MFA handler\n\nBy default, MFA will prompt for the code in the terminal. You can provide your\nown handler:\n\n```python\ngarth.login(email, password, prompt_mfa=lambda: input(\"Enter MFA code: \"))\n```\n\nFor advanced use cases (like async handling), MFA can be handled separately:\n\n```python\nresult1, result2 = garth.login(email, password, return_on_mfa=True)\nif result1 == \"needs_mfa\": # MFA is required\n mfa_code = \"123456\" # Get this from your custom MFA flow\n oauth1, oauth2 = garth.resume_login(result2, mfa_code)\n```\n\n### Configure\n\n#### Set domain for China\n\n```python\ngarth.configure(domain=\"garmin.cn\")\n```\n\n#### Proxy through Charles\n\n```python\ngarth.configure(proxies={\"https\": \"http://localhost:8888\"}, ssl_verify=False)\n```\n\n### Attempt to resume session\n\n```python\nimport garth\nfrom garth.exc import GarthException\n\ngarth.resume(\"~/.garth\")\ntry:\n garth.client.username\nexcept GarthException:\n # Session is expired. You'll need to log in again\n```\n\n## Connect API\n\n### Daily details\n\n```python\nsleep = garth.connectapi(\n f\"/wellness-service/wellness/dailySleepData/{garth.client.username}\",\n params={\"date\": \"2023-07-05\", \"nonSleepBufferMinutes\": 60},\n)\nlist(sleep.keys())\n```\n\n```json\n[\n \"dailySleepDTO\",\n \"sleepMovement\",\n \"remSleepData\",\n \"sleepLevels\",\n \"sleepRestlessMoments\",\n \"restlessMomentsCount\",\n \"wellnessSpO2SleepSummaryDTO\",\n \"wellnessEpochSPO2DataDTOList\",\n \"wellnessEpochRespirationDataDTOList\",\n \"sleepStress\"\n]\n```\n\n### Stats\n\n```python\nstress = garth.connectapi(\"/usersummary-service/stats/stress/weekly/2023-07-05/52\")\n```\n\n```json\n{\n \"calendarDate\": \"2023-07-13\",\n \"values\": {\n \"highStressDuration\": 2880,\n \"lowStressDuration\": 10140,\n \"overallStressLevel\": 33,\n \"restStressDuration\": 30960,\n \"mediumStressDuration\": 8760\n }\n}\n```\n\n## Upload\n\n```python\nwith open(\"12129115726_ACTIVITY.fit\", \"rb\") as f:\n uploaded = garth.client.upload(f)\n```\n\nNote: Garmin doesn't accept uploads of _structured_ FIT files as outlined in\n[this conversation](https://github.com/matin/garth/issues/27). FIT files\ngenerated from workouts are accepted without issues.\n\n```python\n{\n 'detailedImportResult': {\n 'uploadId': 212157427938,\n 'uploadUuid': {\n 'uuid': '6e56051d-1dd4-4f2c-b8ba-00a1a7d82eb3'\n },\n 'owner': 2591602,\n 'fileSize': 5289,\n 'processingTime': 36,\n 'creationDate': '2023-09-29 01:58:19.113 GMT',\n 'ipAddress': None,\n 'fileName': '12129115726_ACTIVITY.fit',\n 'report': None,\n 'successes': [],\n 'failures': []\n }\n}\n```\n\n## Stats resources\n\n### Stress\n\nDaily stress levels\n\n```python\nDailyStress.list(\"2023-07-23\", 2)\n```\n\n```python\n[\n DailyStress(\n calendar_date=datetime.date(2023, 7, 22),\n overall_stress_level=31,\n rest_stress_duration=31980,\n low_stress_duration=23820,\n medium_stress_duration=7440,\n high_stress_duration=1500\n ),\n DailyStress(\n calendar_date=datetime.date(2023, 7, 23),\n overall_stress_level=26,\n rest_stress_duration=38220,\n low_stress_duration=22500,\n medium_stress_duration=2520,\n high_stress_duration=300\n )\n]\n```\n\nWeekly stress levels\n\n```python\nWeeklyStress.list(\"2023-07-23\", 2)\n```\n\n```python\n[\n WeeklyStress(calendar_date=datetime.date(2023, 7, 10), value=33),\n WeeklyStress(calendar_date=datetime.date(2023, 7, 17), value=32)\n]\n```\n\n### Body Battery\n\nDaily Body Battery and stress data\n\n```python\ngarth.DailyBodyBatteryStress.get(\"2023-07-20\")\n```\n\n```python\nDailyBodyBatteryStress(\n user_profile_pk=2591602,\n calendar_date=datetime.date(2023, 7, 20),\n start_timestamp_gmt=datetime.datetime(2023, 7, 20, 6, 0),\n end_timestamp_gmt=datetime.datetime(2023, 7, 21, 5, 59, 59, 999000),\n start_timestamp_local=datetime.datetime(2023, 7, 19, 23, 0),\n end_timestamp_local=datetime.datetime(2023, 7, 20, 22, 59, 59, 999000),\n max_stress_level=85,\n avg_stress_level=25,\n stress_chart_value_offset=0,\n stress_chart_y_axis_origin=0,\n stress_values_array=[\n [1689811800000, 12], [1689812100000, 18], [1689812400000, 15],\n [1689815700000, 45], [1689819300000, 85], [1689822900000, 35],\n [1689826500000, 20], [1689830100000, 15], [1689833700000, 25],\n [1689837300000, 30]\n ],\n body_battery_values_array=[\n [1689811800000, 'charging', 45, 1.0], [1689812100000, 'charging', 48, 1.0],\n [1689812400000, 'charging', 52, 1.0], [1689815700000, 'charging', 65, 1.0],\n [1689819300000, 'draining', 85, 1.0], [1689822900000, 'draining', 75, 1.0],\n [1689826500000, 'draining', 65, 1.0], [1689830100000, 'draining', 55, 1.0],\n [1689833700000, 'draining', 45, 1.0], [1689837300000, 'draining', 35, 1.0],\n [1689840900000, 'draining', 25, 1.0]\n ]\n)\n\n# Access derived properties\ndaily_bb = garth.DailyBodyBatteryStress.get(\"2023-07-20\")\ndaily_bb.current_body_battery # 25 (last reading)\ndaily_bb.max_body_battery # 85\ndaily_bb.min_body_battery # 25\ndaily_bb.body_battery_change # -20 (45 -> 25)\n\n# Access structured readings\nfor reading in daily_bb.body_battery_readings:\n print(f\"Level: {reading.level}, Status: {reading.status}\")\n # Level: 45, Status: charging\n # Level: 48, Status: charging\n # ... etc\n\nfor reading in daily_bb.stress_readings:\n print(f\"Stress: {reading.stress_level}\")\n # Stress: 12\n # Stress: 18\n # ... etc\n```\n\nBody Battery events (sleep events)\n\n```python\ngarth.BodyBatteryData.get(\"2023-07-20\")\n```\n\n```python\n[\n BodyBatteryData(\n event=BodyBatteryEvent(\n event_type='sleep',\n event_start_time_gmt=datetime.datetime(2023, 7, 19, 21, 30),\n timezone_offset=-25200000,\n duration_in_milliseconds=28800000,\n body_battery_impact=35,\n feedback_type='good_sleep',\n short_feedback='Good sleep restored your Body Battery'\n ),\n activity_name=None,\n activity_type=None,\n activity_id=None,\n average_stress=15.5,\n stress_values_array=[\n [1689811800000, 12], [1689812100000, 18], [1689812400000, 15]\n ],\n body_battery_values_array=[\n [1689811800000, 'charging', 45, 1.0],\n [1689812100000, 'charging', 48, 1.0],\n [1689812400000, 'charging', 52, 1.0],\n [1689840600000, 'draining', 85, 1.0]\n ]\n )\n]\n\n# Access convenience properties on each event\nevents = garth.BodyBatteryData.get(\"2023-07-20\")\nevent = events[0]\nevent.current_level # 85 (last reading)\nevent.max_level # 85\nevent.min_level # 45\n```\n\n### Hydration\n\nDaily hydration data\n\n```python\ngarth.DailyHydration.list(period=2)\n```\n\n```python\n[\n DailyHydration(\n calendar_date=datetime.date(2024, 6, 29),\n value_in_ml=1750.0,\n goal_in_ml=2800.0\n )\n]\n```\n\n### Steps\n\nDaily steps\n\n```python\ngarth.DailySteps.list(period=2)\n```\n\n```python\n[\n DailySteps(\n calendar_date=datetime.date(2023, 7, 28),\n total_steps=6510,\n total_distance=5552,\n step_goal=8090\n ),\n DailySteps(\n calendar_date=datetime.date(2023, 7, 29),\n total_steps=7218,\n total_distance=6002,\n step_goal=7940\n )\n]\n```\n\nWeekly steps\n\n```python\ngarth.WeeklySteps.list(period=2)\n```\n\n```python\n[\n WeeklySteps(\n calendar_date=datetime.date(2023, 7, 16),\n total_steps=42339,\n average_steps=6048.428571428572,\n average_distance=5039.285714285715,\n total_distance=35275.0,\n wellness_data_days_count=7\n ),\n WeeklySteps(\n calendar_date=datetime.date(2023, 7, 23),\n total_steps=56420,\n average_steps=8060.0,\n average_distance=7198.142857142857,\n total_distance=50387.0,\n wellness_data_days_count=7\n )\n]\n```\n\n### Intensity Minutes\n\nDaily intensity minutes\n\n```python\ngarth.DailyIntensityMinutes.list(period=2)\n```\n\n```python\n[\n DailyIntensityMinutes(\n calendar_date=datetime.date(2023, 7, 28),\n weekly_goal=150,\n moderate_value=0,\n vigorous_value=0\n ),\n DailyIntensityMinutes(\n calendar_date=datetime.date(2023, 7, 29),\n weekly_goal=150,\n moderate_value=0,\n vigorous_value=0\n )\n]\n```\n\nWeekly intensity minutes\n\n```python\ngarth.WeeklyIntensityMinutes.list(period=2)\n```\n\n```python\n[\n WeeklyIntensityMinutes(\n calendar_date=datetime.date(2023, 7, 17),\n weekly_goal=150,\n moderate_value=103,\n vigorous_value=9\n ),\n WeeklyIntensityMinutes(\n calendar_date=datetime.date(2023, 7, 24),\n weekly_goal=150,\n moderate_value=101,\n vigorous_value=105\n )\n]\n```\n\n### HRV\n\nDaily HRV\n\n```python\ngarth.DailyHRV.list(period=2)\n```\n\n```python\n[\n DailyHRV(\n calendar_date=datetime.date(2023, 7, 28),\n weekly_avg=39,\n last_night_avg=36,\n last_night_5_min_high=52,\n baseline=HRVBaseline(\n low_upper=36,\n balanced_low=39,\n balanced_upper=51,\n marker_value=0.25\n ),\n status='BALANCED',\n feedback_phrase='HRV_BALANCED_2',\n create_time_stamp=datetime.datetime(2023, 7, 28, 12, 40, 16, 785000)\n ),\n DailyHRV(\n calendar_date=datetime.date(2023, 7, 29),\n weekly_avg=40,\n last_night_avg=41,\n last_night_5_min_high=76,\n baseline=HRVBaseline(\n low_upper=36,\n balanced_low=39,\n balanced_upper=51,\n marker_value=0.2916565\n ),\n status='BALANCED',\n feedback_phrase='HRV_BALANCED_8',\n create_time_stamp=datetime.datetime(2023, 7, 29, 13, 45, 23, 479000)\n )\n]\n```\n\nDetailed HRV data\n\n```python\ngarth.HRVData.get(\"2023-07-20\")\n```\n\n```python\nHRVData(\n user_profile_pk=2591602,\n hrv_summary=HRVSummary(\n calendar_date=datetime.date(2023, 7, 20),\n weekly_avg=39,\n last_night_avg=42,\n last_night_5_min_high=66,\n baseline=Baseline(\n low_upper=36,\n balanced_low=39,\n balanced_upper=52,\n marker_value=0.25\n ),\n status='BALANCED',\n feedback_phrase='HRV_BALANCED_7',\n create_time_stamp=datetime.datetime(2023, 7, 20, 12, 14, 11, 898000)\n ),\n hrv_readings=[\n HRVReading(\n hrv_value=54,\n reading_time_gmt=datetime.datetime(2023, 7, 20, 5, 29, 48),\n reading_time_local=datetime.datetime(2023, 7, 19, 23, 29, 48)\n ),\n HRVReading(\n hrv_value=56,\n reading_time_gmt=datetime.datetime(2023, 7, 20, 5, 34, 48),\n reading_time_local=datetime.datetime(2023, 7, 19, 23, 34, 48)\n ),\n # ... truncated for brevity\n HRVReading(\n hrv_value=38,\n reading_time_gmt=datetime.datetime(2023, 7, 20, 12, 9, 48),\n reading_time_local=datetime.datetime(2023, 7, 20, 6, 9, 48)\n )\n ],\n start_timestamp_gmt=datetime.datetime(2023, 7, 20, 5, 25),\n end_timestamp_gmt=datetime.datetime(2023, 7, 20, 12, 9, 48),\n start_timestamp_local=datetime.datetime(2023, 7, 19, 23, 25),\n end_timestamp_local=datetime.datetime(2023, 7, 20, 6, 9, 48),\n sleep_start_timestamp_gmt=datetime.datetime(2023, 7, 20, 5, 25),\n sleep_end_timestamp_gmt=datetime.datetime(2023, 7, 20, 12, 11),\n sleep_start_timestamp_local=datetime.datetime(2023, 7, 19, 23, 25),\n sleep_end_timestamp_local=datetime.datetime(2023, 7, 20, 6, 11)\n)\n```\n\n### Sleep\n\nDaily sleep quality\n\n```python\ngarth.DailySleep.list(\"2023-07-23\", 2)\n```\n\n```python\n[\n DailySleep(calendar_date=datetime.date(2023, 7, 22), value=69),\n DailySleep(calendar_date=datetime.date(2023, 7, 23), value=73)\n]\n```\n\nDetailed sleep data\n\n```python\ngarth.SleepData.get(\"2023-07-20\")\n```\n\n```python\nSleepData(\n daily_sleep_dto=DailySleepDTO(\n id=1689830700000,\n user_profile_pk=2591602,\n calendar_date=datetime.date(2023, 7, 20),\n sleep_time_seconds=23700,\n nap_time_seconds=0,\n sleep_window_confirmed=True,\n sleep_window_confirmation_type='enhanced_confirmed_final',\n sleep_start_timestamp_gmt=datetime.datetime(2023, 7, 20, 5, 25, tzinfo=TzInfo(UTC)),\n sleep_end_timestamp_gmt=datetime.datetime(2023, 7, 20, 12, 11, tzinfo=TzInfo(UTC)),\n sleep_start_timestamp_local=datetime.datetime(2023, 7, 19, 23, 25, tzinfo=TzInfo(UTC)),\n sleep_end_timestamp_local=datetime.datetime(2023, 7, 20, 6, 11, tzinfo=TzInfo(UTC)),\n unmeasurable_sleep_seconds=0,\n deep_sleep_seconds=9660,\n light_sleep_seconds=12600,\n rem_sleep_seconds=1440,\n awake_sleep_seconds=660,\n device_rem_capable=True,\n retro=False,\n sleep_from_device=True,\n sleep_version=2,\n awake_count=1,\n sleep_scores=SleepScores(\n total_duration=Score(\n qualifier_key='FAIR',\n optimal_start=28800.0,\n optimal_end=28800.0,\n value=None,\n ideal_start_in_seconds=None,\n deal_end_in_seconds=None\n ),\n stress=Score(\n qualifier_key='FAIR',\n optimal_start=0.0,\n optimal_end=15.0,\n value=None,\n ideal_start_in_seconds=None,\n ideal_end_in_seconds=None\n ),\n awake_count=Score(\n qualifier_key='GOOD',\n optimal_start=0.0,\n optimal_end=1.0,\n value=None,\n ideal_start_in_seconds=None,\n ideal_end_in_seconds=None\n ),\n overall=Score(\n qualifier_key='FAIR',\n optimal_start=None,\n optimal_end=None,\n value=68,\n ideal_start_in_seconds=None,\n ideal_end_in_seconds=None\n ),\n rem_percentage=Score(\n qualifier_key='POOR',\n optimal_start=21.0,\n optimal_end=31.0,\n value=6,\n ideal_start_in_seconds=4977.0,\n ideal_end_in_seconds=7347.0\n ),\n restlessness=Score(\n qualifier_key='EXCELLENT',\n optimal_start=0.0,\n optimal_end=5.0,\n value=None,\n ideal_start_in_seconds=None,\n ideal_end_in_seconds=None\n ),\n light_percentage=Score(\n qualifier_key='EXCELLENT',\n optimal_start=30.0,\n optimal_end=64.0,\n value=53,\n ideal_start_in_seconds=7110.0,\n ideal_end_in_seconds=15168.0\n ),\n deep_percentage=Score(\n qualifier_key='EXCELLENT',\n optimal_start=16.0,\n optimal_end=33.0,\n value=41,\n ideal_start_in_seconds=3792.0,\n ideal_end_in_seconds=7821.0\n )\n ),\n auto_sleep_start_timestamp_gmt=None,\n auto_sleep_end_timestamp_gmt=None,\n sleep_quality_type_pk=None,\n sleep_result_type_pk=None,\n average_sp_o2_value=92.0,\n lowest_sp_o2_value=87,\n highest_sp_o2_value=100,\n average_sp_o2_hr_sleep=53.0,\n average_respiration_value=14.0,\n lowest_respiration_value=12.0,\n highest_respiration_value=16.0,\n avg_sleep_stress=17.0,\n age_group='ADULT',\n sleep_score_feedback='NEGATIVE_NOT_ENOUGH_REM',\n sleep_score_insight='NONE'\n ),\n sleep_movement=[\n SleepMovement(\n start_gmt=datetime.datetime(2023, 7, 20, 4, 25),\n end_gmt=datetime.datetime(2023, 7, 20, 4, 26),\n activity_level=5.688743692980419\n ),\n SleepMovement(\n start_gmt=datetime.datetime(2023, 7, 20, 4, 26),\n end_gmt=datetime.datetime(2023, 7, 20, 4, 27),\n activity_level=5.318763075304898\n ),\n # ... truncated for brevity\n SleepMovement(\n start_gmt=datetime.datetime(2023, 7, 20, 13, 10),\n end_gmt=datetime.datetime(2023, 7, 20, 13, 11),\n activity_level=7.088729101943337\n )\n ]\n)\n```\n\nList sleep data over several nights.\n\n```python\ngarth.SleepData.list(\"2023-07-20\", 30)\n```\n\n### Weight\n\nRetrieve the latest weight measurement and body composition data for a given\ndate.\n\n**Note**: Weight, weight delta, bone mass, and muscle mass values are measured\nin grams\n\n```python\ngarth.WeightData.get(\"2025-06-01\")\n```\n\n```python\nWeightData(\n sample_pk=1749996902851,\n calendar_date=datetime.date(2025, 6, 15),\n weight=59720,\n source_type='INDEX_SCALE',\n weight_delta=200.00000000000284,\n timestamp_gmt=1749996876000,\n datetime_utc=datetime.datetime(2025, 6, 15, 14, 14, 36, tzinfo=TzInfo(UTC)),\n datetime_local=datetime.datetime(\n 2025, 6, 15, 8, 14, 36,\n tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=64800))\n ),\n bmi=22.799999237060547,\n body_fat=19.3,\n body_water=58.9,\n bone_mass=3539,\n muscle_mass=26979,\n physique_rating=None,\n visceral_fat=None,\n metabolic_age=None\n)\n```\n\nGet weight entries for a date range.\n\n```python\ngarth.WeightData.list(\"2025-06-01\", 30)\n```\n\n```python\n[\n WeightData(\n sample_pk=1749307692871,\n calendar_date=datetime.date(2025, 6, 7),\n weight=59189,\n source_type='INDEX_SCALE',\n weight_delta=500.0,\n timestamp_gmt=1749307658000,\n datetime_utc=datetime.datetime(2025, 6, 7, 14, 47, 38, tzinfo=TzInfo(UTC)),\n datetime_local=datetime.datetime(\n 2025, 6, 7, 8, 47, 38,\n tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=64800))\n ),\n bmi=22.600000381469727,\n body_fat=20.0,\n body_water=58.4,\n bone_mass=3450,\n muscle_mass=26850,\n physique_rating=None,\n visceral_fat=None,\n metabolic_age=None\n ),\n WeightData(\n sample_pk=1749909217098,\n calendar_date=datetime.date(2025, 6, 14),\n weight=59130,\n source_type='INDEX_SCALE',\n weight_delta=-100.00000000000142,\n timestamp_gmt=1749909180000,\n datetime_utc=datetime.datetime(2025, 6, 14, 13, 53, tzinfo=TzInfo(UTC)),\n datetime_local=datetime.datetime(\n 2025, 6, 14, 7, 53,\n tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=64800))\n ),\n bmi=22.5,\n body_fat=20.3,\n body_water=58.2,\n bone_mass=3430,\n muscle_mass=26840,\n physique_rating=None,\n visceral_fat=None,\n metabolic_age=None\n ),\n WeightData(\n sample_pk=1749948744411,\n calendar_date=datetime.date(2025, 6, 14),\n weight=59500,\n source_type='MANUAL',\n weight_delta=399.9999999999986,\n timestamp_gmt=1749948725175,\n datetime_utc=datetime.datetime(\n 2025, 6, 15, 0, 52, 5, 175000, tzinfo=TzInfo(UTC)\n ),\n datetime_local=datetime.datetime(\n 2025, 6, 14, 18, 52, 5, 175000,\n tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=64800))\n ),\n bmi=None,\n body_fat=None,\n body_water=None,\n bone_mass=None,\n muscle_mass=None,\n physique_rating=None,\n visceral_fat=None,\n metabolic_age=None\n ),\n WeightData(\n sample_pk=1749996902851,\n calendar_date=datetime.date(2025, 6, 15),\n weight=59720,\n source_type='INDEX_SCALE',\n weight_delta=200.00000000000284,\n timestamp_gmt=1749996876000,\n datetime_utc=datetime.datetime(2025, 6, 15, 14, 14, 36, tzinfo=TzInfo(UTC)),\n datetime_local=datetime.datetime(\n 2025, 6, 15, 8, 14, 36,\n tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=64800))\n ),\n bmi=22.799999237060547,\n body_fat=19.3,\n body_water=58.9,\n bone_mass=3539,\n muscle_mass=26979,\n physique_rating=None,\n visceral_fat=None,\n metabolic_age=None\n )\n]\n```\n\n## User\n\n### UserProfile\n\n```python\ngarth.UserProfile.get()\n```\n\n```python\nUserProfile(\n id=3154645,\n profile_id=2591602,\n garmin_guid=\"0690cc1d-d23d-4412-b027-80fd4ed1c0f6\",\n display_name=\"mtamizi\",\n full_name=\"Matin Tamizi\",\n user_name=\"mtamizi\",\n profile_image_uuid=\"73240e81-6e4d-43fc-8af8-c8f6c51b3b8f\",\n profile_image_url_large=(\n \"https://s3.amazonaws.com/garmin-connect-prod/profile_images/\"\n \"73240e81-6e4d-43fc-8af8-c8f6c51b3b8f-2591602.png\"\n ),\n profile_image_url_medium=(\n \"https://s3.amazonaws.com/garmin-connect-prod/profile_images/\"\n \"685a19e9-a7be-4a11-9bf9-faca0c5d1f1a-2591602.png\"\n ),\n profile_image_url_small=(\n \"https://s3.amazonaws.com/garmin-connect-prod/profile_images/\"\n \"6302f021-0ec7-4dc9-b0c3-d5a19bc5a08c-2591602.png\"\n ),\n location=\"Ciudad de M\u00e9xico, CDMX\",\n facebook_url=None,\n twitter_url=None,\n personal_website=None,\n motivation=None,\n bio=None,\n primary_activity=None,\n favorite_activity_types=[],\n running_training_speed=0.0,\n cycling_training_speed=0.0,\n favorite_cycling_activity_types=[],\n cycling_classification=None,\n cycling_max_avg_power=0.0,\n swimming_training_speed=0.0,\n profile_visibility=\"private\",\n activity_start_visibility=\"private\",\n activity_map_visibility=\"public\",\n course_visibility=\"public\",\n activity_heart_rate_visibility=\"public\",\n activity_power_visibility=\"public\",\n badge_visibility=\"private\",\n show_age=False,\n show_weight=False,\n show_height=False,\n show_weight_class=False,\n show_age_range=False,\n show_gender=False,\n show_activity_class=False,\n show_vo_2_max=False,\n show_personal_records=False,\n show_last_12_months=False,\n show_lifetime_totals=False,\n show_upcoming_events=False,\n show_recent_favorites=False,\n show_recent_device=False,\n show_recent_gear=False,\n show_badges=True,\n other_activity=None,\n other_primary_activity=None,\n other_motivation=None,\n user_roles=[\n \"SCOPE_ATP_READ\",\n \"SCOPE_ATP_WRITE\",\n \"SCOPE_COMMUNITY_COURSE_READ\",\n \"SCOPE_COMMUNITY_COURSE_WRITE\",\n \"SCOPE_CONNECT_READ\",\n \"SCOPE_CONNECT_WRITE\",\n \"SCOPE_DT_CLIENT_ANALYTICS_WRITE\",\n \"SCOPE_GARMINPAY_READ\",\n \"SCOPE_GARMINPAY_WRITE\",\n \"SCOPE_GCOFFER_READ\",\n \"SCOPE_GCOFFER_WRITE\",\n \"SCOPE_GHS_SAMD\",\n \"SCOPE_GHS_UPLOAD\",\n \"SCOPE_GOLF_API_READ\",\n \"SCOPE_GOLF_API_WRITE\",\n \"SCOPE_INSIGHTS_READ\",\n \"SCOPE_INSIGHTS_WRITE\",\n \"SCOPE_PRODUCT_SEARCH_READ\",\n \"ROLE_CONNECTUSER\",\n \"ROLE_FITNESS_USER\",\n \"ROLE_WELLNESS_USER\",\n \"ROLE_OUTDOOR_USER\",\n \"ROLE_CONNECT_2_USER\",\n \"ROLE_TACX_APP_USER\",\n ],\n name_approved=True,\n user_profile_full_name=\"Matin Tamizi\",\n make_golf_scorecards_private=True,\n allow_golf_live_scoring=False,\n allow_golf_scoring_by_connections=True,\n user_level=3,\n user_point=118,\n level_update_date=\"2020-12-12T15:20:38.0\",\n level_is_viewed=False,\n level_point_threshold=140,\n user_point_offset=0,\n user_pro=False,\n)\n```\n\n### UserSettings\n\n```python\ngarth.UserSettings.get()\n```\n\n```python\nUserSettings(\n id=2591602,\n user_data=UserData(\n gender=\"MALE\",\n weight=83000.0,\n height=182.0,\n time_format=\"time_twenty_four_hr\",\n birth_date=datetime.date(1984, 10, 17),\n measurement_system=\"metric\",\n activity_level=None,\n handedness=\"RIGHT\",\n power_format=PowerFormat(\n format_id=30,\n format_key=\"watt\",\n min_fraction=0,\n max_fraction=0,\n grouping_used=True,\n display_format=None,\n ),\n heart_rate_format=PowerFormat(\n format_id=21,\n format_key=\"bpm\",\n min_fraction=0,\n max_fraction=0,\n grouping_used=False,\n display_format=None,\n ),\n first_day_of_week=FirstDayOfWeek(\n day_id=2,\n day_name=\"sunday\",\n sort_order=2,\n is_possible_first_day=True,\n ),\n vo_2_max_running=45.0,\n vo_2_max_cycling=None,\n lactate_threshold_speed=0.34722125000000004,\n lactate_threshold_heart_rate=None,\n dive_number=None,\n intensity_minutes_calc_method=\"AUTO\",\n moderate_intensity_minutes_hr_zone=3,\n vigorous_intensity_minutes_hr_zone=4,\n hydration_measurement_unit=\"milliliter\",\n hydration_containers=[],\n hydration_auto_goal_enabled=True,\n firstbeat_max_stress_score=None,\n firstbeat_cycling_lt_timestamp=None,\n firstbeat_running_lt_timestamp=1044719868,\n threshold_heart_rate_auto_detected=True,\n ftp_auto_detected=None,\n training_status_paused_date=None,\n weather_location=None,\n golf_distance_unit=\"statute_us\",\n golf_elevation_unit=None,\n golf_speed_unit=None,\n external_bottom_time=None,\n ),\n user_sleep=UserSleep(\n sleep_time=80400,\n default_sleep_time=False,\n wake_time=24000,\n default_wake_time=False,\n ),\n connect_date=None,\n source_type=None,\n)\n```\n\n## Star History\n\n<a href=\"https://www.star-history.com/#matin/garth&Date\">\n <picture>\n <source\n media=\"(prefers-color-scheme: dark)\"\n srcset=\"https://api.star-history.com/svg?repos=matin/garth&type=Date&theme=dark\"\n />\n <source\n media=\"(prefers-color-scheme: light)\"\n srcset=\"https://api.star-history.com/svg?repos=matin/garth&type=Date\"\n />\n <img\n alt=\"Star History Chart\"\n src=\"https://api.star-history.com/svg?repos=matin/garth&type=Date\" />\n </picture>\n</a>\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Garmin SSO auth + Connect client",
"version": "0.5.17",
"project_urls": {
"Changelog": "https://github.com/matin/garth/releases",
"Homepage": "https://github.com/matin/garth",
"Issues": "https://github.com/matin/garth/issues",
"Repository": "https://github.com/matin/garth"
},
"split_keywords": [
"garmin",
" garmin api",
" garmin connect",
" garmin sso"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "eda57b22d90cc735abda0526aa708f54a729a966e50f7acef7d0b57dd48d0823",
"md5": "65106b23720d6b2c2c16bf0aedd2a49c",
"sha256": "4b109140dfacea6d96060bad8dead8da36699a0729cb0c4ca3b31fa49f860e88"
},
"downloads": -1,
"filename": "garth-0.5.17-py3-none-any.whl",
"has_sig": false,
"md5_digest": "65106b23720d6b2c2c16bf0aedd2a49c",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 32973,
"upload_time": "2025-06-22T05:34:22",
"upload_time_iso_8601": "2025-06-22T05:34:22.655937Z",
"url": "https://files.pythonhosted.org/packages/ed/a5/7b22d90cc735abda0526aa708f54a729a966e50f7acef7d0b57dd48d0823/garth-0.5.17-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "37ca6a2b9dc95fd36bb098ec6318c21fd8e2419e5b770bd8c618b9d2d1077362",
"md5": "1013783e182a81c767548025408247d0",
"sha256": "48ede938c38b2fd70777e55c7025538775d93c1047f43cc7c4481f2a04b109cb"
},
"downloads": -1,
"filename": "garth-0.5.17.tar.gz",
"has_sig": false,
"md5_digest": "1013783e182a81c767548025408247d0",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 1817853,
"upload_time": "2025-06-22T05:34:24",
"upload_time_iso_8601": "2025-06-22T05:34:24.385679Z",
"url": "https://files.pythonhosted.org/packages/37/ca/6a2b9dc95fd36bb098ec6318c21fd8e2419e5b770bd8c618b9d2d1077362/garth-0.5.17.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-06-22 05:34:24",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "matin",
"github_project": "garth",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "garth"
}