python-garminconnect logs in through the mobile app's flow
Python 3 API wrapper for Garmin Connect to get statistics and set activities
At a glance
- What is it?
- This is an unofficial Python wrapper for a consumer fitness account's data, covering fourteen categories and more than a hundred and fifty methods for health metrics, workouts, weight, gear, golf and device settings. Two things are worth knowing before you wire it into anything. Authentication reuses the vendor's mobile single sign-on flow programmatically rather than a browser, and version 0.3.0 dropped the previous session library with no way to convert saved tokens.
- Who is it for?
- This fits a developer who wants their own training data out of the account they already have, into a spreadsheet or a local analysis script, without reverse engineering the protocol themselves. Two things to settle before you depend on it.
- Can I use it commercially?
- Yes. MIT 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 last received commits 7 days ago.
- 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 4, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Login goes through the phone app, not a browser
The authentication section is short and its first sentence is the whole mechanism.
Authentication uses the same single sign-on flow as the vendor's official Android application, and no browser is needed.
That is the design decision that makes the rest of the library work, and it is also the part to think about. A browser-based sign-on is the well-trodden path; driving a phone application's sign-on programmatically is not, and the file does not describe the flow beyond saying which one it reuses. The section is cut off partway through describing its steps.
What you do with it is small:
garmin = Garmin(email, password)
garmin.login("~/.garminconnect") # later runs: Garmin().login("~/.garminconnect")You construct the client with an email address and a password, call login with a directory path, and it writes session tokens into a file there and reuses them on later runs. The demo program's output shows the same thing in two lines: an attempt to log in using stored tokens from a directory in your home folder, and a successful login using them.
The two runnable examples at the repository root are the simple one, which covers authentication, token storage and a few calls, and the interactive one, which is a menu over the whole surface.
Two of the three dependencies exist to change how you look on the network
The runtime dependency list has three entries, and two of them are about presentation rather than function.
One is an HTTP client that impersonates the TLS behaviour of other software. One is a user-agent generator. The third is an ordinary requests library with a floor.
There is no JSON library and no date library in that list, which tells you the wrapper hand-rolls or borrows those. What the list does tell you is that the HTTP layer is deliberately presenting itself as something other than a Python script, because a Python TLS fingerprint on a first-party application's internal interface is the first thing that gets blocked.
So this is a library whose reason for existing includes looking like a different client, and that is worth naming plainly rather than discovering when a request starts failing.
The install line says the same thing from the user's side. The command to install from the package index explicitly upgrades the TLS-impersonating client alongside the wrapper, which tells you a stale copy of that library is a known failure mode rather than a hypothetical one.
Version 0.3.0 dropped the old session library with no conversion path
There is an upgrade section and it is the most useful paragraph in the file.
Since version 0.3.0 the library no longer uses the previous session helper, which is deprecated. Sessions saved by older versions, named with two older token filenames and written by a specific function of that library, cannot be converted. And assigning a resumed client to an attribute on the main object no longer does anything at all.
So the migration is: log in once with your credentials again, and new tokens go to a different file in the same directory.
That is a clean break with no bridge, and the file says so in three separate sentences rather than burying it. It is the right call for a library whose session layer depends on a third-party project that is itself deprecated.
It also means an upgrading user's first run does a fresh sign-on through the mobile flow described above. For anyone whose account has multi-factor authentication on that flow, the upgrade instructions do not say what to expect.
The tests run against recordings, and the live ones are switched off
The test configuration is two lines and it explains how the suite is meant to be trusted.
The default options deselect the integration marker, and the marker itself is defined as requiring either a real account or a private recording of one.
So the default suite runs against recorded HTTP interactions rather than against the live service, and the tests that would hit the service are opt-in per name rather than opt-out by default. That is the correct arrangement for a wrapper of an undocumented interface: the recorded interactions are the contract, and the live tests are there for whoever wants to check whether the recording still matches reality.
The repository ships a directory of test data for the recordings and a file at the root describing the strategy, which is unusual placement for both and makes them easy to find.
There is also a type checker configured strictly, with untyped definitions disallowed and unused ignores warned about, and a security document in the tree, which for a library that takes a password is the file most worth reading after the authentication section.
The type checker has a hand-maintained allowlist, and it explains itself
The type checker configuration contains an override block with one module listed per line, and above it a comment explaining exactly why.
It says the tests deliberately omit return type annotations, following a convention established by a specific merged pull request, and that they stub out client methods by patching or by direct attribute assignment, which the checker cannot reason about. And it says each test module is listed explicitly because the tests directory has no package marker and the checker's glob patterns are unreliable for directories that are not packages.
That is three separate reasons written down in one place, each of which would otherwise look like somebody's private grudge against a lint rule.
It is also a maintenance surface: every new test module has to be added by hand to this list, and nothing in the failure mode would tell a contributor which line to add. The honest fix would be a package marker, which the comment identifies and does not do.
Elsewhere the checker is told to ignore missing imports, which is standard for a project this size, and to target one specific language version while the package itself supports two.
Three formatters, one type checker, and a spelling dictionary
The development command list is the inventory of this project's tooling habits, and the format and lint entries are the interesting ones.
The format command runs an import sorter, a formatter, and a third tool in fix mode. The lint command checks the same import sorter, the same third tool, the same formatter, and then the type checker.
So an import sorter and a formatter are each run alongside a second tool that overlaps with both of them. Three tools doing one job is not a sin, it is usually a migration that was never finished, and here the third tool is the one in fix mode during formatting, which suggests it arrived last.
Then there is a spelling check as its own command, a test command, a coverage command, an everything command that runs lint, spelling, hooks and tests together, and separate clean, build and publish commands.
The dependency manager is a Python-native task runner, and the file has a careful note about invoking it as a module on Windows, because installing it puts its executable outside the directories on the path there. That note is the most practically useful sentence in the development section.
The category counts add up to three more than the headline
The coverage section is a table of fourteen categories with a method count and a parenthetical summary for each, and the headline above it claims a hundred and fifty-four methods.
Add the fourteen counts and you get a hundred and fifty-seven. The claim says plus, so it is not wrong, but it is a snapshot label attached to a set that sums to three more than the number it names.
The distribution is the more interesting part. One category, activities and workouts, has thirty-seven methods, and another, hydration and wellness, has twenty. The two editing categories have two and five between them. So two thirds of the surface is in two domains and the long tail is thin.
The parenthetical summaries are where the real detail is, and they read like a work log. Training plans lists plans, lookup by identifier, typed strength workout upload, an exercise catalogue search, in-place editing, push to device, and scheduling management. Advanced health metrics names training readiness, training zones, running tolerance, training load balance and a daily training status, which is a list of proprietary derived metrics rather than raw sensor readings.
There is also one explicit non-feature in the summary: uploads are import-style and there is no re-export to a third-party running service.
Editorial conclusion
This fits a developer who wants their own training data out of the account they already have, into a spreadsheet or a local analysis script, without reverse engineering the protocol themselves. Two things to settle before you depend on it. Work out what you are depending on, because this is an unofficial wrapper of a first-party application's internal interface rather than a documented service, and the file says so plainly and points at the vendor's own site rather than at an API contract. And decide where the credentials live, because the login takes a username and a password and writes session tokens to a file in your home directory, with the security documentation next to the code rather than in front of it.
Frequently asked questions
Can I extract data from Garmin Connect?
Yes. The library wraps the account's data into fourteen categories and more than a hundred and fifty methods covering health metrics, activities and workouts, weight and body composition, nutrition, gear, device information, goals, historical trends and golf scorecards, plus interactive examples at the repository root that export everything they call to a file.
Do I need the Garmin Connect app?
The readme says the library authenticates using the same single sign-on flow as the vendor's official Android application and that no browser is needed. It does not say whether the application itself has to be installed on a device, and it does not describe the rest of the flow beyond that first sentence.
How much does it cost to use the Garmin API?
The file says nothing about cost, and it is not documenting a service with published terms. It is an unofficial wrapper of a first-party application's internal interface, and its own compatibility note points at the vendor's account site rather than at an API contract.
How can I read a .fit file in Python?
Not covered here. This library reads data through the account's web interface rather than from files, so it has nothing to say about the activity file format, and the activities section is all about uploading and editing workouts through the API rather than parsing anything on disk.
How do I install python-garminconnect?
One command from the package index, which explicitly upgrades the TLS-impersonating HTTP client alongside the wrapper. Python 3.12 or later is required. To run the bundled examples instead, create a virtual environment, install the project in editable mode with its example extra, and run either the simple or the interactive script.
Official sources
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.
[](https://hysenlabs.com/projects/cyberjunky-python-garminconnect)