# Polar Health Data (self-hosted)

**Category:** 💻 Developer Tools  
**Repository:** https://github.com/StuMason/polar-flow-server  
**Views:** 0  
**Installs:** 0  
**Upvotes:** 0  
**Directory Page:** https://allmcps.com/mcp/polar-health-data-self-hosted

## Description
Self-hosted Polar health analytics with a built-in MCP server: sleep, HRV, workouts, baselines.

## Claude Desktop Quick Installation
Heuristic fallback — verify the package name and runner against the repository README before running it. Uses `npx` (confidence: low):

```json
"mcpServers": {
  "polar-health-data-self-hosted": {
    "command": "npx",
    "args": ["-y","polar-health-data-self-hosted"]
  }
}
```

## Documentation & README

# polar-flow-server

Self-hosted health analytics for Polar devices — own your data, analyze it against your own baselines, and let your AI assistant read it.

[![Tests](https://github.com/StuMason/polar-flow-server/actions/workflows/tests.yml/badge.svg)](https://github.com/StuMason/polar-flow-server/actions/workflows/tests.yml)
[![Docs](https://img.shields.io/badge/docs-mkdocs-blue)](https://stumason.github.io/polar-flow-server/)
[![Docker](https://img.shields.io/docker/v/stumason/polar-flow-server?label=docker)](https://hub.docker.com/r/stumason/polar-flow-server)
[![MCP](https://img.shields.io/badge/MCP-2026--07--28-6549d5)](https://stumason.github.io/polar-flow-server/mcp-server/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

![Dashboard](docs/assets/dashboard-today.png)

**[Full Documentation](https://stumason.github.io/polar-flow-server/)** · [MCP Server](https://stumason.github.io/polar-flow-server/mcp-server/) · [Integration Guide](https://stumason.github.io/polar-flow-server/integration/) · [API Reference](https://stumason.github.io/polar-flow-server/api/overview/)

## What This Does

Your watch knows more about you than you do — and Polar's API only lets you see the last 28-30 days of it. This server syncs everything, keeps it forever, and turns it into answers:

1. Syncs all **13 Polar API endpoints** automatically — sleep, HRV, activity, workouts, SpO2, ECG, skin temperature, the lot
2. Stores everything in PostgreSQL. Your data, your server, no cloud between you and it
3. Computes **personal baselines** (rolling averages, IQR anomaly bounds) so "is this normal?" means normal *for you*
4. Ships a **built-in MCP server with OAuth sign-in** — ask Claude "should I train hard today?" and it answers from your overnight HRV vs your baseline
5. Admin dashboard (HTMX), REST API, per-user API keys, multi-user ready

## Ask Your AI About Your Body (MCP)

A built-in [Model Context Protocol](https://modelcontextprotocol.io) server — protocol revision **2026-07-28**, streamable HTTP — runs inside the main server at `/mcp`. Ten curated tools cover the one-shot health assessment, sleep, recovery, activity, workouts, seven biosensing streams, personal baselines, patterns/anomalies, and sync control.

In clients that render [MCP Apps](https://modelcontextprotocol.io/extensions/apps/overview) (claude.ai, Claude Desktop, VS Code), asking "how am I doing?" draws an actual card in the conversation:

![MCP Apps card](docs/assets/mcp-apps-card.png)

**Connecting is a sign-in, not a paste.** With `BASE_URL` set, the server is its own OAuth 2.1 authorization server: add `https://your-server/mcp` as a custom connector in Claude Desktop or claude.ai, click **Connect**, log in on *your* server, approve the consent screen. Tokens are user-scoped, expire hourly, refresh automatically, and every connected app is revocable from Settings. API keys still work for headless clients:

```bash
claude mcp add polar-health https://your-server.example.com/mcp \
  --transport http \
  --header "X-API-Key: pfk_your_key_here"
```

Full setup in the [MCP docs](https://stumason.github.io/polar-flow-server/mcp-server/).

## Architecture

```
Polar API → polar-flow SDK → Sync Service → PostgreSQL
                                                  ↓
                                           Admin Dashboard (HTMX)
                                                  ↓
                                             REST API
```

**Stack:**
- Litestar (async web framework)
- SQLAlchemy 2.0 (async ORM)
- PostgreSQL
- HTMX + Tailwind (admin UI)
- polar-flow SDK v1.5.0

> **Don't fancy running a server?** A hosted version is in the works — [join the waitlist](https://pulse.stumason.dev). Self-hosting stays free forever.

## Quick Start

### Option 1: Docker (Recommended)

```bash
# Pull and run
curl -O https://raw.githubusercontent.com/StuMason/polar-flow-server/main/docker-compose.prod.yml
docker-compose -f docker-compose.prod.yml up -d

# That's it. Open http://localhost:8000/admin
```

### Option 2: From Source

```bash
git clone https://github.com/StuMason/polar-flow-server.git
cd polar-flow-server
docker-compose up -d
```

### Setup

1. Open http://localhost:8000/admin
2. Get Polar credentials from [admin.polaraccesslink.com](https://admin.polaraccesslink.com) (set redirect URI to `http://localhost:8000/admin/oauth/callback`)
3. Enter credentials and click "Connect with Polar"
4. Hit "Sync Now" to pull your data

The server syncs data every hour automatically.

## Dashboard

The admin panel at `/admin/dashboard` is organised into tabs (with a
floating tab bar on mobile):

- **Overview** - stat tiles (HRV, resting HR, SpO2, skin temp, steps, strain,
  sleep score, alertness...), Today's Readiness recommendations, and
  "Today at a Glance" mini-charts (sleep stages, heart rate, steps)
- **Trends & Baselines** - personal baselines and detected patterns
- **Sleep** - sleep score and stage-duration charts
- **Heart Rate** - daily HR, HRV and ANS charge charts, biosensing panel
- **Training Load** - activity and cardio load charts

![Trends and baselines](docs/assets/dashboard-trends.png)

Charts have a selectable 7/14/30-day range and CSV export. API keys are
managed from the settings page, with rate limit tracking. All frontend
assets are vendored - the dashboard works offline and on a LAN with no
CDNs.

## Data Synced (13 Endpoints)

| Endpoint | Data |
|----------|------|
| **Sleep** | Score, stages (light/deep/REM), duration |
| **Nightly Recharge** | HRV, ANS charge, recovery status |
| **Daily Activity** | Steps, distance, calories, active time |
| **Exercises** | Sport, duration, HR zones, training load |
| **Cardio Load** | Strain, tolerance, load ratio, status |
| **SleepWise Alertness** | Hourly alertness predictions |
| **SleepWise Bedtime** | Optimal sleep timing recommendations |
| **Activity Samples** | Minute-by-minute step data |
| **Continuous HR** | All-day heart rate (5-min intervals) |
| **SpO2** | Blood oxygen tests (compatible devices) |
| **ECG** | Electrocardiogram tests (compatible devices) |
| **Body Temperature** | Continuous body temperature |
| **Skin Temperature** | Nightly skin temperature with baseline deviation |

## Configuration

### Required Environment Variables

| Variable | Description | Required |
|----------|-------------|----------|
| `DATABASE_URL` | PostgreSQL connection string | Yes |
| `ENCRYPTION_KEY` | 32-byte Fernet key for token encryption | **Yes (production)** |

Generate an encryption key:
```bash
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
```

### Optional Environment Variables

| Variable | Description | Default |
|----------|-------------|---------|
| `DEPLOYMENT_MODE` | `self_hosted` or `saas` | `self_hosted` |
| `SYNC_INTERVAL_HOURS` | Auto-sync frequency | `1` |
| `SYNC_ON_STARTUP` | Sync when server starts | `false` |
| `SYNC_DAYS_LOOKBACK` | Days of history to sync | `28` |
| `LOG_LEVEL` | Logging verbosity | `INFO` |
| `API_KEY` | Master API key (bypasses rate limits) | None |

## API Authentication

**API endpoints require authentication.** Health data should never be publicly accessible.

### Authentication Methods

1. **Per-User API Keys** (recommended) - Create from the admin dashboard or via OAuth flow
2. **Master API Key** - Set `API_KEY` env var for full access (bypasses rate limits)

### Using API Keys

```bash
# With per-user API key (includes rate limit headers)
curl -H "X-API-Key: pfk_your_api_key_here" \
  http://localhost:8000/api/v1/users/{user_id}/sleep?days=7

# Response headers include:
# X-RateLimit-Limit: 1000
# X-RateLimit-Remaining: 999
# X-RateLimit-Reset: 1704067200
```

### Rate Limiting

- Default: 1000 requests per hour per API key
- Rate limits reset hourly
- Master API key (`API_KEY` env var) bypasses rate limiting
- Rate limit info returned in response headers

## OAuth Integration (SaaS / Multi-User)

For applications that need to integrate with polar-flow-server (e.g., Laravel, mobile apps, web frontends).

This allows **any Polar user** to connect their account to your application.

### OAuth Flow

```
┌─────────────────┐     ┌─────────────────────┐     ┌─────────────────┐
│  Your App       │────▶│  polar-flow-server  │────▶│  Polar Flow     │
│  (Laravel etc)  │     │                     │     │  (OAuth)        │
│                 │◀────│                     │◀────│                 │
└─────────────────┘     └─────────────────────┘     └─────────────────┘
```

**Step 1: Redirect user to start OAuth**

```
GET /oauth/start?callback_url=https://yourapp.com/callback&client_id=your-app-name
```

| Parameter | Required | Description |
|-----------|----------|-------------|
| `callback_url` | Yes | Where to redirect after OAuth (your app's callback endpoint) |
| `client_id` | No | Identifier for your app (validated during exchange) |

**Step 2: User authorizes on Polar**

User is redirected to Polar, logs in with their credentials, and authorizes your app.

**Step 3: User redirected to your callback**

```
https://yourapp.com/callback?code=TEMP_CODE_HERE
```

**Step 4: Exchange temp code for API key (server-to-server)**

```bash
POST /oauth/exchange
Content-Type: application/json

{
  "code": "TEMP_CODE_HERE",
  "client_id": "your-app-name"
}
```

Response:
```json
{
  "api_key": "pfk_abc123...",
  "polar_user_id": "12345678",
  "expires_at": null
}
```

**Step 5: Store and use the API key**

Store `api_key` and `polar_user_id` for this user. Use the API key for all data requests:

```bash
curl -H "X-API-Key: pfk_abc123..." \
  "https://your-polar-server.com/api/v1/users/12345678/sleep?days=7"
```

### Polar Admin Setup

In [admin.polaraccesslink.com](https://admin.polaraccesslink.com), set your app's redirect URI to:

```
https://your-polar-server.com/oauth/callback
```

### Key Management

```bash
# Get key info
GET /api/v1/users/{user_id}/api-key/info
X-API-Key: pfk_...

# Regenerate key (invalidates old key)
POST /api/v1/users/{user_id}/api-key/regenerate
X-API-Key: pfk_...

# Revoke key
POST /api/v1/users/{user_id}/api-key/revoke
X-API-Key: pfk_...
```

## API Endpoints

```bash
# Health check (no auth required)
curl http://localhost:8000/health

# Get sleep data (last 7 days)
curl -H "X-API-Key: pfk_..." \
  "http://localhost:8000/api/v1/users/{user_id}/sleep?days=7"

# Get activity data
curl -H "X-API-Key: pfk_..." \
  "http://localhost:8000/api/v1/users/{user_id}/activity?days=7"

# Get nightly recharge (HRV)
curl -H "X-API-Key: pfk_..." \
  "http://localhost:8000/api/v1/users/{user_id}/recharge?days=7"

# Get exercises
curl -H "X-API-Key: pfk_..." \
  "http://localhost:8000/api/v1/users/{user_id}/exercises?days=30"

# Export summary
curl -H "X-API-Key: pfk_..." \
  "http://localhost:8000/api/v1/users/{user_id}/export/summary?days=30"
```

## Development

```bash
# Install dependencies
uv sync --all-extras

# Start PostgreSQL
docker-compose up -d postgres

# Run server with hot reload
uv run uvicorn polar_flow_server.app:app --reload

# Run tests
uv run pytest

# Type check
uv run mypy src/polar_flow_server

# Lint
uv run ruff check src/
```

## Production Deployment

Deploy anywhere that runs Docker:

```bash
# Download and run
curl -O https://raw.githubusercontent.com/StuMason/polar-flow-server/main/docker-compose.prod.yml
docker-compose -f docker-compose.prod.yml up -d
```

**Coolify, Railway, Render, etc.** - Point at the GitHub repo, it builds from the Dockerfile.

**Required for production:**
- Set `ENCRYPTION_KEY` environment variable (tokens won't persist across restarts otherwise)
- Set `DATABASE_URL` to your PostgreSQL instance

**Database migrations** run automatically on startup.

## Multi-Tenancy

The server supports multiple users out of the box:

- Every table includes `user_id` column
- All queries scoped by `user_id`
- Per-user API keys ensure users can only access their own data
- Self-hosted: typically one user
- Multi-user: many users, same codebase

## Built With

- [polar-flow](https://github.com/StuMason/polar-flow) - Python SDK for Polar AccessLink API
- [Litestar](https://litestar.dev/) - Async web framework
- [SQLAlchemy](https://www.sqlalchemy.org/) - Async ORM
- [HTMX](https://htmx.org/) - Admin UI interactions
- [Tailwind CSS](https://tailwindcss.com/) - Styling

## License

MIT

