Open Health Sync

السجل الرسمي الصحة

Read-only access to self-hosted health data: samples, sleep, workouts, nutrition, daily summaries.

ماذا يمكنك أن تفعل مع Open Health Sync؟

  • A health app backend. Your iOS app uploads HealthKit data, your users connect their
  • An AI agent over your own data. Run it at home, connect your devices, and give your desktop
  • A study that collects from participants' wearables. Raw payloads kept for audit, hard
  • A personal data archive. Load years of history from the Health app export in one command,
  • Small tools on your own data. ohs admin personal setup gives you one instance for

التثبيت

أضف هذا إلى إعدادات MCP في عميلك (Claude Desktop: claude_desktop_config.json، وCursor: ‏.cursor/mcp.json، ويستخدم VS Code وغيره الصيغة نفسها «mcpServers»).

OCI package ghcr.io/soma-intelligence/open-health-sync:0.2.0
Claude Code
claude mcp add open-health-sync -e 'OHS_MCP_URL=<OHS_MCP_URL>' -e 'OHS_MCP_KEY=<your ohs_mcp_key>' -- docker run -i --rm ghcr.io/soma-intelligence/open-health-sync:0.2.0 mcp
إعداد JSON (Claude Desktop وCursor وVS Code)
{
    "mcpServers": {
        "open-health-sync": {
            "command": "docker",
            "args": [
                "run",
                "-i",
                "--rm",
                "ghcr.io/soma-intelligence/open-health-sync:0.2.0",
                "mcp"
            ],
            "env": {
                "OHS_MCP_URL": "<OHS_MCP_URL>",
                "OHS_MCP_KEY": "<your ohs_mcp_key>"
            }
        }
    }
}

متغيرات البيئة

  • OHS_MCP_URL إلزامي Base URL of your Open Health Sync instance. Must be https unless the host is loopback (inside a container, loopback is the container itself).
  • OHS_MCP_KEY إلزامي سري A user token with the read scope (ohs_ut_...) or a tenant API key (ohs_sk_...).

التوثيق

مأخوذ من ملف README للمشروع على GitHub ويُحدَّث أسبوعيًا. تفتح الروابط على GitHub.

المحتويات
  1. Open Health Sync
  2. Hosted beta
  3. Roadmap at a glance
  4. What you can build
  5. Why
  6. Features
  7. Quickstart
  8. How it works
  9. Connect an AI agent (MCP)
  10. Data model
  11. Deploy
  12. Open Health Sync vs Terra and other health data APIs
  13. FAQ
  14. Status and roadmap
  15. Documentation
  16. Contributing
  17. Security
  18. License

Open Health Sync

One open API for wearable and health data. On your own server.

Apple Health, Withings, Oura, WHOOP, Fitbit, Health Connect, Dexcom, OMRON and five more.

One clean model out, over REST, FHIR R4 and a read-only MCP server for AI agents.

Free, one Go binary plus Postgres, runs on a Raspberry Pi.

An open source, self-hosted alternative to hosted wearable APIs such as Terra.

docker pull ghcr.io/soma-intelligence/open-health-sync:0.2.0

Rather not run a server? The hosted beta is the same API with nothing to operate: a key in a minute, demo data on the first call.

**Get started for free →** · Sign in to the developer portal · ▶ Watch the 75-second tour

**Quickstart** · Hosted beta · Compare · Roadmap · Use cases · Why · Features · How it works · MCP · FAQ

If it saves you one more parser, a ⭐ star helps other developers find it. Questions and ideas: Discussions.

v0.2.0, early preview. APIs, configuration and schema may change in any 0.x release.

The quickstart, recorded on v0.1.0 with synthetic data: start the stack, load 90 days from six devices, then read one day of the merged view, one heart rate reading as REST, FHIR R4 and Open mHealth, and the MCP tools. Nothing is mocked; the tape renders it again.

Open Health Sync (ohs) is a self-hosted ingestion and normalization layer for health data. It pulls from providers, accepts uploads from phones, stores each payload verbatim, maps it to one canonical model with one set of units, and serves it over a REST API and a read-only MCP server. It is not a dashboard or a coaching app, and it is not a medical device or clinical software. Run it yourself, or let us run it for you in the hosted beta.

🔌 Eighteen sources, one model

Every provider maps into the same records, with one unit per quantity (UCUM, LOINC). Heart rate is bpm, whatever the source called it. All eighteen ship in v0.2.0.

⚡ Always current

Provider webhooks trigger a pull of just what changed. A backstop pull catches anything lost, and /freshness says how up to date each source is.

🧾 Nothing thrown away

Every payload is kept verbatim on disk or S3. Fix a mapper, re-process a user from the raw files. Re-sent uploads are de-duplicated.

🤖 Ready for AI agents

A read-only MCP server: a person points their own agent at their own data, with a token that can only read.

🏥 Standards out

An OpenAPI 3.1 REST API, plus FHIR R4 Observations checked against the official HL7 validator.

