Releasesv0.1.0

Open mHealth output

Read records as Open mHealth data points, checked against the official schemas.

Open Health Sync serves a user's records as Open mHealth data points, so a tool built on the Open mHealth schemas (a research pipeline, a mobile health study, an analysis notebook) can read them without learning the REST JSON shapes. Each data point is a header and a body that follows one schema of the Open mHealth catalog. Every mapping is checked against the official schemas by the unit tests, on every push (see Validation).

Daily summaries, category samples, nutrition, sleep phases, routes and the quantity types without an Open mHealth schema are not in this version.

Route

RouteAnswers
GET /v1/users/{id}/omh/data-pointsone page of data points, {"data": [...], "next_cursor": ...}

It takes a tenant key, like every read, and answers application/json. A user of another tenant is a 404; errors are application/problem+json like the rest of the API.

The list is the FHIR search's list (see fhir.md): the same records, in the same order (time, then kind, then id), with the same kind of cursor. A record that has no Open mHealth schema is left out of its page, so a page can hold fewer than limit data points and still have a next_cursor: follow next_cursor until it is null.

ParameterMeaning
schemaonly the data points of one schema: bare (heart-rate) or with its namespace and, optionally, its version (omh:heart-rate, omh:heart-rate:2.0). One value; a schema the mapping does not emit is a 400.
start, endRFC 3339 or YYYY-MM-DD; start <= the record's time < end
limit, cursorpage size (default 100, at most 1000) and the next_cursor of the previous page
viewmerged (the default) or all, as in the API
curl -s "$OHS/v1/users/$USER_ID/omh/data-points?schema=omh:heart-rate&start=2026-03-01&limit=1" \
  -H "Authorization: Bearer $KEY"
{
  "data": [
    {
      "header": {
        "id": "qs-101",
        "creation_date_time": "2026-03-01T09:15:30.25Z",
        "schema_id": { "namespace": "omh", "name": "heart-rate", "version": "2.0" },
        "acquisition_provenance": {
          "source_name": "apple_health",
          "source_data_point_id": "hk-uuid-1",
          "modality": "sensed"
        },
        "user_id": "3f0c2f8e-8b8a-4c55-9f5e-2b0f3a1d9c11"
      },
      "body": {
        "effective_time_frame": { "date_time": "2026-03-01T08:00:00Z" },
        "heart_rate": { "value": 62, "unit": "beats/min" }
      }
    }
  ],
  "next_cursor": "MjAyNi0wMy0wMVQwODowMDowMFp8MXwxMDE"
}

Mapping

Header memberValue
idthe record's id, the same as its FHIR Observation id: qs- (a quantity sample), bp- (a blood pressure pair, the systolic sample's id), sleep-, workout-, then the stored row id
creation_date_timewhen Open Health Sync stored the record (ingested_at; for a blood pressure pair, the later of the two halves)
schema_idomh, the schema's name and version
acquisition_provenance.source_namethe provider the record came from (apple_health, oura, withings...)
acquisition_provenance.source_data_point_idthe provider's own id of the record (external_id), when it gave one
acquisition_provenance.modalityself-reported for a sample the user typed in, sensed for one the source says was measured, left out when the source does not say
user_idthe end user's id

Bodies

Each body holds the value, its unit as Open mHealth spells it, and effective_time_frame: a time_interval (start_date_time, end_date_time) when the record has an end after its start, else a date_time. A schema that only accepts an interval (step count, calories burned, sleep episode) gets an interval from the start to the start when the record has no end. Times are RFC 3339 in UTC.

RecordsSchemaBody
heart_rateomh:heart-rate:2.0heart_rate in beats/min
resting_heart_rateomh:heart-rate:2.0heart_rate in beats/min, temporal_relationship_to_physical_activity: "at rest"
step_countomh:step-count:3.0step_count in steps
active_energy_burnedomh:calories-burned:2.0kcal_burned in kcal
body_massomh:body-weight:2.0body_weight in kg
heightomh:body-height:1.0body_height in cm
body_mass_indexomh:body-mass-index:2.0body_mass_index in kg/m^2
body_fat_percentageomh:body-fat-percentage:1.0body_fat_percentage in %
body_temperatureomh:body-temperature:3.0body_temperature in C
oxygen_saturationomh:oxygen-saturation:2.0oxygen_saturation in %
respiratory_rateomh:respiratory-rate:2.0respiratory_rate in breaths/min
blood_glucoseomh:blood-glucose:3.0blood_glucose in mg/dL
a blood pressure pairomh:blood-pressure:3.1systolic_blood_pressure and diastolic_blood_pressure in mmHg
a sleep sessionomh:sleep-episode:1.1the bedtime as effective_time_frame, total_sleep_time in sec when known
a workoutomh:physical-activity:1.2activity_name (the activity type, trail running), distance in m and kcal_burned in kcal when known

A blood pressure pair is a systolic and a diastolic sample with the same user, provider, start, end, source and device, as in the FHIR output; a half without its partner has no data point.

A sample has a data point only in its type's storage unit (the unit the data model lists, which the pipeline converts to), and only with a finite value. A workout distance or energy that is not a finite number is left out of the body.

Schema versions

Each schema is the newest version whose definitions are all in the Open mHealth schema repository. Since 2022 the repository has been moving to the IEEE 1752.1 standard: the newest majors of several schemas (for example heart-rate 3.0, blood-pressure 4.0) differ from the versions above only in that their building blocks (unit value, time frame) are IEEE 1752.1 schemas hosted outside the repository, and some schemas (step-count 3.0, sleep-episode 1.1, physical-activity 1.2, header 1.2) are marked as superseded by their IEEE 1752.1 counterparts. The versions above are the ones that can be checked end to end against the repository's own files. IEEE 1752.1 output is not in this version.

Validation

internal/omh/testdata holds a copy of the official schemas the mapping emits and every schema they refer to, and the repository's own test data for them, taken unchanged from openmhealth/schemas (Apache-2.0) at a pinned commit named in internal/omh/testdata/README.md. The unit tests, part of make test and CI:

  • run the repository's shouldPass and shouldFail examples through the schemas, so the validator is known to accept and refuse what the repository expects;
  • check every golden data point (internal/omh/testdata/golden, one per mapping) against omh:data-point:1.0 with its header, and its body against the schema its header names, with formats (date-time) asserted;
  • check that every quantity type the mapping names is in the seeded catalog with the storage unit the mapping expects.
go test ./internal/omh              # validate
go test ./internal/omh -update      # rewrite the golden files after a mapping change

Not in this version

  • Daily summaries: Open mHealth has no daily summary schema. Their steps and energy are in the REST list, the FHIR output and the exports.
  • Quantity types without an Open mHealth schema here (heart rate variability, VO2 max, the scores...), category samples, nutrition, sleep phases and routes.
  • IEEE 1752.1 data points (see Schema versions).
  • One data point by id: the list has no single-item route.
Edit on GitHub

On this page