Wearables

Sync a connected wearable provider now

  • Sandbox: available
  • Staging: available
  • Production: unknown
POST
/v1/wearables/connections/{provider}/sync

Pulls the signed-in patient's latest data from a connected wearable provider now, instead of waiting for the daily sync, and writes it as Observations.

Auth: bearer token of a patient. Scope: the caller's project and Patient profile (from the token).

Behaviour

  • Oura, WHOOP, Strava, Withings, Polar, Ultrahuman and Suunto can be synced. Other providers answer 501 SYNC_NOT_SUPPORTED for now; they can still be connected.
  • The first sync reads the last 30 days. Each later sync reads from 2 days before syncedThrough, because providers revise recent records.
  • Each reading becomes one Observation about the patient, created once. A reading already held is updated in place, so a value the provider revised within the re-read window replaces the earlier one, and created counts only new ones.
  • What is synced:
    • heart rate, resting heart rate, heart-rate variability (RMSSD), SpO2, respiratory rate;
    • daily steps;
    • sleep duration and its light, deep, REM and awake parts.
  • What each provider gives:
    • Oura: per sleep period, heart rate, resting heart rate, HRV, respiratory rate and the sleep stages; daily SpO2 and steps.
    • WHOOP: resting heart rate, HRV and SpO2 from each recovery; respiratory rate and the sleep stages; each day's average heart rate.
    • Strava records workouts only, so it gives each workout's average heart rate.
    • Withings gives heart-rate and SpO2 measurements, daily steps, and per night its sleep summary: the night's average heart rate, average respiratory rate and RMSSD, and the sleep durations.
    • Polar gives each night's sleep stages, and from its nightly recharge the night's average heart rate, heart-rate variability (RMSSD) and breathing rate, dated by the night. Its daily steps are dated by the watch's local day.
    • Ultrahuman: SpO2 (when given as one value), resting heart rate during sleep, daily steps and the sleep stages. Its per-sample heart rate and HRV are not synced. It is asked one day at a time, so a first sync makes 31 requests. It gives no respiratory rate.
    • Suunto: daily steps only, for now.
  • heart_rate is not the same kind of value everywhere: for Oura, Withings and Polar it is a sleep or nightly average (Withings adds its heart-rate measurements), for WHOOP the day's average, and for Strava the average of one workout.
  • Codes are LOINC, except heart-rate variability: LOINC has no RMSSD code, so it is https://api.ovok.com/fhir/CodeSystem/wearables|hrv-rmssd.
  • Every Observation carries the provider in meta.tag (https://api.ovok.com/fhir/CodeSystem/wearables-source) and in device.display.
  • Wearable Observations are not forwarded to Signals.
  • A daily sync runs for every connection at 08:00 UTC; this route only brings it forward, at most once every 15 minutes.
  • Withings also tells ovok when a patient has new data, and a sync of that connection follows about 30 seconds later. ovok subscribes each patient when they connect; nothing needs setting up in the Withings portal beyond the redirect URL.
  • Withings Sleep Analyzer mats on ovok's own Withings device integration are separate: their bed presence and sleep series do not come through this connection, and this connection does not change them.
  • A sync never reads further back than 30 days: when the provider or the connection is out of reach for longer, the oldest days are lost.
  • Polar lists sleep and nightly recharge for its last 28 days only, so a first Polar sync holds 28 days of those and 30 of steps.
  • Polar's access is long-lived and cannot be refreshed: once Polar stops accepting it, the sync answers 409 RECONNECT_REQUIRED.

Errors

  • 404 CONNECTION_NOT_FOUND: the patient has not connected this provider, or disconnected it during the sync.
  • 404 PROVIDER_NOT_AVAILABLE: the project removed or switched off the provider.
  • 409 SYNC_IN_PROGRESS: a sync of this connection is already running.
  • 409 CONNECTION_CHANGED: the patient reconnected the provider while it was syncing; nothing was written over the new connection. Sync again.
  • 409 RECONNECT_REQUIRED: the provider no longer accepts the connection, for example after the patient removed the app's access. Connect it again with POST /v1/wearables/connections/<provider>/authorize. GET /v1/wearables/connections shows reconnectRequired: true meanwhile.
  • 429 SYNC_TOO_SOON: the last sync was under 15 minutes ago. Retry after the seconds in Retry-After; the provider was not asked.
  • 500 SYNC_FAILED: the sync failed on our side; nothing moved, so try again later.
  • 502 PROVIDER_UNAVAILABLE: the provider failed, could not be reached, or answered something unreadable; nothing moved, so try again later.
  • 503 PROVIDER_RATE_LIMITED: the provider is limiting requests. Retry after the seconds in Retry-After.

Example

curl -X POST 'https://api.sandbox.ovok.com/v1/wearables/connections/oura/sync' \  -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'

Authorization

bearer
headerAuthorizationBearer <JWT>

Path Parameters

provider*string
Value in"oura""whoop""strava""withings""polar""suunto""garmin""ultrahuman"

Response Body

What this sync wrote, and up to when the data is now synced.

application/json
  1. response
provider*string
Value in"oura""whoop""strava""withings""polar""suunto""garmin""ultrahuman"
created*integer

How many Observations this sync wrote; readings already held are skipped.

syncedThrough*string

Up to when the data is now synced.

Example Request
curl -X POST 'https://api.sandbox.ovok.com/v1/wearables/connections/oura/sync'
Example Response
{  "provider": "oura",  "created": 0,  "syncedThrough": "string"}

Start connecting a wearable provider POST

Starts connecting the signed-in patient's account at a wearable provider. Open the returned `authorizationUrl` in a browser or an auth session (`ASWebAuthenticationSession`, Custom Tabs) whose callback scheme is your patient app's. **Auth:** bearer token of a patient. **Scope:** the caller's project and Patient profile (from the token). ## Flow 1. The patient consents on the provider's page. 2. The provider sends the browser to `GET /v1/wearables/oauth/<provider>/callback`. 3. ovok exchanges the code for tokens, stores them encrypted, and redirects the browser (`302`) to the project's patient app at `/wearables/<provider>?result=connected|denied|failed`. ## Behaviour - The URL is valid for 10 minutes and for one use. - Polar shares data only for users registered to the app, so ovok registers the patient with Polar right after the code exchange. A patient who has not accepted Polar's mandatory consents is refused, and the callback ends with `result=failed`. - Connecting a provider the patient is already connected to replaces the stored tokens. - A provider the project has not set up or has switched off answers `404 PROVIDER_NOT_AVAILABLE`. - A project with no usable patient app URL (`PATIENT_APP_URL`) answers `409 APP_URL_NOT_CONFIGURED`: the flow would have nowhere to return to. - When the authorization cannot be stored, the answer is `503 STATE_STORE_UNAVAILABLE` with `Retry-After`. ## Example ```bash curl -X POST 'https://api.sandbox.ovok.com/v1/wearables/connections/oura/authorize' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' ```

Disconnect a wearable provider DELETE

Disconnects the signed-in patient from a wearable provider: the connection is deleted and its tokens are no longer readable through the API. **Auth:** bearer token of a patient. **Scope:** the caller's project and Patient profile (from the token). ## Behaviour - Before the connection is deleted, ovok undoes what it set up at the provider where the provider allows it: Withings stops notifying ovok about the patient, and Polar deregisters the patient, which also revokes the app's access. Other providers keep the app's access until the patient removes it in the provider's own settings. - A failure at the provider never stops the disconnect; the connection is deleted all the same. - The resource history keeps the earlier versions, with the tokens still encrypted. - A provider the patient has not connected answers `404 CONNECTION_NOT_FOUND`. ## Example ```bash curl -X DELETE 'https://api.sandbox.ovok.com/v1/wearables/connections/oura' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' ```