Skip to main content

polar-flow

CI PyPI Python Version License codecov

Modern async Python client for Polar AccessLink API.

Features

  • Async-first with httpx
  • Full type safety with Pydantic 2 and mypy strict mode
  • Python 3.11+ with modern syntax
  • Complete V3 API coverage
  • 90%+ test coverage

API Coverage

Complete Polar AccessLink V3 API implementation:

  • OAuth2 authentication with HTTP Basic Auth
  • Sleep endpoint (get/list sleep data)
  • Exercises endpoint (list/get/samples/zones/export TCX/GPX)
  • Activity endpoint (daily activity with steps/zones/inactivity)
  • Nightly Recharge endpoint (ANS charge, HRV, breathing rate)
  • Users endpoint (register/get/delete)
  • Physical Information endpoint (transaction-based body metrics)
  • CLI authentication tool

All endpoints tested and validated against real Polar API.

Install

pip install polar-flow-api

Quick Start

1. Get Access Token

# Set your Polar API credentials
export CLIENT_ID="your_client_id"
export CLIENT_SECRET="your_client_secret"

# Run interactive OAuth flow
polar-flow auth

This opens your browser, handles the OAuth callback, and saves the token to ~/.polar-flow/token.

2. Use the Client

import asyncio
from polar_flow import PolarFlow

async def main():
    async with PolarFlow(access_token="your_token") as client:
        # Get sleep data
        sleep_data = await client.sleep.list(user_id="self", days=7)
        for night in sleep_data:
            print(f"{night.date}: {night.sleep_score}/100 ({night.total_sleep_hours:.1f}h)")

        # Get exercises
        exercises = await client.exercises.list()
        for ex in exercises:
            print(f"{ex.start_time}: {ex.sport} - {ex.duration_minutes}min, {ex.calories}cal")

asyncio.run(main())

OAuth2 Flow

from polar_flow.auth import OAuth2Handler

oauth = OAuth2Handler(
    client_id="your_client_id",
    client_secret="your_client_secret",
    redirect_uri="http://localhost:8888/callback"
)

# Get authorization URL
auth_url = oauth.get_authorization_url()
print(f"Visit: {auth_url}")

# After user authorizes, exchange code for token
token = await oauth.exchange_code(code="authorization_code")
print(f"Access token: {token.access_token}")

Sleep API

# Get sleep for specific date
sleep = await client.sleep.get(user_id="self", date="2026-01-09")
print(f"Sleep score: {sleep.sleep_score}")
print(f"Total sleep: {sleep.total_sleep_hours}h")
print(f"Deep sleep: {sleep.deep_sleep_seconds / 3600:.1f}h")
print(f"REM sleep: {sleep.rem_sleep_seconds / 3600:.1f}h")
print(f"HRV average: {sleep.hrv_avg}ms")

# List sleep data for date range
sleep_list = await client.sleep.list(user_id="self", days=7)
for night in sleep_list:
    print(f"{night.date}: score {night.sleep_score}, {night.total_sleep_hours:.1f}h")

Exercises API

# List exercises (last 30 days)
exercises = await client.exercises.list()
for ex in exercises:
    print(f"{ex.start_time}: {ex.sport}")
    print(f"  Duration: {ex.duration_minutes} min")
    print(f"  Calories: {ex.calories}")
    if ex.distance_km:
        print(f"  Distance: {ex.distance_km} km")
    if ex.average_heart_rate:
        print(f"  Avg HR: {ex.average_heart_rate} bpm")

# Get detailed exercise
exercise = await client.exercises.get(exercise_id="123")

# Get exercise samples (HR, speed, cadence, altitude)
samples = await client.exercises.get_samples(exercise_id="123")
hr_sample = samples.get_sample_by_type("HEARTRATE")
if hr_sample:
    print(f"HR values: {hr_sample.values[:10]}")  # First 10 values

# Get heart rate zones
zones = await client.exercises.get_zones(exercise_id="123")
for zone in zones.zones:
    print(f"Zone {zone.index}: {zone.lower_limit}-{zone.upper_limit} bpm, {zone.in_zone_minutes} min")

# Export to TCX/GPX
tcx_xml = await client.exercises.export_tcx(exercise_id="123")
gpx_xml = await client.exercises.export_gpx(exercise_id="123")

Error Handling

from polar_flow.exceptions import (
    AuthenticationError,
    NotFoundError,
    RateLimitError,
    ValidationError,
)

try:
    data = await client.sleep.get(user_id="self", date="2026-01-09")
except AuthenticationError:
    print("Invalid or expired token")
except NotFoundError:
    print("No data for this date")
except RateLimitError as e:
    print(f"Rate limited. Retry after {e.retry_after} seconds")
except ValidationError as e:
    print(f"Invalid request: {e}")

CLI Commands

# Authenticate (opens browser)
polar-flow auth

# Authenticate with explicit credentials
polar-flow auth --client-id YOUR_ID --client-secret YOUR_SECRET

# Show version
polar-flow version

Development

git clone https://github.com/StuMason/polar-flow.git
cd polar-flow
uv sync --all-extras
uv run pytest

Requirements

Links

License

MIT

Download files

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

Source Distribution

polar_flow_api-1.5.0.tar.gz (139.8 kB view details)

Uploaded Source

Built Distribution

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

polar_flow_api-1.5.0-py3-none-any.whl (45.0 kB view details)

Uploaded Python 3

File details

Details for the file polar_flow_api-1.5.0.tar.gz.

File metadata

  • Download URL: polar_flow_api-1.5.0.tar.gz
  • Upload date:
  • Size: 139.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for polar_flow_api-1.5.0.tar.gz
Algorithm Hash digest
SHA256 7b356449cc952e6c0ecd2cd975b081362ab9ccf78ab2d305ffeb5949e3d0c538
MD5 5aa557fb1f2847fff3fb38917e50b868
BLAKE2b-256 0a9ed87898b571e8cfe4ff9c9a646de976a704abb66dd2f229be1f46e07a21f7

See more details on using hashes here.

Provenance

The following attestation bundles were made for polar_flow_api-1.5.0.tar.gz:

Publisher: publish.yml on StuMason/polar-flow

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

File details

Details for the file polar_flow_api-1.5.0-py3-none-any.whl.

File metadata

  • Download URL: polar_flow_api-1.5.0-py3-none-any.whl
  • Upload date:
  • Size: 45.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for polar_flow_api-1.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a6453f8dbc87e9491faa4bace882876104d9260d71fb20b6f656080e6e5d6ceb
MD5 9621b7da4c59f13763de4152ba2e20f1
BLAKE2b-256 39d09811b77565809311bc609f0032a6d768cc4a0ba392c4e676b2dff3cea093

See more details on using hashes here.

Provenance

The following attestation bundles were made for polar_flow_api-1.5.0-py3-none-any.whl:

Publisher: publish.yml on StuMason/polar-flow

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

Release history Release notifications | RSS feed

This release

1.5.0 This release

2 files

1.4.0

2 files

1.3.0

2 files

1.1.0

2 files

1.0.1

2 files

1.0.0

2 files

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