Skip to main content

django-garmin

CI

A reusable Django app for the Garmin Health API (Garmin Connect Developer Program). Handles Garmin's OAuth 2.0 + PKCE flow, fetches wellness summaries from apis.garmin.com, receives push/ping webhook notifications, and persists everything through django-healthdatamodel so the same storage and query layer serves Apple Health, Fitbit, Google Health, and Garmin side-by-side.

Garmin's Health API is partner-gated: you need an approved Garmin Connect Developer Program app (an evaluation-tier app works) to obtain a consumer key/secret. OAuth 1.0a is retired at the end of 2026; this package speaks only the current OAuth 2.0 + PKCE flow.

Install

pip install django-garmin

Add both this app and django-healthdatamodel to INSTALLED_APPS, then run migrations:

INSTALLED_APPS = [
    ...
    "healthdatamodel",
    "garmin",
]
python manage.py migrate

The model uses settings.AUTH_USER_MODEL so it works with any custom user model.

Configuration

GARMIN_CLIENT_ID = "..."  # consumer key from the Garmin developer portal
GARMIN_CLIENT_SECRET = "..."  # consumer secret
GARMIN_REDIRECT_URI = "https://your-app.example.com/garmin/callback/"

Register the redirect URI in the Garmin developer portal — the token exchange fails on any mismatch. Garmin has no per-request scope parameter: the API sections your app can reach are fixed at app creation, and each user picks permissions (e.g. HEALTH_EXPORT, ACTIVITY_EXPORT) on the consent screen. The permissions a user actually granted are stored on their GarminConnection.permissions.

URLs

urlpatterns = [
    ...
    path("garmin/", include("garmin.urls")),
]

This mounts:

  • garmin/connect/ — start the OAuth 2.0 + PKCE flow (login required)
  • garmin/callback/ — the redirect URI target
  • garmin/disconnect/ — POST; calls DELETE user/registration at Garmin (required by their terms when you offer your own disconnect) and marks the connection revoked
  • garmin/notifications/ — webhook receiver for push/ping notifications

Mobile clients that run the OAuth dance themselves can POST the resulting token dict to a project-local endpoint that calls garmin.oauth.ingest_tokens.

Getting data

Garmin delivers data two ways; this package supports both.

Webhooks (push/ping) — Garmin's recommended architecture. Configure your summary-type endpoints in the developer portal to point at garmin/notifications/, then connect a signal handler:

from django.dispatch import receiver
from garmin.signals import notification_received
from garmin.webhooks import process_notification


@receiver(notification_received)
def on_notification(sender, payload, **kwargs):
    process_notification(payload)  # or hand off to celery / a queue

process_notification routes entries to users by Garmin's stable userId, fetches callbackURLs for ping-style notifications, and ingests everything into healthdatamodel. Garmin expects a fast 200 and disables endpoints that keep failing — hand off to a queue if your processing is heavy.

Pull. garmin.ingest.sync_user(connection, start=..., end=...) fetches and ingests the configured summary types. Note Garmin's pull semantics: the window filters by upload time (when the user's device synced), capped at 24 hours per request (the client chunks longer windows automatically). For deep history, request a backfill (GarminClient.request_backfill or manage.py sync_garmin --backfill); Garmin re-sends the data asynchronously through your webhook endpoints.

python manage.py sync_garmin                       # last 24h of uploads, all active connections
python manage.py sync_garmin --user alice --days 7
python manage.py sync_garmin --summary-type dailies --summary-type sleeps
python manage.py sync_garmin --user alice --days 90 --backfill

What gets stored

This app does not define Record / Workout tables — those live in django-healthdatamodel. The garmin.ingest module maps Garmin summaries to healthdatamodel inputs and persists them with source="garmin":

Garmin summary healthdatamodel records
dailies steps, active + BMR calories, distance, floors, resting HR, intensity minutes, intraday heart-rate samples
sleeps one sleep-stage record per sleepLevelsMap interval (deep/light/rem/awake)
bodyComps weight, body-fat %, BMI
pulseox SpO2 samples
activities one Workout per activity
epochs steps/calories/distance at 15-minute granularity — opt-in, double-counts against dailies

Garmin reports BMR directly (bmrKilocalories), so unlike the Google Health integration there is no BMR-estimation step.

The only model defined here is GarminConnection: per-user OAuth tokens (Garmin rotates the refresh token on every refresh — both are rewritten together), granted permissions, connection status, and last sync timestamp.

Try it on your own data

The repo includes a runnable demo Django project at demo/.

1. Get Garmin developer program access (one-time)

Apply at the Garmin Connect Developer Program — approval unlocks an evaluation environment. Create an app to get a consumer key/secret, and register the redirect URI http://localhost:8000/garmin/callback/ (exact match, trailing slash included).

2. Run the demo

uv sync
uv run python manage.py migrate
uv run python manage.py createsuperuser
export GARMIN_CLIENT_ID=...
export GARMIN_CLIENT_SECRET=...
uv run python manage.py runserver

Open http://localhost:8000/, sign in with the superuser you just created, then:

  • Click Connect Garmin → consent on Garmin Connect → land back on the homepage with a GarminConnection saved for your user.
  • Sync your watch in the Garmin Connect mobile app (pull windows filter by upload time), then click Sync now.
  • Browse the resulting rows at /admin/healthdatamodel/record/ (and .../workout/).

Or drive sync from the terminal:

uv run python manage.py sync_garmin --user <your-username>

Documentation

Garmin's Health API docs are partner-gated, so summaries live under docs/garmin/:

  • oauth2-pkce.md — the OAuth 2.0 + PKCE flow (endpoints, parameters, token lifetimes)
  • health-api.md — pull endpoints, summary payload shapes, push/ping webhooks, backfill

Development

uv sync --group dev
uv run pytest tests/ -v
uv run pre-commit run --all-files

Metadata

Release files for django-garmin 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-garmin 0.1.1
File Size Uploaded Provenance
django_garmin-0.1.1.tar.gz 62.6 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for django-garmin 0.1.1
File Interpreter ABI Platform
django_garmin-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 90.4 kB

Release files / django_garmin-0.1.1.tar.gz

Download URL django_garmin-0.1.1.tar.gz
Size 62.6 kB
Tags Source
SHA-256 checksum
How to use checksums
7b32e5617778e19e5c2e36f554e32e1d4e474e530bd5c0c679891a3ce9ddce97
BLAKE2b-256 checksum
How to use checksums
cae1ae3664ec4acc28b16ead7513b7b25555a29905459cb6d05004e54217cced
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.

Transparency log

Release files / django_garmin-0.1.1-py3-none-any.whl

Download URL django_garmin-0.1.1-py3-none-any.whl
Size 27.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1fd4447206390bb3284e509a19a0c2f5fcc2283bee620f037b3d32a3171f9e33
BLAKE2b-256 checksum
How to use checksums
2c57f625a0c6d95eea46589139ad753189f97088e25e07c6e575f91f6e385641
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.

Transparency log

Security for django-garmin 0.1.1

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for django_garmin-0.1.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.

Transparency log

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.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page