🔒 Yours, and erasable

Self-hosted, no call home. DELETE /v1/users/{id} is a hard delete of rows and raw files. Provider deletions are honored too.

Hosted beta

The same server, run for you. The hosted beta is Open Health Sync without a Postgres, a raw store or workers to operate: you sign in, create a key and read data on your first call.

A 75-second tour on test data: create an account and a project, connect a coding agent over MCP, load sample data, see it charted, call the API from the explorer, then Ctrl K. Play the video (MP4, 9 MB).

  • A key in a minute. Create a project and get its API key. During the beta: three projects per account, five keys per project.
  • Data on the first call. One click adds a synthetic user with 30 days from six sources, so your first request already returns sleep, heart rate and steps.
  • Every output. REST with the OpenAPI file, FHIR R4, Open mHealth, CSV and Parquet, signed webhooks and the read-only MCP server, as documented here.
  • A quickstart with your values. curl, TypeScript and Python filled in with your instance's address, plus the example apps and an AI agent over MCP.
  • Nothing to migrate. It runs this open source server. Code written for it moves to your own instance with a new base URL and key, and back.

The web example reading a user back with a project key: daily steps, resting heart rate and sleep stages, all synthetic data.

It is a free beta with open sign-up, on shared capacity. For now it takes test data only: demo and synthetic users, no real people's health data. Self-hosting stays first class, and the server stays free under the AGPL.

Get started for free → Sign up with an email and confirm it. Already have an account? Sign in to the developer portal.

Roadmap at a glance

✅ in v0.2.0 · 🛠 planned

Outputs and integrationsSources
✅REST API (OpenAPI 3.1), read-only MCP server✅Apple Health, Withings
✅FHIR R4 (Observations, checked with the HL7 validator)✅Oura, WHOOP, Polar, Strava
✅Open mHealth output (docs)✅Fitbit, Suunto, Ultrahuman, intervals.icu
✅CSV and Parquet exports (docs)✅Dexcom (CGM), OMRON
✅Outgoing webhooks (docs)✅Health Connect (Android app upload)
✅Background sync and /freshness✅Health app export import
✅Prometheus metrics and OpenTelemetry traces, off by default (docs)
✅A merged view across sources, with priorities per user and metric (docs)
✅Admin console at /console: run the pipeline and turn providers on from the browser (docs)
✅Consent per user, source and category, with a revocation webhook (docs)
✅Complete per-user archive for data portability (docs)
✅Append-only audit log of every authenticated request, read with GET /v1/audit (docs)
✅Backup and restore scripts, tested in CI (docs)
✅Kubernetes through a Helm chart (docs)
✅Deploy templates for Render, Fly.io and Railway (docs)
✅Hosted connect page: your app name and logo, one button per source (docs)
✅Erase one source's data for a user on disconnect, or for Apple Health and Health Connect
✅Twelve MCP prompts on training, sleep and recovery, and an agent skill (docs)
✅Server SDKs for TypeScript and Python, and a Postman collection (docs)
✅Rate limits on the provider-facing routes (docs)
✅Images and checksums.txt signed with cosign, keyless (verify)
✅Online rotation of the encryption key (docs)
✅Hourly and daily statistics per quantity type, in any time zone, over REST and MCP (docs)
✅First-run setup in the browser with a one-shot token, rate limited, so app stores need no terminal step (docs)
✅Xiaomi: Mi Fitness data import, and Health Connect attribution for Xiaomi, Amazfit and RingConn (guide)
✅Garmin: Health API connector (daily summaries, sleep, activities, HRV, body composition and more) for operators with their own Garmin app (guide)
✅Activity files: FIT, GPX and TCX, and the exports of Garmin, COROS, Wahoo, Suunto, Zwift and Strava, with ohs import files (guide)
✅ECG recordings over REST, FHIR (waveform as SampledData), MCP and exports (docs)
✅Self reports: a person's own ratings (feeling, mood, stress, energy, sleep quality and more) next to their synced data (docs)
✅Share links (SMART Health Links): an encrypted FHIR bundle for a doctor or coach, with expiry, passcode and a built-in viewer (docs)
✅Tenants and their API keys managed over HTTP with an instance admin key, for one instance serving several teams (docs)
✅Personal mode (ohs admin personal setup) and eight small Go recipes on your own data🛠Health records (EHR) through SMART on FHIR
✅Kotlin, Swift, React Native and Flutter SDKs and example apps🛠Coros

A line moves up only when its release is published. Details per source: docs/connectors.

