Modern async Python client for Polar AccessLink API
Project description
polar-flow
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
- Python 3.11+
- Polar AccessLink API credentials from admin.polaraccesslink.com
Links
License
MIT
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file polar_flow_api-1.4.0.tar.gz.
File metadata
- Download URL: polar_flow_api-1.4.0.tar.gz
- Upload date:
- Size: 134.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a955b755bbb096678953769bdf47d9fffe4409dbb2b94222c6fc070d83d21cd3
|
|
| MD5 |
795d622769089818061622f18e320018
|
|
| BLAKE2b-256 |
44a382389d0847b933d9f70da69741409d954ecb937165f969fa9e8b2bfb8aab
|
Provenance
The following attestation bundles were made for polar_flow_api-1.4.0.tar.gz:
Publisher:
publish.yml on StuMason/polar-flow
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
polar_flow_api-1.4.0.tar.gz -
Subject digest:
a955b755bbb096678953769bdf47d9fffe4409dbb2b94222c6fc070d83d21cd3 - Sigstore transparency entry: 813623236
- Sigstore integration time:
-
Permalink:
StuMason/polar-flow@8cb2006b52fd8e6dafea68cb56ba4e6e2d6c347d -
Branch / Tag:
refs/heads/main - Owner: https://github.com/StuMason
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8cb2006b52fd8e6dafea68cb56ba4e6e2d6c347d -
Trigger Event:
push
-
Statement type:
File details
Details for the file polar_flow_api-1.4.0-py3-none-any.whl.
File metadata
- Download URL: polar_flow_api-1.4.0-py3-none-any.whl
- Upload date:
- Size: 42.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7527a23096f0d4d1a9de0a6df5946913e4eee27f1563e9145f11226abbf63a8e
|
|
| MD5 |
822cb1d389d00e2c0ae5828f7a2fcf08
|
|
| BLAKE2b-256 |
a2aef4786112f0e75d616218988484060218857e1abea81174162116e14597f5
|
Provenance
The following attestation bundles were made for polar_flow_api-1.4.0-py3-none-any.whl:
Publisher:
publish.yml on StuMason/polar-flow
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
polar_flow_api-1.4.0-py3-none-any.whl -
Subject digest:
7527a23096f0d4d1a9de0a6df5946913e4eee27f1563e9145f11226abbf63a8e - Sigstore transparency entry: 813623238
- Sigstore integration time:
-
Permalink:
StuMason/polar-flow@8cb2006b52fd8e6dafea68cb56ba4e6e2d6c347d -
Branch / Tag:
refs/heads/main - Owner: https://github.com/StuMason
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8cb2006b52fd8e6dafea68cb56ba4e6e2d6c347d -
Trigger Event:
push
-
Statement type: