gc2everywhere — Garmin Connect to Everywhere
Keep a copy of your Garmin workouts. Send them to the services you choose.
This Python tool uses Garmin's official Personal API to download original activity files, save them in a folder you control, and send new recordings to Intervals.icu and Effort Signal. A local Dropbox folder works like any other folder. No Garmin password or browser automation is required.
Why this exists
Independent services have built useful, sometimes unique features that Garmin does not offer. They have helped people get more from their devices and given them more reasons to buy Garmin products.
Garmin's move toward paid commercial API access makes life harder for those services. Its current program distinguishes a limited Business Starter tier from Enterprise access with negotiated commercial terms.
We think it is disappointing that Garmin has chosen a direction that puts more of the burden on ordinary users, after independent developers helped make its products more valuable. People should not have to become integration engineers to keep using their own workout data. This small tool is our practical response: keep your files, choose your services, and keep moving.
This is an independent project, not an official Garmin product.
Disclaimer: Provided "as is", without warranties, under the MIT License. To the extent permitted by applicable law, the authors and copyright holders are not liable for losses or damages arising from its use, including data loss, failed or duplicate transfers, account restrictions, or service charges. You are responsible for backups, API keys, and complying with each service's terms.
Install
You need Python 3.11 or newer. Install from PyPI in a virtual environment:
On macOS or Linux:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install gc2everywhere
On Windows, in PowerShell:
py -m venv .venv
.venv\Scripts\python.exe -m pip install gc2everywhere
The examples use gc2e. On Windows, use .venv\Scripts\gc2e.exe instead.
On macOS/Linux, activate .venv again in a new terminal or use the full path
to .venv/bin/gc2e.
To install from a source checkout instead, run python -m pip install . from
the repository directory with the virtual environment activated.
Get your keys
Garmin: open Personal API settings in Garmin Connect, create a Personal API key, choose its expiry, and copy it when shown. This is not an OAuth application client secret. Garmin offers expiry choices of 30, 90, 180 days or one year. See the Personal API FAQ.
Intervals.icu: sign in, open Settings, scroll to Developer Settings, and create an API key. No athlete ID is needed: the tool uses the account belonging to the key. See the maintainer's API access instructions.
Effort Signal: open Settings → Integrations → Personal API keys, select
Create API key, give it a name such as gc2everywhere, allow
activities:write, and choose an expiry. Copy the key immediately, it is shown
once. Other data-write permissions are unnecessary for workout files. See the
Personal API key guide for creation,
permissions, usage, expiry, and revocation.
First run
On macOS or Linux, paste your keys into these environment variables. Omit a destination key if you do not want to send files to that service.
export GARMIN_API_KEY='your-garmin-personal-api-key'
export INTERVALS_API_KEY='your-intervals-api-key'
export EFFORTSIGNAL_API_KEY='your-effortsignal-personal-api-key'
gc2e sync --archive-dir "$HOME/Dropbox/Garmin"
On Windows PowerShell:
$env:GARMIN_API_KEY = 'your-garmin-personal-api-key'
$env:INTERVALS_API_KEY = 'your-intervals-api-key'
$env:EFFORTSIGNAL_API_KEY = 'your-effortsignal-personal-api-key'
.venv\Scripts\gc2e.exe sync --archive-dir "$HOME\Dropbox\Garmin"
The folder is created if needed. There are no setup questions. The first sync
checks the last seven days, downloads recordings, and sends them to the services
whose keys you supplied. Later runs catch up from the last completed scan with a
seven-day overlap, so recent late uploads are found too. For older workouts,
use import-history.
For archive only, omit destination keys or add --archive-only.
Keys pasted into a terminal may remain in shell history. For regular use, keep
the exports in a private file outside Dropbox and this repository, restrict its
permissions (chmod 600 /path/to/private-keys.sh), and load it with
source /path/to/private-keys.sh. Never share keys in arguments, logs, screenshots
or scripts. Revoke and replace an exposed key.
Four commands
| Command | What it does |
|---|---|
sync |
Downloads new recordings, sends them to configured services, and retries earlier deliveries. |
import-history |
Downloads past recordings. Never sends them automatically. |
send |
Explicitly sends saved, undelivered recordings. No Garmin key needed. |
status |
Shows local progress and errors. Makes no network requests. |
Save your history first:
gc2e import-history --from 2020-01-01 --archive-dir "$HOME/Dropbox/Garmin"
Send the saved history to one service when you are ready:
gc2e send --destination intervals --archive-dir "$HOME/Dropbox/Garmin"
Or select a date range:
gc2e send --destination effortsignal --from 2026-01-01 --to 2026-10-21 \
--archive-dir "$HOME/Dropbox/Garmin"
Dates are inclusive and use the workout's local start date in Garmin. The
history available to your key is controlled by Garmin; a requested range cannot
override its access restrictions. --destination can be repeated.
Adding a destination key later does not send the existing archive to it.
Use send for that. Workouts discovered during --archive-only also need an
explicit send. A key for a different account creates a different delivery
identity; use it only if you intend to send files to that account.
Files, progress, and retries
Garmin/
.gc2everywhere.json
2026/10/
123456789.fit
123456789.zip
123456789.json
The filename is the Garmin activity ID, not the workout name or a timestamp. The ZIP is the unchanged original; FIT is extracted for easy use elsewhere. JSON records the checksum and stable upload revision. A raw FIT response does not create a ZIP.
Complete existing files are reused. Interrupted downloads never become finished files. Repeated runs skip completed downloads and deliveries. A changed or corrupt local file produces an error instead of silently replacing it. Manual activities without a downloadable FIT are logged as skipped. ZIPs containing several FIT files are not treated as one workout.
The delivery journal and Garmin's temporary access token live outside Dropbox,
under ~/.local/state/gc2everywhere/ by default (XDG_STATE_HOME is honored).
--state-dir overrides that location. Keep one writer per archive: a local
lease prevents overlapping runs using the same journal, but cannot coordinate
different computers writing to Dropbox.
After moving an archive or losing the journal, sidecars restore the inventory
without downloading files again. Previous service receipts cannot be recovered
locally, so restored history stays unsent until you run send. Receivers also
check duplicates; exactly-once delivery cannot be guaranteed if a connection
fails after the server accepts an upload.
Effort Signal's accepted means queued. Later runs poll its receipt and mark it
sent only after publication. failed and needs_review require inspection in
Effort Signal. Failure in one destination does not stop the others. Rate limits
honor Retry-After; retryable upload errors use increasing delays across runs.
This is a recording synchronizer, not a mirror of all Garmin data. It does not sync wellness metrics, deletions, subsequent workout edits, or manually changed files.
Logs and scheduling
Logs include activity IDs, discovery, filenames, sizes, skips, each destination's outcome, retries, and a final count. They exclude keys, authorization headers, raw API responses, and FIT contents. IDs and dates are still personal data.
gc2e sync --archive-dir "$HOME/Dropbox/Garmin" --log-file "$HOME/gc2everywhere.log"
gc2e status --archive-dir "$HOME/Dropbox/Garmin"
Logs rotate at 2 MiB with three backups. --verbose adds HTTP request counts.
--json prints a machine-readable summary to stdout; logs use stderr.
Exit codes: 0 successful run, 1 operational error, 2 invalid arguments,
130 interrupted.
For a large history, --max-activities 50 bounds each download and destination
batch. Repeat the command to continue; completed work is retained.
Run sync every 30 minutes with your computer's scheduler. On macOS/Linux,
create a private script using absolute paths:
#!/bin/sh
. /absolute/path/to/private-keys.sh
exec /absolute/path/to/gc2everywhere/.venv/bin/gc2e sync \
--archive-dir /absolute/path/to/Dropbox/Garmin \
--log-file /absolute/path/to/gc2everywhere.log
Add */30 * * * * /bin/sh /absolute/path/to/sync-garmin.sh with crontab -e.
On Windows, use Task Scheduler for a private PowerShell script with the
environment assignments and command above. A sleeping computer catches up
on its next run.
Cloudflare Workers
Experimental: See CLOUDFLARE_WORKERS.md in the repository or the
source distribution on PyPI.
The Worker deployment files are included in that source archive; installing
the CLI with pip does not deploy a Worker.
Contributing a destination
Pull requests adding other services are welcome. Destinations are independent
modules under src/gc2everywhere/destinations/. Implement the Destination contract:
- Declare a name and the environment variable for its API key.
connect()verifies access and sets a stable account identity.send(row, content, filename)uploads and returns aDelivery.- For asynchronous processing, return
acceptedwith a receipt and implementpoll(receipt). Returnsentonly after confirmation. - Register the class in
DESTINATIONS, document key creation, and add mocked tests for request format, failures, retries, and duplicates.
Use the shared transport so request limits and safe diagnostics work locally and in Workers. Never log secrets or follow credential-bearing redirects. Document Worker limitations if a service does not accept ZIP; local delivery supplies FIT.
Developer checks, with no keys or live API calls:
uv sync --group dev
uv run pytest -q
uv run ruff check src tests
uv run ruff format --check src tests
Metadata
Release files for gc2everywhere 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| gc2everywhere-0.1.0.tar.gz | 68.4 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| gc2everywhere-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 98.9 kB
Release files / gc2everywhere-0.1.0.tar.gz
| Download URL | gc2everywhere-0.1.0.tar.gz |
|---|---|
| Size | 68.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0344195e30374d542f95b7735b25dffa64d795c5b5ab78963fcb12032117b04b
|
|
BLAKE2b-256 checksum How to use checksums |
e4361214be294d871f6134270fee0ab33033185b1883e0831ca8416affd6e58d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.23 {"installer":{"name":"uv","version":"0.11.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / gc2everywhere-0.1.0-py3-none-any.whl
| Download URL | gc2everywhere-0.1.0-py3-none-any.whl |
|---|---|
| Size | 30.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
31d768fa0a0a2452a0187858381bd200270d663cda8fafc89d9dd751fd55f86b
|
|
BLAKE2b-256 checksum How to use checksums |
b2d8dfa6618332cb83acd16cd07e94475e4afd4b95732792d97fdad48cf2ce6c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.23 {"installer":{"name":"uv","version":"0.11.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for gc2everywhere 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.