What you can build

  • A health app backend. Your iOS app uploads HealthKit data, your users connect their Oura, WHOOP or Withings accounts (through a hosted connect page with your logo), and your app reads one API instead of eighteen.
  • An AI agent over your own data. Run it at home, connect your devices, and give your desktop agent a read-only view of your sleep, workouts and heart rate through MCP.
  • A study that collects from participants' wearables. Raw payloads kept for audit, hard deletes for withdrawals, FHIR R4 out for the analysis side.
  • A personal data archive. Load years of history from the Health app export in one command, and keep it on a machine you own.
  • Small tools on your own data. ohs admin personal setup gives you one instance for yourself, and the recipes add a sleep report, a heart trend, a weekly digest, a dashboard and more, each a single Go file with no dependency.

Why

Every health source speaks its own dialect. Apple Health sends one JSON line per HealthKit sample, heart rate in count/min, oxygen saturation as a fraction. Withings sends a daily activity object keyed by date, and intraday series keyed by Unix epoch. Each one has its own idea of a day, a device and a duplicate.

So teams write a script per provider: a parser, a table, a cron job. It works until the second provider, the first re-sent upload, the first mapper bug that needs a backfill, or the first person who asks you to delete everything you hold about them.

Open Health Sync is that layer, written once, with the boring parts done properly.

DIY script per providerOpen Health Sync
Add a sourcea new parser, new tables, a new cron joba connector that maps into the same model
Unitswhatever each API sendsone unit per quantity, the original value and unit kept in metadata
"Today"UTC, or the provider's clockthe local calendar day of the record's time zone
A re-sent uploadstored twiceanswered 200 with "duplicate": true, records de-duplicated by content hash
Raw payloadsparsed, then thrown awaykept verbatim on disk or S3, so a user can be re-processed after a fix
Is it done?guess, or check the logsGET /coverage: pending_jobs, parked_jobs, rejected_records
Is it current?a cron job and hopewebhook-triggered pulls, a backstop pull, GET /freshness per source
A record deleted at the providerstays in your tablesdeleted here too, when the provider reports it
New data arrivedpoll on a timera signed webhook (data.written) names the user, provider and days
A crash mid-runrerun the script and hopePostgres queue, retries with backoff, parked after 5 attempts
Provider OAuth tokensa plain column or a .envsealed with AES-256-GCM under OHS_ENCRYPTION_KEY
Delete a usergrep across tables and bucketsDELETE /v1/users/{id}, then poll GET /v1/deletions/{job_id}
Erase one provider's datathe same grep, filtered by handDELETE /v1/users/{id}/connections/{provider}?delete_data=true, or DELETE /v1/users/{id}/data/{provider} for a device
Let an AI agent read itbuild an integrationread-only MCP server, ohs mcp for desktop agents

Features

  • One canonical model. Quantity samples, category samples, sleep sessions and phases, workouts and routes, nutrition, daily summaries. Each record carries its provenance: provider, source, device and ingested_at.
  • One unit per quantity. Quantity names come with UCUM units and LOINC codes (data model). Heart rate is bpm whatever the source called it.
  • Raw payloads are kept. Every upload and pull lands in the raw store (local folder or any S3 compatible bucket) before anything else, and is never rewritten. After a mapper fix, any user can be re-processed from the raw files (how).
  • Local days, not UTC days. Daily summaries are per local calendar day, from the record's time zone. When a provider sends no daily summary, step, energy and distance totals are derived from samples.
  • Idempotent uploads. Each Apple Health part is stored under its day, batch and part number. Re-sending the same bytes answers 200 with "duplicate": true; other bytes under a taken key are refused, never written over. Records are de-duplicated by content hash.
  • Always current, without a sync button. Provider webhooks trigger a pull of just what changed, ahead of any routine work; a backstop pull every 6 hours (hourly when webhooks are silent) catches anything lost. GET /v1/users/{id}/freshness says how up to date each source is (background sync).
  • Know when it's processed. The coverage report counts records per provider and local day, plus jobs still pending, jobs parked after their retries, and lines that were not stored.
  • Real erasure. DELETE /v1/users/{id} removes the user's raw files and every row, then the user. Not a soft flag. When a provider reports that a record was deleted (Oura, WHOOP, Strava), it is deleted here too. Disconnecting a source with ?delete_data=true erases everything that source sent for the user (records, raw files, sync history), and DELETE /v1/users/{id}/data/{provider} does the same for Apple Health or Health Connect. OHS_RETENTION_DAYS drops raw files and normalized data past an age.
  • Consent per category. Each user grants or refuses each source per data category (sleep, heart, location, glucose and so on). Data not granted is dropped before it is stored, revoking a category deletes what was stored, and consent.granted and consent.revoked webhooks tell your server (guide).
  • One-call user archive. GET /v1/users/{id}/archive returns everything held about one person as a single zip, for access and portability requests (docs).
  • Multi-tenant. Tenant API keys (ohs_sk_) for your server, short-lived scoped user tokens (ohs_ut_, scopes upload and read) for one person's app or agent. Keys and tokens are stored as SHA-256 hashes.
  • Outgoing webhooks. Your server is told when a sync writes or deletes data, a user is erased or a connection needs the user again. Signed per Standard Webhooks, retried for about 28 hours, and thin: no health value ever leaves in a payload (guide).
  • Read-only MCP for AI agents. A person can point their own agent at their own data, with a token that can only read.
  • One binary plus Postgres. The queue lives in Postgres (FOR UPDATE SKIP LOCKED). No message broker, cache or worker fleet. The api, collect and etl roles run in one process or apart.
  • Runs small. A Raspberry Pi 5 with 4 GB runs the full stack, with a compose overlay tuned for it. Samples older than 30 days are compressed, about 7 times smaller on disk, and re-importing history into them stays fast.
  • Open standards. An OpenAPI 3.1 file describes the API, FHIR R4 Observations are checked against the official HL7 validator (scripts/fhir-validate.sh), MCP is an open protocol, and ohs doctor checks a configuration before you trust it.
  • No scores, no AI inside. It stops at clean, traceable data. Build insights on top.
