Library / SDK
petergardfjall/garminexport avatar
petergardfjall/garminexport

garminexport: a backup tool for Garmin Connect activities

[DEPRECATED] Garmin Connect activity exporter and backup tool

562 stars88 forksPythonApache-2.0

At a glance

What is it?
Your activity history lives on somebody else's server, and there is a category of software whose entire purpose is to get it back onto your disk in a form you control. garminexport is one of those, for Garmin Connect, and it is also a short lesson in how such a tool depends on the undocumented behaviour of the service it talks to. The repository carries a deprecation notice at the top, and the reason it gives is the most interesting part.
Who is it for?
garminexport fits somebody who wants a local copy of their own activity history in a format they control, particularly in the training data format that the device itself writes. It does not fit anybody who wants a maintained tool, because the repository says it is not maintained and the reason is not likely to change.
Can I use it commercially?
Yes. Apache-2.0 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 156 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 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What it is, and why it stopped

garminexport is both a library and a tool for downloading and backing up Garmin Connect activities to local disk. The main utility performs incremental backups of an account to a local directory, and the repository also contains a client module you can import to build your own tools against the service's interface.

The first thing in the readme, though, is a notice, and it is a decommission notice rather than a changelog. The project is no longer actively maintained, and the author's reason is given in the same breath: there is neither the time nor the motivation to keep battling to circumvent Garmin's bot protection.

That sentence is the most informative thing in the repository, and it is worth understanding what it says about this whole class of software.

A tool like this cannot use the documented interface, because the documented interface for activity data does not exist in a form that supports bulk export. So it uses the same authentication flow that the phone application uses, and it calls the endpoints the phone application calls, and it gets through the bot protection that stands in front of a service designed to keep automated traffic out. Every one of those steps is an inference from observed behaviour rather than a contract, and every one of them can change without notice.

The notice then says what the last attempt depended on. The authentication was handed to an upstream library, and that project is no longer maintained either. So the tool is at the bottom of a chain: an unmaintained client library, on top of an unmaintained authentication library, talking to a service whose protection mechanisms are actively tuned against exactly this kind of client.

That is the honest shape of the project, and it is a much better readme than a repository that claims to work. A reader now knows three things: the tool does not work reliably any more, the reason is structural rather than a bug, and nobody is going to fix it.

The other thing the notice does is date the project. The last push to the repository is recent but the project itself is old, and it is on the package index with a published version, a supported range of Python versions, and a build badge. It is a finished tool that has been abandoned rather than an experiment.

The backup script and how the increment works

The backup program takes a directory and an account name, and the flag for the directory is required to be explicit.

bash
garmin-backup --backup-dir=activities <username or email>

Once started it prompts for the password, logs in, and downloads the activities to the directory you named. And the incremental behaviour is the point of the tool.

The first run downloads every activity the account holds. Every later run downloads only the activities that have not already been downloaded to the backup directory.

That is a simple rule, and simplicity is what makes it a good rule for a backup tool. The check is presence: if the file is in the directory, it was already fetched. There is no database, no manifest, no state file, no comparison of modification times. The directory is the state.

That design has a property that matters. A backup tool that keeps its own index can lose the index, and then the index and the truth disagree and the tool has to guess. A backup tool that derives its state from the filesystem cannot have that failure, because the filesystem is the state. The cost is that the naming has to be deterministic, and the readme says how: each activity file is prefixed by its upload timestamp and its activity id.

That prefix is doing two jobs. It makes the files sort chronologically in a directory listing, which is what you want when the directory is a hundred thousand activities. And it makes the name unique, because the upload timestamp and the activity id together identify the activity, and a file named after the activity id alone would collide with a re-upload of the same activity with different content.

All the files for one activity are written to the same directory, with the same prefix and a different extension. So an activity downloaded in the training data format and in the track format appears as two files whose names differ only at the end, which is what you want when you are looking at one activity's data in two shapes.

There is one more option worth noting. By default the program downloads all the formats for every activity, and the format flag narrows the selection. That default is the right one for a first backup, because you do not yet know which format you will want later, and the formats are not equally available for every activity, which is the subject of the next section.

Authentication, and the token on disk

The authentication section describes a flow that is worth reading carefully, because it explains what the tool depends on.

As a user you supply a username and a password, and you are prompted for a multi-factor code if the account has it enabled. That information is handed to the authentication library, which executes the same authentication flow as the Garmin Connect Android application in order to exchange the credentials for an OAuth2 token, and that token is what signs the subsequent interface requests.

So the tool does not implement a protocol of its own. It uses a library that imitates the phone application's login, which is a strong signal that the mobile application is the reference implementation for whatever the service expects, and that anything else has to copy it.

Then the token handling, which is the part a user needs to know about. The token is stored under a directory in the user's home, or under whatever directory the auth token directory flag names, and it is reused for as long as it has not expired. Only when the OAuth token has expired does a new login happen.

That is the right behaviour for a backup tool you run on a schedule. The login is the fragile part, because it may involve a second factor prompt, which needs a human, and it may fail because the service changed something. The token is the cheap part, because it is just a file and it works until it does not.

So the design minimises the number of times the fragile operation happens, and the token's expiry is the clock. A user who runs the backup every night with a second factor enabled will be prompted more often than they expect, because the token's lifetime, not the schedule, decides when the prompt appears.

The storage location being configurable is a small kindness with a real purpose. A backup tool that runs on a machine with several accounts, or on a shared build machine, needs to keep tokens apart, and a tool that hardcodes one path in the home directory makes that awkward.

And there is a security consideration that the readme does not belabour but that follows from the design: the token is a bearer credential for the account, stored in plain text in a file, and the password is never stored at all. The tool asks for the password when it needs to exchange it for a token, and then keeps the token. That is the right order of operations for a credential that is used once per token lifetime.

Five formats, and what each one is for

The format list is the longest part of the readme, and it is a good list because each entry says what the format is and when you cannot have it.

The track format is the activity as an XML file. It is described as an open format, mainly for storing routes and tracks, and it supports extensions, and the readme notes that the manufacturer appears to annotate the track data with things like heart rate and cadence when the device recorded them.

The training format is the manufacturer's own XML format, essentially an extension of the track format that includes more metrics and divides the track into laps as recorded by the device, with a lap summary for each metric. That is the format to use if you want to analyse intervals, because the laps are the unit your analysis cares about.

The binary format is the raw data type stored in the device, and the readme says it should contain all the metrics the device is capable of tracking, including the position, heart rate and cadence. It is a binary format, so tools are needed to read its content.

And then two JSON formats, one with a summary of the activity and one with the details. Both come with the same caveat in almost the same words: they seem to lack a formal schema and should not be counted on as a stable data format, since it may change at any time. They are included only because they may contain additional data that could be useful for developers of analysis tools.

That is an unusually honest description of a company's own output format, and it is a warning worth heeding: a file with no schema and no stability promise is a file your analysis code will break on.

The unavailability notes are the other valuable part. A training format file may not always be possible to export, for example if the activity was uploaded in the track format, in which case the service will not try to synthesise one. And a binary format file may not always be possible, for example if the activity was entered manually rather than imported from a device.

Both notes describe the same thing from two sides. The service does not compute these formats on demand; it stores what the device uploaded. So the format you can have depends on how the activity was created, and a user with a mixture of imported and manually entered activities will have a mixture of available formats. That is a property of the data, not of the tool, and the readme says so rather than letting you discover it as a missing file.

The library side, and the shape of the repository

The tool is also a library, and the readme tells you how to use it that way.

You import the client module, which handles authentication and establishes a secure session with the service, and the command line tools under the package's client directory are given as the example to read. The packaging instruction is the standard one: in your project file, in whichever of the three formats you use, add the package to the install requirements, with a link to the specification that defines that key for modern packaging metadata.

So the library is the interesting half for a developer, and the fact that the readme points at the command line tools as the thing to read is a good sign: the tools are the worked example of the library's use.

There are also two smaller programs, and they are the natural pair. One downloads a single activity, and the other uploads a single activity file, in any of the three main formats. A backup tool with an upload command is a complete tool for moving activity data in and out of the service, which is what somebody consolidating data from several devices actually needs.

The contribution section is short and conventional: create a virtual environment, install the dependencies, and you are set up. The readme mentions the environment and the dependency installation and then the rest is cut off in the version available here.

The repository's shape is small and predictable. There is the package itself, the tests, the continuous integration configuration that produces the build badge, the project configuration, and the standard governance files. The badges tell the story of a tool that was published and maintained properly: a package index release badge, a badge for the supported range of Python versions, and a licence badge.

There are no tagged releases in the repository, which is worth noting: the versioning happened on the package index, through the usual publishing flow, and the repository's own history is the source rather than the distribution. The licence is the permissive Apache 2.0, and the deprecation notice at the top of the readme is the only thing a prospective user really needs to read before installing it.

Editorial conclusion

garminexport fits somebody who wants a local copy of their own activity history in a format they control, particularly in the training data format that the device itself writes. It does not fit anybody who wants a maintained tool, because the repository says it is not maintained and the reason is not likely to change. Verify first that your account does not require a second factor prompt in a way the flow cannot handle, that the service still accepts the mobile application's authentication handshake, since the whole approach depends on that continuing to work, and that your use of the service's interface is within its terms, which is a question the tool cannot answer for you.

Frequently asked questions

What does the garminexport tool do?

It downloads activities from a Garmin Connect account to a local directory, incrementally, so that later runs fetch only what is not already there. It also exports each activity in five formats and can upload a single activity file back, and it ships as a library with a client module for building your own tools.

Why is garminexport no longer maintained?

The author's stated reason is having neither the time nor the motivation to keep circumventing the service's bot protection. The last version depended on an upstream authentication library which is also no longer maintained, so the whole chain rests on behaviour of the service that is inferred rather than documented.

How does garminexport authenticate?

You give it a username and a password, and a multi-factor code if the account requires one. The credentials are handed to an authentication library that performs the same flow as the phone application, exchanging them for an OAuth2 token that signs the later requests. The token is stored in a directory under your home folder, or one you choose, and reused until it expires.

Which export formats does garminexport support?

Five: the activity as a track file, the manufacturer's training format with laps and more metrics, the binary raw format the device writes, and two JSON formats for the summary and the details. The readme notes that the training and binary formats cannot always be produced, because the service stores what the device uploaded, and that the JSON formats have no formal schema and are not stable.

How does the incremental backup in garminexport decide what to download?

By checking the backup directory. The first run downloads every activity, and later runs download only the activities whose files are not already there. There is no separate index or manifest, so the directory itself is the state, and each file is prefixed with the upload timestamp and the activity id to keep the names unique and in order.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. petergardfjall/garminexport on GitHub
  4. README
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/petergardfjall-garminexport.svg)](https://hysenlabs.com/projects/petergardfjall-garminexport)

Was this useful?