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

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:

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.