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("0")  # 0 = heart rate
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

License

MIT

Metadata

Release files for polar-flow-api 1.5.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 polar-flow-api 1.5.1
File Size Uploaded
polar_flow_api-1.5.1.tar.gz 140.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for polar-flow-api 1.5.1
File Interpreter ABI Platform
polar_flow_api-1.5.1-py3-none-any.whl Python 3 none any Details

Total release size: 185.3 kB

Release files / polar_flow_api-1.5.1.tar.gz

Download URL polar_flow_api-1.5.1.tar.gz
Size 140.2 kB
Tags Source
SHA-256 checksum
How to use checksums
118dd9ab6687f52f47777ca701bbce984d19f2ccaa0f6a597baed6cd59e118fc
BLAKE2b-256 checksum
How to use checksums
8506b98a6297cc61ce8702eacc9031fc479f7f2bfe1dcfac06a013b712b6a871
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 Oct 2, 2026.

Transparency log

Release files / polar_flow_api-1.5.1-py3-none-any.whl

Download URL polar_flow_api-1.5.1-py3-none-any.whl
Size 45.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9df27a7ba2b26743d11eccdd8b29f0bae77bf116c75e628b1fad27ea32bb83a8
BLAKE2b-256 checksum
How to use checksums
933a448b7695bd3a588dd162b89ae1cd5edbefdddf825eccec213489758f25d9
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 Oct 2, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.5.1 This release

2 release files

1.5.0

2 release files

1.4.0

2 release files

1.3.0

2 release files

1.1.0

2 release files

1.0.1

2 release files

1.0.0

2 release 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