Skip to main content

A Django app for the Garmin Health API (Garmin Connect Developer Program), backed by django-healthdatamodel.

Project description

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_garmin-0.1.0.tar.gz (62.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_garmin-0.1.0-py3-none-any.whl (27.8 kB view details)

Uploaded Python 3

File details

Details for the file django_garmin-0.1.0.tar.gz.

File metadata

  • Download URL: django_garmin-0.1.0.tar.gz
  • Upload date:
  • Size: 62.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for django_garmin-0.1.0.tar.gz
Algorithm Hash digest
SHA256 c85f0b3c3186302ff4d3bb58c8f72bc5f264cdfefff731653adb7ccfc7c5e12c
MD5 ad4f41a06553effe105fd0f9fce2ff19
BLAKE2b-256 2d8691d78c3aee3f43c6e8408839e955142b39517a2d7e99d7993be6149bf4cb

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_garmin-0.1.0.tar.gz:

Publisher: ci.yml on django-health/django-garmin

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file django_garmin-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: django_garmin-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 27.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for django_garmin-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b5dc67d3640a7fad504d202f55011b5657372bb43caaeb1619ae7124ea8166fb
MD5 7e0b0f80e888c809506569bf0b43de15
BLAKE2b-256 ab03c779413ec2f2fd06de15cfc98c7e8f8c7edf2f554d48cd1aea141c691f12

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_garmin-0.1.0-py3-none-any.whl:

Publisher: ci.yml on django-health/django-garmin

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page