Open-Source Garmin Connect Python Uploader — Code Samples and Troubleshooting

Open-Source Garmin Connect Python Uploader — Code Samples and TroubleshootingGarmin Connect is a widely used platform for storing and analyzing activity data from Garmin devices. While Garmin provides official apps and sync tools, many developers and power users prefer automating uploads or integrating Garmin data with custom workflows. This article explains how to build and use an open-source Python uploader for Garmin Connect, includes code examples, covers authentication approaches, and provides troubleshooting tips for common issues.


Why build a Python uploader?

  • Automation: Automatically push activity files (FIT, TCX, GPX) from a local device, server, or CI pipeline.
  • Integration: Send Garmin activities to downstream tools — personal dashboards, training-analysis systems, backups, or third-party services.
  • Customization: Pre-process or annotate activities, add custom metadata, or filter uploads programmatically.
  • Control: Use open-source code to avoid vendor lock-in and inspect what the tool sends to Garmin.

Overview of approaches

There are three common approaches to uploading activities to Garmin Connect:

  1. Web API (reverse-engineered): Interact with Garmin Connect’s web endpoints. Many open-source tools use this approach but rely on undocumented endpoints so they can break if Garmin changes their frontend.
  2. Official device sync (Garmin Express / Garmin Mobile): Simulate device behavior or integrate locally; more complex and not typically open.
  3. Third-party APIs / integrations: Use intermediary platforms (if available) that accept uploads and sync to Garmin.

This article focuses on the reverse-engineered web API approach because it’s the most accessible for open-source Python projects.


Authentication: options and tradeoffs

Garmin’s web app uses session-based authentication and sometimes multi-step redirects and CSRF tokens. Common choices:

  • Username/password + session cookie: Script logs in with credentials, obtains session cookies, and uses them for uploading. Simpler but stores credentials.
  • OAuth (if available): Safer, token-based; Garmin historically has not exposed a simple OAuth flow for Connect in the same way as public APIs.
  • Manual cookie export: User logs in via a browser and exports session cookies for the script to reuse. Avoids storing password but requires manual steps and cookie refresh.
  • API key or developer token: Not generally available for Garmin Connect user uploads.

Tradeoffs:

  • Automation vs Security: Storing credentials enables full automation but requires secure storage (OS keyring, environment variables, secrets manager). Manual cookie export is less automated but avoids storing sensitive credentials in code.
  • Stability: Reverse-engineered endpoints can change; maintainers need to update the uploader when Garmin updates their site.

Example project layout

A minimal open-source uploader might look like:

  • garmin_uploader/
    • uploader.py — main upload functions
    • auth.py — authentication/session handling
    • cli.py — command-line interface
    • requirements.txt
    • README.md
    • examples/
      • upload_example.py

Key HTTP endpoints and parameters (example)

Note: Garmin changes endpoints occasionally. This example reflects typical reverse-engineered endpoints used by community tools (auth/login, upload, metadata). Always inspect network traffic in your browser devtools to confirm endpoints.

You’ll need to supply correct headers (User-Agent, Referer, X-Requested-With) and include cookies and CSRF tokens where required.


Minimal example: authenticate and upload (conceptual)

Below is a conceptual, minimal example that demonstrates the core flow: authenticate, initiate an upload, and send a file. This example omits error handling, retries, and production-level security. Use it as a starting point, not a drop-in solution.

# uploader.py import requests import json from pathlib import Path GARMIN_LOGIN = "https://sso.garmin.com/sso/login" GARMIN_UPLOAD_INIT = "https://connect.garmin.com/modern/proxy/upload-service-1.1/json/activities" def login(session: requests.Session, username: str, password: str):     payload = {         "username": username,         "password": password,         "embed": "false"     }     headers = {         "User-Agent": "Mozilla/5.0",         "Referer": "https://connect.garmin.com/modern"     }     resp = session.post(GARMIN_LOGIN, data=payload, headers=headers)     resp.raise_for_status()     # Further checks needed to ensure login succeeded and cookies set     return session def init_upload(session: requests.Session, filename: str, filetype: str = "fit"):     data = {         "dataType": filetype,         "fileName": filename     }     headers = {"Content-Type": "application/json"}     resp = session.post(GARMIN_UPLOAD_INIT, json=data, headers=headers)     resp.raise_for_status()     return resp.json() def upload_file_to_url(upload_url: str, file_path: Path):     with open(file_path, "rb") as f:         resp = requests.put(upload_url, data=f, headers={"Content-Type": "application/octet-stream"})     resp.raise_for_status()     return resp.status_code if __name__ == "__main__":     import getpass, sys     username = input("Garmin username: ")     password = getpass.getpass("Password: ")     path = Path(sys.argv[1])     session = requests.Session()     login(session, username, password)     init = init_upload(session, path.name, "fit")     upload_url = init.get("presignedUrl") or init.get("uploadUrl")  # depends on response     upload_file_to_url(upload_url, path)     print("Uploaded (check Garmin Connect)") 

