Skip to content

Instantly share code, notes, and snippets.

Created July 22, 2011 04:06
What would you like to do?
bulk-export run data from Garmin Connect
#!/usr/bin/env ruby
## disconnect
# ./disconnect.rb -u yourusername
# This is a command-line utility for the bulk-downloading of run data from
# the web application, which has lackluster export
# capabilities.
# Using this code is a matter of your own relationship with Garmin Connect
# and their TOS. I can't imagine this being very destructive to their service,
# and it's just filling in a hole in their existing service.
# It's built against Garmin Connect as of July 22, 2011. It's a scraper:
# thus if Garmin changes, this **will break**.
# This script requires all of the utilities on the line below: install them
# with rubygems
%w{rubygems json fileutils mechanize choice highline/import}.map{|x| require x}
Choice.options do
header ''
header 'Specific options:'
option :user, :required => true do
short '-u'
long '--user=USER'
desc ' username. Required'
option :dir do
short '-o'
long '--output-dir=OUTPUT'
desc 'the directory to save .tcx files'
default 'tcx'
password = ask("Enter your password: " ) { |q| q.echo = "*" }
def login(agent, user, password)
page = agent.get(LOGIN_PAGE)
login_form = page.form('login')
login_form['login:loginUsernameField'] = user
login_form['login:password'] = password
page = agent.submit(login_form, login_form.buttons.first)
raise "Login incorrect!" if page.title().match('Sign In')
def download_run(agent, id)
print "."
# This downloads TCX files: you can swap out the constant, or add
# more lines that download the different kinds of exports. I prefer TCX,
# because despite being a 'private standard,' it includes all data,
# including heart rate data.
agent.get(TCX_EXPORT % (id).to_i).save_as(File.join(Choice[:dir], "%d.tcx" % id))
def activities(agent)
j = agent.get(ACTIVITIES_SEARCH)
search = JSON.parse(j.content)
runs = search['results']['activities'].map {|r|
# Get each activity id to insert into the download URL
}.map {|id|
# Download a run.
download_run(agent, id)
agent =
# One needs to log in to get access to private runs. Mechanize will store
# the session data for the API call that cames next.
home_page = login(agent, Choice[:user], password)
FileUtils.mkdir_p(Choice[:dir]) if not[:dir])
puts "Downloading runs..."
Copy link

Works perfectly! Thanks a lot for this. just used it to export a couple of years of cycling data from Garmin and import it into Strava.

Copy link

lukszp commented Sep 11, 2011

Works perfectly! Thanks a lot!

@pza --> thanks for your comment!

Copy link

Thanks for great inspiration on writing Garmin Connect backup script.

Copy link

dhaskew commented Sep 24, 2011

yeah, thanks for this.

Copy link

dano11 commented Oct 9, 2011

Ok. help me out here. Where and how do I copy and paste the above code? Thanks.

Copy link

Thanks a lot for this, but.. I get Net::HTTPBadRequest even befgore I get one activity, and Garmin's "Activity Search Service 1.0" is deprecated: Is it over..?

Copy link

tmcw commented Nov 18, 2011

@cloveras: try @gljeremy's fork:

@dano11 you'd copy & paste it into a file called disconnect.rb, and you'll need to gem install the dependencies on the top and run it with Ruby in a terminal.

Copy link

Yes! Works perfectly, thanks a lot for the quick reply!

Copy link

I note the TCX file downloaded does not contain the name or description associated with and activity on the garmin connect website, even though the file format supports that. :-( Can this script be extend to down loads as well?

Copy link

rumland commented Apr 8, 2013

Yes, thanks a lot for sharing. I also experienced the 400 => Net::HTTPBadRequest problem. Instead of limiting ACTIVITIES_SEARCH to 100, I limited it to 1 and changed the activities function to look like the following:
def activities(agent)
$activityIdx = 0
$numActivities = 624
while $activityIdx <= $numActivities do
puts("Downloading activity = #$activityIdx")
as = ACTIVITIES_SEARCH % $activityIdx
print "\tas: ", as, "\n"
j = agent.get(as)
search = JSON.parse(j.content)
runs = search['results']['activities'].map {|r|
# Get each activity id to insert into the download URL
}.map {|id|
# Download an activity.
download_run(agent, id)
$activityIdx += 1

In my case I wanted all of my history, 624.

Thanks a lot for sharing @tmcw!

Copy link

Garmin Connect UI has changed and this script no longer works for me.

Copy link

magsol commented May 18, 2014

If you're interested in a Python solution, I have one that does pretty much the same thing (in fact, this script was inspiration for it) and also contains a fix for the new Garmin Connect UI (basically just needs a redirect after login):

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment