API Reference
The Cloudflare Worker exposes a JSON REST API under the /api/ prefix. All responses have Content-Type: application/json and the following CORS headers:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, DELETE, PUT, OPTIONS
Access-Control-Allow-Headers: Content-Type
Blood Pressure
POST /api/blood-pressure
Record a new blood pressure measurement session. Exactly three readings are required. The Worker selects the best reading, calculates derived metrics, and assigns an AHA/ACC 2017 category.
Request Body
{
"readings": [
{ "systolic": 122, "diastolic": 78, "pulse": 65 },
{ "systolic": 120, "diastolic": 76, "pulse": 63 },
{ "systolic": 121, "diastolic": 77, "pulse": 64 }
],
"context": "morning",
"notes": "After coffee",
"tags": ["resting"]
}
| Field | Type | Required | Constraints |
|---|---|---|---|
readings | Array<Reading> | Yes | Exactly 3 elements |
readings[].systolic | number | Yes | 50–300 mmHg; must be > diastolic |
readings[].diastolic | number | Yes | 30–200 mmHg |
readings[].pulse | number | Yes | 20–300 bpm |
context | string | No | Free text |
notes | string | No | Free text |
tags | string[] | No | Must be pre-existing tag names; defaults to [] |
Response — 201 Created
{
"sessionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"bestReading": {
"systolic": 120,
"diastolic": 76,
"pulse": 63,
"pulsePressure": 44,
"MAP": 90.7,
"category": {
"label": "Normal",
"color": "#27ae60"
}
}
}
Errors
| Status | Message |
|---|---|
| 400 | Exactly three readings are required |
| 400 | Reading N: systolic must be between 50 and 300 |
| 400 | Reading N: diastolic must be less than systolic |
| 400 | tags must be an array of strings |
GET /api/blood-pressure
Retrieve a paginated list of blood pressure sessions, most recent first.
Query Parameters
| Parameter | Default | Max | Description |
|---|---|---|---|
limit | 7 | 100 | Number of sessions to return |
offset | 0 | — | Number of sessions to skip |
Response — 200 OK
{
"sessions": [
{
"id": "3fa85f64-...",
"recorded_at": "2026-03-22T07:14:00Z",
"context": "morning",
"notes": null,
"readings": [
{
"reading_number": 1,
"is_best": 0,
"metrics": { "systolic": 122, "diastolic": 78, "pulse": 65 }
},
{
"reading_number": 2,
"is_best": 1,
"metrics": {
"systolic": 120,
"diastolic": 76,
"pulse": 63,
"pulse_pressure": 44,
"mean_arterial_pressure": 90.7
}
},
{
"reading_number": 3,
"is_best": 0,
"metrics": { "systolic": 121, "diastolic": 77, "pulse": 64 }
}
],
"category": { "label": "Normal", "color": "#27ae60" }
}
],
"limit": 7,
"offset": 0
}
DELETE /api/blood-pressure/:id
Delete a measurement session and all associated observations and tags.
Path Parameters
| Parameter | Description |
|---|---|
:id | UUID of the session to delete |
Response — 200 OK
{ "deleted": "3fa85f64-5717-4562-b3fc-2c963f66afa6" }
Errors
| Status | Message |
|---|---|
| 400 | Session ID required |
Weight
POST /api/weight
Record a new weight measurement.
Request Body
{
"weight_kg": 82.5,
"notes": "After morning run"
}
| Field | Type | Required | Constraints |
|---|---|---|---|
weight_kg | number | Yes | 20–300 kg |
notes | string | No | Free text |
Response — 201 Created
{
"id": "b5e0a1f2-...",
"weight_kg": 82.5
}
Errors
| Status | Message |
|---|---|
| 400 | weight_kg must be between 20 and 300 |
GET /api/weight
Retrieve weight entries, most recent first.
Query Parameters
| Parameter | Default | Max |
|---|---|---|
limit | 30 | 100 |
Response — 200 OK
{
"entries": [
{
"id": "b5e0a1f2-...",
"weight_kg": 82.5,
"notes": "After morning run",
"recorded_at": "2026-03-22T07:30:00Z"
}
]
}
DELETE /api/weight/:id
Delete a weight entry.
Path Parameters
| Parameter | Description |
|---|---|
:id | UUID of the weight entry to delete |
Response — 200 OK
{ "deleted": "b5e0a1f2-..." }
Profile
GET /api/profile
Retrieve the singleton user profile. Always returns a record (either the stored profile or a default with height_cm: null).
Response — 200 OK
{
"id": 1,
"height_cm": 178.0,
"updated_at": "2026-01-10T09:00:00Z"
}
PUT /api/profile
Update the user’s height.
Request Body
{ "height_cm": 178.0 }
| Field | Type | Required | Constraints |
|---|---|---|---|
height_cm | number | Yes | 50–280 cm |
Response — 200 OK
{ "height_cm": 178.0 }
Errors
| Status | Message |
|---|---|
| 400 | height_cm must be between 50 and 280 |
Error Response Shape
All errors follow this shape:
{ "error": "Human-readable error message" }
Request Flow Diagram
External CPAP API (Read-Only)
The frontend also communicates with an external Worker at https://myair-sync.eddie-547.workers.dev. These endpoints are not part of this Worker but are documented here for completeness.
| Method | Path | Description |
|---|---|---|
| GET | /api/auth/status | Check MyAir authentication status |
| POST | /api/auth/start | Initiate MyAir login (triggers email MFA) |
| POST | /api/auth/verify | Submit MFA code to complete auth |
| POST | /api/sync-now | Trigger immediate CPAP data sync |
| GET | /api/cpap | Retrieve stored CPAP records |
| GET | /api/cpap-correlation | CPAP data correlated with next-morning BP |