Sources and outputs

Eighteen sources are in v0.2.0, five of them new: Garmin, Eight Sleep, Xiaomi, activity files and Bluetooth devices. Amazfit and RingConn arrive through Health Connect. Every other one is planned: on the roadmap, not built yet. Integration paths, vendor programs and tracking issues for each: docs/connectors.

SourceStatusHow the data arrives
Apple Health✅ Availableyour iOS app uploads HealthKit data with the Swift SDK or its own code (contract); ohs import apple-health loads a Health app export
Withings✅ AvailableOAuth, scheduled pulls, push notifications (guide)
Oura✅ AvailableOAuth, scheduled pulls, webhooks (guide)
Whoop✅ AvailableOAuth, scheduled pulls, webhooks (guide)
Strava✅ AvailableOAuth, scheduled pulls, webhook events (guide)
Polar✅ AvailableOAuth, scheduled pulls, webhooks (guide)
Garmin✅ Availablepartner program: OAuth with PKCE, scheduled pulls, Garmin's notifications; bring your own Garmin app, and Garmin has paused new applications (guide)
Fitbit✅ AvailableGoogle Health API: OAuth, scheduled pulls, signed webhooks (guide). Unverified Google projects serve at most 100 users
Suunto✅ Availablepartner program: OAuth, scheduled pulls, signed notifications (guide)
Ultrahuman✅ AvailablePartnership API: OAuth, daily pulls; ring data and M1 glucose (guide)
intervals.icu✅ AvailableOAuth, scheduled pulls, webhooks; activities from Garmin, Wahoo, Zwift, Coros and more, plus daily wellness (guide)
Health Connect✅ Availableyour Android app uploads Health Connect records (contract)
Dexcom✅ AvailableCGM glucose: OAuth, scheduled pulls (guide)
OMRON✅ Availableblood pressure and weight: partner API, OAuth, scheduled pulls (guide)
Xiaomi✅ Availableohs import mi-fitness loads the Mi Fitness data copy (full history); ongoing data through Health Connect or Apple Health (guide)
Bluetooth devices✅ Availableheart rate straps, blood pressure cuffs, scales, glucose meters, thermometers and pulse oximeters on the standard Bluetooth health profiles, through the ohs-bridge desktop bridge (macOS, Linux, Raspberry Pi, Windows) or the Swift and Kotlin SDKs; no vendor account (guide)
Activity files✅ Availableohs import files loads FIT, GPX and TCX files, and Garmin, COROS, Wahoo, Suunto, Zwift and Strava exports (guide)
FreeStyle LibreNo public APIthrough Health Connect or Apple Health (notes)
Coros🛠 Plannedcloud API, OAuth
Zepp / Amazfit, RingConn✅ Available, via Health Connecttheir apps write to Health Connect on Android; records keep their own source (zepp, ringconn) (details)
Samsung Health🛠 Planned, via Health Connectits app writes to Health Connect on Android
Google FitDeprecated by Googleuse Health Connect
Eight Sleep✅ Availableno public API: the sign-in the Eight Sleep app uses, scheduled pulls of sleep stages, heart rate, HRV, breathing and bed temperature (guide)

Want one sooner? Connectors are self-contained: read writing a connector and pick a good first issue.

OutputStatus
REST JSON, described by api/openapi.yaml✅ Available
MCP server, read only (docs/mcp.md)✅ Available
FHIR R4, checked with the official HL7 validator (docs/fhir.md)✅ Available (Observations, Patient, Device, Provenance, Patient $everything)
RR intervals and HRV (RMSSD, SDNN, pNN50, mean RR and heart rate) per heartbeat series, over REST and MCP (docs/hrv.md)✅ Available
Menstrual cycles derived from recorded flow, over REST and MCP; no predictions (docs/menstrual-cycles.md)✅ Available
Time tags (illness, injury, travel, jet lag, vaccination, medication, alcohol) that give context to the data, over REST, webhooks and MCP (docs/time-tags.md)✅ Available
Read routes for ECG recordings, which are stored today🛠 Planned
FHIR R4 bulk export ($export)🛠 Planned
Open mHealth (docs/open-mhealth.md)✅ Available
CSV and Parquet exports (docs/exports.md)✅ Available
Outgoing webhooks, signed per Standard Webhooks (docs/webhooks.md)✅ Available (data written or deleted, user erased, connection needs reauthorization)
Client SDKs: Kotlin, Swift (with HealthKit sync), React Native, Flutter, and example apps for iOS, Android and React Native (docs/sdks)✅ Available (install from this repository; not on package registries)
Health records (EHR): lab results, conditions, medications and allergies read through SMART on FHIR, served as FHIR R4🛠 Planned