Working with FIT, TCX, GPX files

  • FIT (binary, native Garmin) preserves full device data including sensors; preferred when available.
  • TCX is XML-based and commonly used for workouts.
  • GPX is simpler and often used for GPS traces (no sensor/channel data).

If you only have GPX/TCX, the uploader should label the file type correctly when initializing the upload. For FIT files, ensure the uploader sends the correct MIME type and that the server accepts binary.

If you need to convert between formats, consider libraries:

  • fitparse (read FIT)
  • gpxpy (read/write GPX)
  • lxml or xml.etree for TCX
  • python-fitparse for conversion; other community tools exist for format conversion.

CLI usage example

Provide a simple CLI so users can automate runs or plug into scripts.

# cli.py import argparse from uploader import login, init_upload, upload_file_to_url import requests from pathlib import Path def main():     p = argparse.ArgumentParser(description="Upload activity to Garmin Connect")     p.add_argument("file", type=Path, help="Path to activity file (.fit/.tcx/.gpx)")     p.add_argument("--username", required=True)     p.add_argument("--password", required=True)     args = p.parse_args()     session = requests.Session()     login(session, args.username, args.password)     init = init_upload(session, args.file.name, args.file.suffix.lstrip("."))     upload_url = init.get("presignedUrl") or init.get("uploadUrl")     upload_file_to_url(upload_url, args.file)     print("Upload complete") if __name__ == "__main__":     main() 

Troubleshooting common issues

  1. Authentication failures

    • Ensure credentials are correct. Some accounts use SSO (Google/Facebook) and require manual cookie extraction or a different flow.
    • Check for multi-factor auth (MFA). If MFA is present, automated login with username/password may fail.
    • Verify the login endpoint and request payload—Garmin may change parameter names or require specific headers/cookies.
  2. 403 / Unauthorized on upload

    • Session cookies not carried over. Use the same requests.Session for login and subsequent requests.
    • Missing CSRF/anti-forgery token. Extract tokens from the HTML or response headers and include them in requests.
    • Upload endpoint expects a specific Referer and User-Agent. Mirror browser headers.
  3. 400 / Bad request on init

    • Incorrect JSON schema or missing fields. Compare to what the Garmin web UI sends via devtools.
    • Wrong dataType or filename format.
  4. File rejected or processing fails

    • File corrupted or incorrect format. Validate file with local tools (e.g., fitparse, gpxpy).
    • Wrong MIME type when uploading to a presigned URL. Use application/octet-stream or the type returned by init response.
  5. Rate limits or CAPTCHAs

    • Implement exponential backoff and retries.
    • If a CAPTCHA appears, automated scripts may not work; consider manual intervention.
  6. DNS, TLS, or SSL errors

    • Ensure requests uses up-to-date CA certificates (use certifi with requests).
    • Network middleboxes or proxies may interfere—test from a different network.

Best practices for open-source projects

  • Store credentials securely: recommend OS keyring, environment variables, or secrets managers.
  • Don’t hardcode endpoints that may change; allow configurable base URLs.
  • Provide clear instructions for manual cookie export as a fallback.
  • Use retries with exponential backoff for transient network errors.
  • Add automatic tests that mock Garmin endpoints so you can adapt quickly when real endpoints change.
  • Include a CONTRIBUTING.md and clear license (MIT, Apache 2.0, etc.) so others can contribute fixes when Garmin updates break the uploader.
  • Respect Garmin’s Terms of Service; avoid scraping or aggressive automated behavior.

Example error-resolution workflow

  1. Reproduce the issue locally with verbose logging of requests and responses.
  2. Compare request/response with the browser’s network trace to spot header, token, or payload differences.
  3. Adjust headers or payload accordingly; run unit tests.
  4. If an endpoint changed, update the code and add a test that mimics the new behavior.
  5. Tag a new release and document the fix in CHANGELOG.md.

Alternatives and integrations

  • Use third-party platforms like Strava or TrainingPeaks (if you already have integrations) and sync between them and Garmin where supported.
  • Consider using a self-hosted solution that stores activities centrally and forwards them to multiple services for redundancy.
  • For bulk archival, download activities from Garmin (when supported) and store them in cloud storage rather than pushing individually.

Community resources

Search GitHub for community projects (keywords: garmin-connect, garmin-uploader, python) to find sample implementations, forks, and active maintainers. Forks often adapt to Garmin changes quickly; look for projects with recent commits and active issues.


Conclusion

An open-source Garmin Connect Python uploader gives you automation, control, and integration capabilities. Because it relies on reverse-engineered endpoints, expect occasional maintenance when Garmin updates their web stack. Follow best practices for authentication security, error handling, and project maintainability to keep your uploader reliable.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *