Documentation
Health Export Pro sends health data to your server as a JSON POST request. This page covers the payload format, authentication, and how to set up a receiver.
Quick Links
- Endpoint & Authentication
- Payload Structure
- Data Types
- Example Servers
- Setup Guides
- OpenAPI Spec
- Troubleshooting
Endpoint & Authentication
The app sends a POST request to:
{your_base_url}{endpoint_path}
The default endpoint path is /api/health-data. Configurable in
Settings.
Headers
| Header | Value | Notes |
|---|---|---|
Content-Type |
application/json |
Always present |
Authorization |
Bearer {api_key} |
When auth type is "Bearer" |
{custom_header} |
{api_key} |
When auth type is "Custom Header" |
Response
The app accepts any 2xx response as success. Optionally, your
server can return a JSON body with a record count:
{ "count": 42 }
Recognized count fields: count, total,
records, or inserted.measurements +
inserted.medications + inserted.workouts.
Payload Structure
{
"exportDate": "2026-02-26T12:00:00.000Z",
"startDate": "2026-02-25T00:00:00.000Z",
"endDate": "2026-02-26T00:00:00.000Z",
"syncSource": "manual-upload",
"characteristics": {
"biologicalSex": "male"
},
"data": {
"HKQuantityTypeIdentifierHeartRate": [ ... ],
"HKCategoryTypeIdentifierSleepAnalysis": [ ... ],
"workouts": [ ... ],
"medicationDoseEvents": [ ... ],
"medications": [ ... ]
},
"workoutRoutes": {
"I9J0K1L2-...": [ ... ]
}
}
| Field | Type | Description |
|---|---|---|
exportDate |
ISO 8601 | When the export was created |
startDate |
ISO 8601 | Start of the exported date range |
endDate |
ISO 8601 | End of the exported date range |
syncSource |
string |
Usually "manual-upload",
"background-delivery", or "widget"
|
characteristics |
object | Optional static HealthKit characteristics such as biological sex, date of birth, blood type, wheelchair use, and activity move mode |
data |
object | Health data keyed by HealthKit identifier or special section key |
workoutRoutes |
object | Optional GPS routes keyed by workout UUID |
Health Export Pro exports Apple Health data as directly as possible. Workout
durations are sent as durationSeconds using raw Apple Health
seconds. Special sections such as workouts,
medicationDoseEvents, medications, and
workoutRoutes appear only when relevant data exists.
Data Types
Quantity Samples
Keys like HKQuantityTypeIdentifierHeartRate,
HKQuantityTypeIdentifierStepCount, etc.
{
"uuid": "A1B2C3D4-...",
"value": 72,
"unit": "count/min",
"startDate": "2026-02-26T08:30:00.000Z",
"endDate": "2026-02-26T08:30:00.000Z",
"metadata": {},
"sourceRevision": {
"source": {
"name": "Apple Watch",
"bundleIdentifier": "com.apple.health.123"
},
"version": "11.0",
"operatingSystemVersion": "11.0.0",
"productType": "Watch7,3"
},
"device": {
"name": "Apple Watch",
"manufacturer": "Apple Inc.",
"model": "Watch",
"hardwareVersion": "Watch7,3",
"softwareVersion": "11.0"
}
}
| Field | Type | Description |
|---|---|---|
uuid |
string | HealthKit sample UUID |
value |
number | The measured quantity |
unit |
string |
HealthKit unit (e.g., "count/min", "mg/dL",
"m")
|
startDate |
ISO 8601 | Sample start |
endDate |
ISO 8601 | Sample end |
metadata |
object | HealthKit metadata dictionary (optional) |
sourceRevision |
object | Source app/device info (optional) |
device |
object | Hardware device info (optional) |
Category Samples
Keys like HKCategoryTypeIdentifierSleepAnalysis,
HKCategoryTypeIdentifierAppleStandHour, symptom/event
categories, and other HealthKit category identifiers.
Same structure as quantity samples but value is an integer
category value or event value defined by HealthKit.
Workouts
Key: workouts
{
"uuid": "I9J0K1L2-...",
"workoutActivityType": 37,
"durationSeconds": 3600,
"startDate": "2026-02-26T07:00:00.000Z",
"endDate": "2026-02-26T08:00:00.000Z",
"totalEnergyBurned": 450.5,
"totalDistance": 5200,
"totalDistanceUnit": "m",
"allStatistics": { ... },
"events": [ ... ],
"activities": [ ... ],
"metadata": {}
}
| Field | Type | Description |
|---|---|---|
workoutActivityType |
number | HealthKit workout type enum |
durationSeconds |
number | Raw Apple Health workout duration in seconds |
totalEnergyBurned |
number | Active calories (kcal) |
totalDistance |
number | Distance value |
totalDistanceUnit |
string |
Distance unit (e.g., "m", "km",
"mi")
|
allStatistics |
object | Workout statistics keyed by HealthKit identifier. Present on iOS 16+ when available |
events |
array | Pause/resume/lap markers when available |
activities |
array | Segmented workout activities on iOS 16+ when available |
GPS Routes
Included under workoutRoutes keyed by workout UUID:
{
"workoutRoutes": {
"I9J0K1L2-...": [{
"locations": [{
"latitude": 47.1234,
"longitude": 8.5678,
"altitude": 450.0,
"speed": 2.5,
"course": 180.0,
"date": "2026-02-26T07:00:05.000Z",
"horizontalAccuracy": 5.0,
"verticalAccuracy": 3.0
}]
}]
}
}
Medication Dose Events
Key: medicationDoseEvents
{
"uuid": "M3N4O5P6-...",
"medicationName": "Aspirin 81mg",
"medicationNickname": "Daily aspirin",
"scheduleType": 2,
"logStatus": 4,
"doseQuantity": 1,
"unit": "count",
"startDate": "2026-02-26T08:00:00.000Z",
"endDate": "2026-02-26T08:00:00.000Z",
"metadata": {}
}
Medication Definitions
Key: medications. These are static medication definitions
rather than timestamped dose events.
{
"nickname": "Daily aspirin",
"displayText": "Aspirin 81mg",
"isArchived": false,
"hasSchedule": true,
"generalForm": "tablet"
}
Example Servers
Node.js (Express)
const express = require("express");
const app = express();
app.use(express.json({ limit: "50mb" }));
app.post("/api/health-data", (req, res) => {
const { startDate, endDate, syncSource, data, workoutRoutes } = req.body;
const types = Object.keys(data);
const total = types.reduce(
(sum, key) => sum + (Array.isArray(data[key]) ? data[key].length : 0), 0
);
const workoutRouteCount = workoutRoutes
? Object.values(workoutRoutes).reduce(
(sum, routes) => sum + (Array.isArray(routes) ? routes.length : 0),
0
)
: 0;
console.log(
`[${new Date().toISOString()}] ${syncSource} ${startDate} -> ${endDate}: ${total} array items, ${workoutRouteCount} route sets`
);
// Store in your database of choice
res.json({ ok: true, count: total });
});
app.listen(3000, () => console.log("Listening on :3000"));
Python (Flask)
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/api/health-data", methods=["POST"])
def receive_health_data():
payload = request.get_json()
health_data = payload.get("data", {})
total = sum(len(v) for v in health_data.values() if isinstance(v, list))
print(
f"Received {total} samples "
f"({payload.get('syncSource')}, "
f"{payload.get('startDate')} -> {payload.get('endDate')})"
)
# Store in your database of choice
return jsonify({"ok": True, "count": total})
if __name__ == "__main__":
app.run(port=3000)
Setup Guides
Detailed guides for popular backends:
- REST API — Generic setup with Node.js/Python receivers, database examples (PostgreSQL, SQLite), and HTTPS configuration.
- InfluxDB + Grafana — Docker Compose stack with InfluxDB 2, Grafana dashboards, and a health data receiver. Includes Flux queries for heart rate, steps, sleep, and workouts.
OpenAPI Spec
The full payload schema is available directly from this site as a raw OpenAPI YAML file.
For interactive browsing, use the Swagger UI view.
Troubleshooting
| Issue | Solution |
|---|---|
| Connection failed | Ensure your server is reachable from your iPhone's network. Use HTTPS for non-local servers. |
| 401 Unauthorized | Check your API key and auth type in Settings match your server's expectations. |
| Large payloads timeout | For date ranges with many samples, increase your server's request body size limit (payloads can be 10-50 MB). |
| Missing data types |
Check the Data Types screen. Disabled types are excluded, and some
sections such as allStatistics, activities,
and medication fields are OS-version dependent.
|
| Sync not starting | Ensure Low Power Mode is off. Background App Refresh must be enabled in iOS Settings. |