Quickstart

You need Docker with Compose v2.24 or later, plus curl, jq and openssl. Compose pulls the published image ghcr.io/soma-intelligence/open-health-sync (amd64 and arm64).

git clone https://github.com/soma-intelligence/open-health-sync.git && cd open-health-sync
cp .env.example .env
sed -i.bak "s|^OHS_ENCRYPTION_KEY=.*|OHS_ENCRYPTION_KEY=$(openssl rand -base64 32)|" .env
docker compose --env-file .env -f deploy/docker-compose.yml up -d --wait
docker compose --env-file .env -f deploy/docker-compose.yml run --rm ohs doctor
docker compose --env-file .env -f deploy/docker-compose.yml exec ohs /ohs admin tenant create --name dev

ohs doctor prints one ok or fail line per check (configuration, database, raw store, public URL, connectors); run it again whenever you change .env. The last command prints a tenant API key once. Keep it:

export OHS=http://localhost:8080
export KEY=ohs_sk_...    # the key printed above

The API listens on 127.0.0.1 only, so these steps run on the same machine. A phone app, provider webhooks or another computer need it exposed: put a reverse proxy with TLS in front, or set OHS_HTTP_BIND=0.0.0.0 in .env for plain http on your network.

No device or provider keys yet? ohs seed --demo fills the instance with 90 days of synthetic data from six overlapping sources and prints a demo API key and calls to try (docs/demo.md):

docker compose --env-file .env -f deploy/docker-compose.yml exec ohs /ohs seed --demo

Port 8080 taken? Set OHS_HTTP_PORT and a matching OHS_PUBLIC_URL in .env (see self-hosting).

Create an end user and a token the user's app would hold:

USER_ID=$(curl -s -X POST $OHS/v1/users -H "Authorization: Bearer $KEY" \
  -H 'Content-Type: application/json' -d '{"external_ref":"demo"}' | jq -r .id)
TOKEN=$(curl -s -X POST $OHS/v1/users/$USER_ID/tokens -H "Authorization: Bearer $KEY" \
  -H 'Content-Type: application/json' -d '{"scopes":["upload"]}' | jq -r .token)

Upload one Apple Health part (one heart rate reading, gzip compressed):

DAY=$(date -u +%Y-%m-%d); NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
printf '{"quantityType":"HKQuantityTypeIdentifierHeartRate","startDate":"%s","endDate":"%s","value":72,"unit":"count/min","sourceName":"Demo Watch","metadata":{"HKTimeZone":"UTC"}}\n' "$NOW" "$NOW" \
  | gzip | curl -s -X POST $OHS/v1/ingest/apple-health \
    -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/gzip" \
    -H "X-OHS-Type: HKQuantityTypeIdentifierHeartRate" -H "X-OHS-Day: $DAY" \
    -H "X-OHS-Batch: demo1" -H "X-OHS-Part: 0" --data-binary @-

Wait until processing is done (pending_jobs is 0), then read the normalized sample back:

curl -s "$OHS/v1/users/$USER_ID/coverage" -H "Authorization: Bearer $KEY"
curl -s "$OHS/v1/users/$USER_ID/samples?type=heart_rate" -H "Authorization: Bearer $KEY"

Install the ohs binary on your machine. The import below and the MCP setup run ohs outside the container. Take the archive for your system from the releases page (ohs_<version>_<os>_<arch>.tar.gz, os is linux or darwin, arch is amd64 or arm64); it unpacks to a folder of the same name:

tar xzf ohs_*_linux_amd64.tar.gz
sudo install ohs_*_linux_amd64/ohs /usr/local/bin/ohs
ohs version

On macOS, clear the quarantine flag first: xattr -d com.apple.quarantine ohs_*_darwin_*/ohs.

Load your existing history. On the iPhone, Health app, your picture, Export All Health Data gives export.zip. Load it with the user's token (--dry-run first counts what it would send):

OHS_TOKEN=$TOKEN ohs import apple-health export.zip --url $OHS

Records, workouts with their routes and ECGs go through the same upload API, in constant memory. Running it again uploads nothing twice. Details: importing an export.

Data lives in the pgdata and raw volumes; docker compose ... down keeps them.

How it works

