@@ -12,7 +12,7 @@ This server:
12122 . Stores everything in PostgreSQL (your data, your server)
13133 . Provides an HTMX-powered admin dashboard
14144 . Exposes REST API for custom integrations
15- 5 . Multi-user ready (same codebase for self-hosted and SaaS)
15+ 5 . Multi-user ready with per-user API keys
1616
1717## Architecture
1818
@@ -65,6 +65,7 @@ The server syncs data every hour automatically.
6565The admin panel at ` /admin/dashboard ` shows:
6666
6767- ** Key Metrics** - HRV, Heart Rate, Training Strain, Alertness, Sleep Score
68+ - ** API Keys** - Manage per-user API keys with rate limit tracking
6869- ** Record Counts** - All 9 data types with totals
6970- ** Recent Sleep** - Last 7 days with scores
7071- ** Nightly Recharge** - HRV, ANS charge, recovery status
@@ -87,22 +88,103 @@ The admin panel at `/admin/dashboard` shows:
8788
8889## Configuration
8990
90- Environment variables (see ` .env.example ` ):
91+ ### Required Environment Variables
92+
93+ | Variable | Description | Required |
94+ | ----------| -------------| ----------|
95+ | ` DATABASE_URL ` | PostgreSQL connection string | Yes |
96+ | ` ENCRYPTION_KEY ` | 32-byte Fernet key for token encryption | ** Yes (production)** |
97+
98+ Generate an encryption key:
99+ ``` bash
100+ python -c " from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
101+ ```
102+
103+ ### Optional Environment Variables
104+
105+ | Variable | Description | Default |
106+ | ----------| -------------| ---------|
107+ | ` DEPLOYMENT_MODE ` | ` self_hosted ` or ` saas ` | ` self_hosted ` |
108+ | ` SYNC_INTERVAL_HOURS ` | Auto-sync frequency | ` 1 ` |
109+ | ` SYNC_ON_STARTUP ` | Sync when server starts | ` false ` |
110+ | ` SYNC_DAYS_LOOKBACK ` | Days of history to sync | ` 28 ` |
111+ | ` LOG_LEVEL ` | Logging verbosity | ` INFO ` |
112+ | ` API_KEY ` | Master API key (bypasses rate limits) | None |
113+
114+ ## API Authentication
115+
116+ The server supports per-user API keys with rate limiting.
117+
118+ ### Authentication Methods
119+
120+ 1 . ** Per-User API Keys** (recommended) - Each user gets their own API key via OAuth
121+ 2 . ** Master API Key** - Set ` API_KEY ` env var for full access (bypasses rate limits)
122+ 3 . ** Open Access** - If no ` API_KEY ` is set and no key provided, endpoints are open
123+
124+ ### Using API Keys
125+
126+ ``` bash
127+ # With per-user API key (includes rate limit headers)
128+ curl -H " X-API-Key: pfk_your_api_key_here" \
129+ http://localhost:8000/users/{user_id}/sleep? days=7
130+
131+ # Response headers include:
132+ # X-RateLimit-Limit: 1000
133+ # X-RateLimit-Remaining: 999
134+ # X-RateLimit-Reset: 1704067200
135+ ```
136+
137+ ### Rate Limiting
138+
139+ - Default: 1000 requests per hour per API key
140+ - Rate limits reset hourly
141+ - Master API key (` API_KEY ` env var) bypasses rate limiting
142+ - Rate limit info returned in response headers
143+
144+ ## OAuth Integration
145+
146+ For applications that need to integrate with polar-flow-server (e.g., mobile apps, web frontends):
147+
148+ ### OAuth Flow
149+
150+ 1 . ** Start OAuth** - Redirect user to ` /oauth/start?client_id=YOUR_CLIENT_ID `
151+ 2 . ** User Authorizes** - User logs into Polar and grants access
152+ 3 . ** Callback** - Server receives auth code, creates user, generates temp code
153+ 4 . ** Exchange** - Your app exchanges temp code for API key:
91154
92155``` bash
93- # Database
94- DATABASE_URL=postgresql+asyncpg://polar:polar@postgres:5432/polar
156+ POST /oauth/exchange
157+ Content-Type: application/json
158+
159+ {
160+ " code" : " temp_code_from_callback" ,
161+ " client_id" : " YOUR_CLIENT_ID"
162+ }
163+
164+ # Response:
165+ {
166+ " api_key" : " pfk_..." ,
167+ " user_id" : " polar_user_id" ,
168+ " expires_in" : null
169+ }
170+ ```
95171
96- # Deployment mode
97- DEPLOYMENT_MODE=self_hosted
172+ 5 . ** Use API Key** - Make authenticated requests with the API key
98173
99- # Sync settings
100- SYNC_INTERVAL_HOURS=1
101- SYNC_ON_STARTUP=false
102- SYNC_DAYS_LOOKBACK=28
174+ ### Key Management
175+
176+ ``` bash
177+ # Get key info
178+ GET /users/{user_id}/api-key/info
179+ X-API-Key: pfk_...
103180
104- # Optional: Set explicit encryption key (auto-generated otherwise)
105- # ENCRYPTION_KEY=your-32-byte-fernet-key
181+ # Regenerate key (invalidates old key)
182+ POST /users/{user_id}/api-key/regenerate
183+ X-API-Key: pfk_...
184+
185+ # Revoke key
186+ POST /users/{user_id}/api-key/revoke
187+ X-API-Key: pfk_...
106188```
107189
108190## API Endpoints
@@ -112,23 +194,26 @@ SYNC_DAYS_LOOKBACK=28
112194curl http://localhost:8000/health
113195
114196# Get sleep data (last 7 days)
115- curl " http://localhost:8000/users/{user_id}/sleep?days=7"
197+ curl -H " X-API-Key: pfk_..." \
198+ " http://localhost:8000/users/{user_id}/sleep?days=7"
116199
117200# Get activity data
118- curl " http://localhost:8000/users/{user_id}/activity?days=7"
201+ curl -H " X-API-Key: pfk_..." \
202+ " http://localhost:8000/users/{user_id}/activity?days=7"
119203
120204# Get nightly recharge (HRV)
121- curl " http://localhost:8000/users/{user_id}/recharge?days=7"
205+ curl -H " X-API-Key: pfk_..." \
206+ " http://localhost:8000/users/{user_id}/recharge?days=7"
122207
123208# Get exercises
124- curl " http://localhost:8000/users/{user_id}/exercises?days=30"
209+ curl -H " X-API-Key: pfk_..." \
210+ " http://localhost:8000/users/{user_id}/exercises?days=30"
125211
126212# Export summary
127- curl " http://localhost:8000/users/{user_id}/export/summary?days=30"
213+ curl -H " X-API-Key: pfk_..." \
214+ " http://localhost:8000/users/{user_id}/export/summary?days=30"
128215```
129216
130- ** Optional Authentication:** Set ` API_KEY ` environment variable to require ` X-API-Key ` header on all data endpoints. If not set, endpoints are open.
131-
132217## Development
133218
134219``` bash
@@ -163,24 +248,21 @@ docker-compose -f docker-compose.prod.yml up -d
163248
164249** Coolify, Railway, Render, etc.** - Point at the GitHub repo, it builds from the Dockerfile.
165250
166- ** Database migrations ** run automatically on startup.
167-
168- ### Optional Environment Variables
251+ ** Required for production: **
252+ - Set ` ENCRYPTION_KEY ` environment variable (tokens won't persist across restarts otherwise)
253+ - Set ` DATABASE_URL ` to your PostgreSQL instance
169254
170- | Variable | Description | Default |
171- | ----------| -------------| ---------|
172- | ` API_KEY ` | Require authentication on data endpoints | None (open) |
173- | ` SYNC_INTERVAL_HOURS ` | Auto-sync frequency | 1 |
174- | ` LOG_LEVEL ` | Logging verbosity | INFO |
255+ ** Database migrations** run automatically on startup.
175256
176257## Multi-Tenancy
177258
178259The server supports multiple users out of the box:
179260
180261- Every table includes ` user_id ` column
181262- All queries scoped by ` user_id `
263+ - Per-user API keys ensure users can only access their own data
182264- Self-hosted: typically one user
183- - SaaS : many users, same codebase
265+ - Multi-user : many users, same codebase
184266
185267## Built With
186268
0 commit comments