CPAP Integration
Health Observability integrates with ResMed MyAir to pull nightly CPAP/sleep data. Because MyAir uses a proprietary authentication scheme (including email-based MFA), the integration is handled by a separate Cloudflare Worker (myair-sync) that owns credentials and scheduling. The main Health Observability Worker is read-only with respect to CPAP data.
Architecture
Note: The
myair-syncworker is a separate deployment. Thehealth-observabilityD1 database is shared between both workers via the same binding (or the CPAP data is stored in the sync worker’s own storage — the exact sharing mechanism is internal to the sync worker’s implementation).
Authentication Flow
ResMed MyAir requires email-based multi-factor authentication. The flow is managed entirely in the myair-sync worker and exposed via its auth endpoints.
Auth Status States
| State | needs_reauth | last_sync | Badge |
|---|---|---|---|
| Authenticated + synced | false | timestamp | Active (green) |
| Session expired | true | any | Re-auth needed (red) |
| Authenticated, never synced | false | null | Pending first sync (amber) |
| Worker unreachable | — | — | Unavailable (red) |
Sync Schedule
Data is synchronised from ResMed MyAir on two triggers:
- Scheduled sync — automatically at 8:00am daily (configured as a Cron Trigger in the
myair-syncworker) - Manual sync — the user clicks “Sync now” in the CPAP tab, triggering
POST /api/sync-now
The sync endpoint returns { synced: N } where N is the count of records written. The UI displays this in the status area after a manual sync.
Data Model
CPAP data is stored in the cpap_data table (Migration 08). Each row represents one night of sleep:
| Column | Type | Description |
|---|---|---|
log_date | TEXT (PK) | Sleep date YYYY-MM-DD |
ahi | REAL | Apnea-Hypopnea Index (events/hour) |
usage_minutes | INTEGER | Minutes of mask usage |
mask_leak_pct | REAL | Large leak percentage of total session |
myair_score | INTEGER | ResMed composite score 0–100 |
pressure_min | REAL | Minimum delivered pressure (cmH₂O) |
pressure_max | REAL | Maximum delivered pressure (cmH₂O) |
synced_at | DATETIME | Timestamp of last upsert |
log_date is the primary key, so nightly re-syncs are idempotent — the row is overwritten with the latest values.
CPAP API Endpoints (myair-sync Worker)
The frontend communicates with the following endpoints on the myair-sync worker:
GET /api/auth/status
Returns current authentication and sync status.
Response:
{
"needs_reauth": false,
"last_sync": "2026-03-22T08:05:00Z"
}
POST /api/auth/start
Initiates the ResMed MyAir login flow. ResMed sends an MFA code to the registered email address.
Response (MFA required):
{ "mfa_required": true }
Response (no MFA needed):
{ "success": true }
POST /api/auth/verify
Submits the MFA code to complete authentication.
Request:
{ "code": "123456" }
Response:
{ "success": true }
POST /api/sync-now
Triggers an immediate CPAP data sync.
Response:
{ "synced": 1 }
GET /api/cpap
Returns stored CPAP records, most recent first.
Query Parameters: limit (default 30)
Response:
{
"entries": [
{
"log_date": "2026-03-21",
"ahi": 2.1,
"usage_minutes": 427,
"mask_leak_pct": 3.2,
"myair_score": 88,
"pressure_min": 8.0,
"pressure_max": 12.4,
"synced_at": "2026-03-22T08:05:00Z"
}
]
}
GET /api/cpap-correlation
Returns CPAP nights joined to the next morning’s blood pressure reading.
Response:
{
"entries": [
{
"log_date": "2026-03-21",
"myair_score": 88,
"ahi": 2.1,
"usage_minutes": 427,
"mask_leak_pct": 3.2,
"systolic": 122,
"diastolic": 76
}
]
}
Entries where no morning BP reading exists will have systolic: null and diastolic: null. The frontend filters these out for the correlation chart (entries.filter(e => e.systolic)).
Python Integration (myair-py)
The cf-requirements.txt file declares myair-py==0.1.2e, a Python package that interfaces with the ResMed MyAir API. This dependency is used by the myair-sync worker’s Python layer (or is used in a separate data pipeline). The main health-observability JavaScript codebase does not directly depend on it.
cf-requirements.txt
└── myair-py==0.1.2e # ResMed MyAir API client