flowchart LR
  AH["Apple Health, Health Connect<br/>app upload, export import"] --> C
  CL["Withings, Oura, WHOOP, Fitbit,<br/>Polar, Strava, Suunto, Ultrahuman,<br/>intervals.icu, Dexcom, OMRON<br/>OAuth pull + webhooks"] --> C
  C["collect / ingest"] --> R[("raw store<br/>disk or S3")]
  R --> E["ETL<br/>normalize, dedup,<br/>units, local-day summaries"]
  E --> P[("Postgres<br/>+ TimescaleDB")]
  P --> API["REST API<br/>(OpenAPI)"]
  P --> MCP["MCP server<br/>(read only)"]
  P --> FHIR["FHIR R4<br/>Observations"]
  1. Collect. Your iOS app posts HealthKit data to POST /v1/ingest/apple-health as gzip NDJSON parts, with a user token that has the upload scope and the headers X-OHS-Type, X-OHS-Day, X-OHS-Batch and X-OHS-Part. The cloud providers (Withings, Oura, WHOOP, Fitbit and the others) are pulled when they send a webhook (only what changed), with a backstop pull on a schedule.
  2. Keep the raw payload. The part is written to the raw store first, then a job is queued in Postgres. The answer names the raw key and whether the part was a duplicate.
  3. Normalize. The etl role maps each line to the canonical model, converts units, drops duplicates, attributes source and device, and rolls up daily summaries per local day.
  4. Serve. Reads are GET /v1/users/{id}/... with the tenant key, paged with a cursor. The MCP server reads the same data, read only.

Details: architecture, API guide.

A provider is turned on, then a user connects

In the admin console you paste the keys of your Oura app and Oura is on, with no restart. Your app then creates a connection link and sends its user to it. The user picks Oura, then Withings, and allows access at each. ohs exchanges the code, stores the grant encrypted, pulls the history in the background and normalizes it. Back in the console, the pipeline shows the data moving through and the user's page shows each source active and fresh.

Recorded on this repository with synthetic data. The console, the connect page, the code exchange, the encrypted grant, the background pulls and the normalization are the real ones; the provider side (consent screen and API) is played by the test servers of the connector tests. How it was made: docs/demo.

See it run

These are real captures, not mockups: ohs 0.0.1 built from this repository, run natively against TimescaleDB (on port 8088, since 8080 was taken on that machine), with the end to end Apple Health fixture in connectors/applehealth/testdata/e2e uploaded as six parts. The fixture's values are synthetic.

A sample comes back in canonical units, with what the provider sent kept in metadata (count/min became bpm):

Daily summaries are derived per local day, and the coverage report says when processing is done:

Connect an AI agent (MCP)

Mint a user token with only the read scope (it may live up to 30 days):

curl -s -X POST $OHS/v1/users/$USER_ID/tokens -H "Authorization: Bearer $KEY" \
  -H 'Content-Type: application/json' -d '{"scopes":["read"],"ttl_seconds":2592000}'

Then add ohs mcp (stdio) to your desktop agent's mcpServers (the binary from the install step):

{
  "mcpServers": {
    "open-health-sync": {
      "command": "/usr/local/bin/ohs",
      "args": ["mcp"],
      "env": { "OHS_MCP_URL": "http://localhost:8080", "OHS_MCP_KEY": "ohs_ut_..." }
    }
  }
}

ohs mcp holds no data and needs no database: it forwards each message to the /mcp endpoint of an instance. OHS_MCP_URL must be https unless the host is loopback. Agents that speak Streamable HTTP can call /mcp directly with the bearer token.

Since 0.2.0, the server also lists twelve prompts (prompts/list): ready-made questions about training, sleep and recovery that read the data first and show the numbers (prompts, prompt library). The same guidance ships as an agent skill in skills/open-health-sync.

Tools, arguments and paging: docs/mcp.md.

Data model

Every source maps into these records (see docs/data-model.md for fields and units):

  • QuantitySample: one scalar measurement, such as heart rate, steps or SpO2.
  • CategorySample: a categorical event, such as a stand hour or a menstrual flow entry. Menstrual cycles are derived from the flow entries at read time by /menstrual-cycles (docs/menstrual-cycles.md).
  • SleepSession and SleepPhase: a night with its duration breakdown, then each stage.
  • Workout and RoutePoint: a session with its metrics, then its GPS track.
  • NutritionLog: a food or drink entry with its nutrients.
  • ECGRecording and ECGVoltageSample: the recording's metadata, then the voltage trace. Stored, not served yet: the read route is planned.
  • HeartbeatSample: one beat-to-beat (RR) interval. Served with HRV derived per series by /rr-intervals and /hrv (docs/hrv.md).
  • DailySummary: totals per local day, from the provider or rolled up from samples.
  • Observation: a generic coded fact for the long tail. No connector writes it yet.

