Wearables

Start connecting a wearable provider

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

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

curl -X POST 'https://api.sandbox.ovok.com/v1/wearables/connections/oura/authorize' \  -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

The provider's consent page, valid for 10 minutes and one use.

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

Open this in a browser or auth session; it ends at the project's patient app.

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

List the patient's wearable connections GET

Returns the wearable providers the signed-in patient can connect, and when they connected each one. **Auth:** bearer token of a patient. **Scope:** the caller's project and Patient profile (from the token). ## Behaviour - `connections` holds one entry per provider the project has enabled, plus any provider the patient is still connected to after the project switched it off. - `connection` is `null` when the patient has not connected the provider. Otherwise: - `connectedAt`: when the patient last connected it; - `syncedThrough`: up to when its data has been synced, `null` before the first sync; - `reconnectRequired`: `true` when the provider no longer accepts the connection. Connect it again to clear it. - A session that is not a patient's answers `403 PATIENT_PROFILE_REQUIRED`. ## Example ```bash curl -X GET 'https://api.sandbox.ovok.com/v1/wearables/connections' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' ```

Sync a connected wearable provider now POST

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 ```bash curl -X POST 'https://api.sandbox.ovok.com/v1/wearables/connections/oura/sync' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' ```