Deploy

  • Compose with the published image. deploy/docker-compose.yml, as in the quickstart. Pin the tag with OHS_VERSION (there is no latest while 0.x). See self-hosting.
  • Raspberry Pi 5. Add the overlay deploy/docker-compose.pi.yml (smaller Postgres buffers, memory limits). See Raspberry Pi 5.
  • S3 raw store. Add deploy/docker-compose.s3.yml, or point OHS_RAW_STORE at any S3 compatible bucket. See S3.
  • Backup and restore. scripts/backup.sh writes one archive with the database, the raw store and the schema version; scripts/restore.sh puts it back. CI destroys and restores a stack on every pull request. See backup and restore.
  • Release binaries. Linux and macOS, amd64 and arm64, with checksums.txt, on the releases page. Bring your own Postgres with TimescaleDB and run ohs doctor, then ohs up (migrate, then serve). Each archive unpacks to ohs_<version>_<os>_<arch>/ohs (install). The macOS binaries are not signed yet: clear the quarantine flag with xattr -d com.apple.quarantine. Since 0.2.0, the image and checksums.txt are signed with cosign (verifying releases).
  • Build from source. Go 1.26: make build gives bin/ohs. For compose, add deploy/docker-compose.build.yml, which needs docker buildx; without it, see build from source.

Open Health Sync vs Terra and other health data APIs

Looking for a Terra API alternative, or an open source health data aggregator? Here is how Open Health Sync compares with the hosted wearable APIs. Facts about other products come from their own sites and pricing pages, checked on 2026-10-17; each row links to a full comparison, with how to move.

Open sourceStarting priceFHIR R4MCP server for AI agentsWhere the data lives
Open Health SyncYes, AGPL-3.0Free, you pay for your serverYes, checked with the official HL7 validatorRead-only, stdio and HTTPYour server
TerraNo$399 a month billed annuallyA FHIR option, no version namedRead-onlyTerra's cloud, then your destination
JunctionNo$300 a month for up to 500 usersNot in its documentationNot in its documentationJunction's cloud, US or EU
ThryveNo499 EUR a month for up to 500 usersNot found on its siteNot found on its siteThryve's servers in Germany
Spike APINoFrom $450 a monthNot found in its documentationYesSpike's cloud
SahhaNoFrom $299 a month billed annuallyNot found on its siteYesSahha's cloud
ROOKNo$399 a month for up to 750 usersAn add-on, included from BusinessNot found on its siteROOK's cloud

Pick a hosted API when you need Garmin or a long tail of devices today, do not want to run a server, or need your vendor's SOC 2 and HIPAA attestations. Pick Open Health Sync when the data has to stay on your infrastructure, per-user fees do not fit, or you need standard output (FHIR R4, Open mHealth) and every raw payload kept. Every option, side by side: health data aggregators compared.

FAQ

Who sees my data?

Only whoever runs the instance. Self-hosted, there is no account to create with us, no analytics and no call home. The server talks to the providers you enable (the ones whose keys you set) and to your own database and raw store. Logs never contain tokens, keys or health values. On the hosted beta we run the instance, which is why it takes test data only for now.

Where does the data live?

In two places you control: Postgres with TimescaleDB (connections and jobs in the ohs_sync schema, normalized records in ohs_data) and the raw store, a local folder or an S3 compatible bucket. With compose, those are the pgdata and raw volumes. Back them up together, with OHS_ENCRYPTION_KEY.

Is it HIPAA or GDPR compliant?

No software is compliant on its own, and Open Health Sync is not certified, audited or covered by a business associate agreement. What it gives you: data stays on infrastructure you choose, DELETE /v1/users/{id} is a hard delete of raw files and rows (useful for erasure requests), erasing one source's data for a user is a single call, consent is kept per source and data category and enforced on every write, GET /v1/users/{id}/archive gives one person everything held about them in a zip, OHS_RETENTION_DAYS enforces a retention period, every query is scoped to a tenant and a user, keys and tokens are stored hashed, and provider OAuth tokens are encrypted with AES-256-GCM under a key that can be rotated online (key rotation). Health records themselves are not encrypted by ohs: use disk or database encryption, TLS in front of the API, and your own access controls. Whether a deployment meets HIPAA, GDPR or any other rule depends on how you run it.

What does it cost?

Nothing. The server is free and open source. You pay for wherever you run it, which can be a Raspberry Pi 5 with 4 GB. Each cloud provider (Withings, Oura, WHOOP, Fitbit and the others) needs your own developer application, registered with the vendor. The hosted beta is free while it is a beta.

Is there a hosted version?

Yes, in free beta with open sign-up: the same API, run for you on shared capacity, for test and demo data only for now. Sign up free; the hosted beta section says what it gives you.

How does it compare to Terra?

Terra is a hosted API: it advertises 500+ integrations, Garmin included, starts at $399 a month billed annually, and your users' data passes through its cloud. Open Health Sync is free and puts standards first: FHIR R4 and Open mHealth output checked against the official validators, every raw payload kept, and one Go binary that runs on a Raspberry Pi. See the comparison table, or side by side: Terra API alternative.

Which devices does it work with?

Anything that writes to Apple Health (Apple Watch, iPhone, and the apps and devices that sync into HealthKit), through an iOS app that uploads it: the Swift SDK reads HealthKit and uploads what changed, or your app implements the upload contract itself. Without an app, ohs import apple-health loads the export the Health app writes, history included. Withings scales, watches and sleep devices through the user's Withings account. Oura rings, WHOOP bands, Polar watches, Strava activities, Suunto watches, Fitbit devices (through the Google Health API), Ultrahuman rings, intervals.icu accounts, Dexcom CGMs and OMRON blood pressure monitors and scales work through the user's own account with each. Fitbit, Suunto, Ultrahuman and OMRON need keys their vendor gives to approved developers only. On Android, anything that writes to Health Connect (Samsung Health, CGM apps such as FreeStyle Libre) arrives through an app that uploads it (contract). Garmin watches work through the user's Garmin Connect account, with keys Garmin gives approved businesses only (guide). More sources are planned, and the connector request form is open.

Why AGPL?

So the layer stays open. Anyone can run, study and change it; if you run a modified Open Health Sync as a network service, you publish your changes under the same license. The client SDKs in sdks/ are AGPL-3.0-only too.

Can I use it commercially?

Yes. A company can run it, unmodified or modified, to power its own product. The AGPL only asks that users who interact with a modified server over the network can get its source. This is not legal advice: read LICENSE or ask your counsel.

Is it a medical device?

No. It moves and normalizes data. It does not diagnose, score or advise, and it is not clinical software.

Status and roadmap

v0.2.0 is the latest release. It adds an admin console that runs the pipeline, an admin API for tenants and their keys, provider keys saved per tenant, the Garmin, Eight Sleep and Xiaomi connectors, activity file import, Bluetooth health devices through ohs-bridge, ECG reads, self reports, share links, hourly and daily sample statistics, online rotation of the encryption key and signed releases. What changed: CHANGELOG.md. Install and upgrade notes and the full list of limitations: release notes.

Known limitations:

  • After a hard kill, a job that was in flight is picked up again only after its 10 minute visibility timeout.
  • One database and one raw store per instance; no multi-region replication.
  • FHIR R4 has no bulk export ($export) yet.
  • The client SDKs are 0.1.0 and not on package registries yet; install them from this repository. Reading Health Connect from the Kotlin SDK is planned.

The hosted beta is a free beta with open sign-up (shared capacity, test and demo data only): sign up free.

Next: more connectors, standard outputs (more FHIR R4 resources), then Health Connect reading in the Kotlin SDK, then health records (EHR) through SMART on FHIR. Requests are welcome as GitHub issues, including the connector request form.

Documentation

Contributing

Issues and pull requests are welcome. Three ways in, from five minutes to a weekend:

  • Own a wearable? Record your account in about five minutes with make record PROVIDER=oura (or any other source). The recorder anonymizes on your machine and CI replays the file on every change: pick a source in #60, and see recording a connector.
  • Pick a good first issue: docs, a recipe, a CLI command or a deploy template, each scoped to one PR.
  • Add a provider. Connectors are self-contained: start with writing a connector.

Read CONTRIBUTING.md and the code of conduct first. Every first-time contributor gets a review within 48 hours.

make setup        # git hooks
make build        # bin/ohs
make test         # unit tests
make integration  # needs Docker (testcontainers)
Contributors

Security

Report vulnerabilities privately, as described in SECURITY.md. Never paste health data, keys or tokens in a public issue.

License

The server is AGPL-3.0-only, see LICENSE. If you run a modified Open Health Sync as a service, you publish your changes. The client SDKs in sdks/ are under the same license.

الأسئلة الشائعة

ما هو Open Health Sync؟

Read-only access to self-hosted health data: samples, sleep, workouts, nutrition, daily summaries.

كيف أثبّت Open Health Sync؟

يعمل على جهازك. في Claude Code: claude mcp add open-health-sync -e 'OHS_MCP_URL=<OHS_MCP_URL>' -e 'OHS_MCP_KEY=<your ohs_mcp_key>' -- docker run -i --rm ghcr.io/soma-intelligence/open-health-sync:0.2.0 mcp. وفي العملاء الآخرين، الصق إعداد JSON من هذه الصفحة في إعدادات MCP الخاصة بهم.

هل يحتاج Open Health Sync إلى مفتاح API؟

نعم. يتطلب OHS_MCP_URL, OHS_MCP_KEY، وتضبطها في إعداداته.

هل Open Health Sync مفتوح المصدر؟

نعم. شيفرته متاحة على GitHub بترخيص AGPL-3.0.

من يصون Open Health Sync؟

ينشره soma-intelligence. يدرجه AI.tn لكنه لا يصونه ولا يوصي به.

من سجل MCP الرسمي. الإدراج ليس توصية: راجع شيفرة الخادم وصلاحياته قبل منحه الوصول إلى بياناتك.