Browse the docs

API reference

Data model

Latido normalizes every provider into six record families. A developer never converts per provider: the field names and units below are the same whether the data came from Garmin, Oura, Apple Health or the demo provider. The tables at the end of this page are generated from the API's own schemas.

Conventions

  • Units are fixed and SI-like: meters, seconds, kilograms, kilocalories, beats per minute, watts, m/s, milliseconds for HRV (RMSSD), mg/dL for glucose, °C, percentages on 0-100. The unit is in the field name (distanceMeters, durationSeconds, weightKg…).
  • Absent means unknown, never zero. A field the provider did not give is left out: "no data" and "zero" are different answers.
  • Time. Instants are ISO 8601 (startTime, measuredAt…), stored in UTC; utcOffsetMinutes keeps the member's wall clock. Days (date) are the member's local calendar day, YYYY-MM-DD — for sleep, the day they woke up.
  • Ids. id is Latido's (rec_…); providerRecordId is the provider's own. With the data type, providerRecordId is unique per connection: a provider correcting a record updates it in place (and raises *.updated where that event exists).
  • Raw payload. Every record synced from a provider keeps the provider's payload verbatim (mobile uploads have none). Add include=raw ({ include: "raw" } in the SDK) to a list or GET /v1/records/{id} and each record gains raw. Webhooks and MCP never carry it.
  • Sources. For aggregators (Apple Health, Health Connect) source.appName names the app that wrote the record and source.deviceName / deviceManufacturer the device.

Reading records

GET /v1/members/{memberId}/{workouts | sleep-sessions | daily-summaries | body-measurements | heart-rate | glucose}
    ?from=2026-10-01&to=2026-10-27T00:00:00Z&provider=garmin&include=raw&limit=50&cursor=…
GET /v1/records/{recordId}?include=raw
  • Newest first. from (inclusive) and to (exclusive) are ISO dates or instants, compared with the record's start.
  • Pages of limit (1-200, default 50); meta.nextCursor is absent on the last page.
  • SDK: latido.members.workouts.list(memberId, query) for one page, latido.members.workouts.iterate(memberId, query) for all of them; sleepSessions, dailySummaries, bodyMeasurements, heartRate, glucose likewise. Each family's type is exported (WorkoutRecord, SleepSessionRecord, …), and the SDK checks that every record of a page is of the family asked for.
json
{
  "data": [
    {
      "id": "rec_V1StGXR8_Z5jdHi6B",
      "type": "workout",
      "memberId": "mem_…",
      "connectionId": "conn_…",
      "provider": "garmin",
      "providerRecordId": "17234567890",
      "sport": "running",
      "providerSport": "RUNNING",
      "startTime": "2026-10-25T06:00:00.000Z",
      "endTime": "2026-10-25T07:00:00.000Z",
      "utcOffsetMinutes": 120,
      "durationSeconds": 3600,
      "distanceMeters": 10000,
      "averageHeartRateBpm": 148,
      "hasFile": false,
      "createdAt": "2026-10-25T07:05:12.000Z",
      "updatedAt": "2026-10-25T07:05:12.000Z"
    }
  ],
  "meta": { "nextCursor": "eyJ0Ijoi…" }
}

Members and connections

  • Member (mem_…): your end user, known by your externalUserId (unique per project and mode) with optional metadata (≤ 2 KB JSON). GET /v1/members/{id} adds connections and recordCounts (records stored per data type, every type present).
  • Connection (conn_…): one member × one provider — status (pending, active, expired, revoked, error), scopes, providerUserId / providerUserLabel, lastSyncedAt, backfill (status: pending | running | completed | failed, from, completedAt) and lastError (code, message, at). Tokens never leave the backend.

Planned workouts

POST /v1/members/{memberId}/planned-workouts with { provider, workout } sends a structured workout to a device: name (≤ 100), sport (SportType), scheduledDate? (YYYY-MM-DD, the member's day), description?, and 1-100 steps. A step has a kind (warmup, active, recovery, cooldown, rest, repeat), an optional durationSeconds (≤ 86,400) or distanceMeters, optional target ranges { min, max } — targetHeartRateBpm, targetPowerWatts, targetPaceSecondsPerKm — and a note. A repeat step runs its steps (≤ 50) repeat times (1-100). The answer is queued; GET /v1/planned-workouts/{id} and the planned_workout.pushed / .failed webhooks report the outcome.

Field reference

Common fields

Every record, whatever its family, carries these.

Field Type Always present Unit Notes
id string yes Latido's id of the record (rec_…)
memberId string yes The member the record belongs to
connectionId string yes
provider enum (16 values) yes
providerRecordId string yes The provider's own id; with the data type, it is unique per connection
source { deviceName, deviceManufacturer, appName }
createdAt string (ISO 8601 instant) yes When Latido first stored it
updatedAt string (ISO 8601 instant) yes When Latido last changed it

Workout (workout)

GET /v1/members/{memberId}/workouts · SDK latido.members.workouts

Field Type Always present Unit Notes
type "workout" yes
sport enum (44 values) yes
providerSport string yes The provider's own activity type, verbatim
name string
startTime string (ISO 8601 instant) yes
endTime string (ISO 8601 instant) yes
utcOffsetMinutes integer min
durationSeconds number (≥ 0) yes s Elapsed, start to end
movingSeconds number (≥ 0) s
distanceMeters number (≥ 0) m
elevationGainMeters number m
elevationLossMeters number m
activeKilocalories number (≥ 0) kcal
averageHeartRateBpm number bpm
maxHeartRateBpm number bpm
averageSpeedMps number m/s
maxSpeedMps number m/s
averagePowerWatts number W
normalizedPowerWatts number W
averageCadence number Steps per minute for running, revolutions per minute for cycling
steps integer (≥ 0)
trainingLoad number The provider's own load / strain score
perceivedExertion number (≥ 1, ≤ 10)
heartRateZones { zone, minBpm, maxBpm, seconds }[]
laps { startTime, durationSeconds, distanceMeters, averageHeartRateBpm, averagePowerWatts, averageSpeedMps }[]
polyline string Encoded polyline of the route, when there is one
startLatitude number
startLongitude number
isManual boolean
hasFile boolean yes Whether the original file (FIT/GPX/TCX) can be downloaded through Latido

heartRateZones[] items:

Field Type Always present Unit Notes
zone integer (≥ 1, ≤ 5) yes
minBpm number bpm
maxBpm number bpm
seconds number (≥ 0) yes s

laps[] items:

Field Type Always present Unit Notes
startTime string (ISO 8601 instant) yes
durationSeconds number (≥ 0) yes s
distanceMeters number (≥ 0) m
averageHeartRateBpm number bpm
averagePowerWatts number W
averageSpeedMps number m/s

Daily summary (daily_summary)

GET /v1/members/{memberId}/daily-summaries · SDK latido.members.dailySummaries

Field Type Always present Unit Notes
type "daily_summary" yes
date string (YYYY-MM-DD) yes The member's local calendar day
steps integer (≥ 0)
distanceMeters number (≥ 0) m
floorsClimbed number (≥ 0)
activeKilocalories number (≥ 0) kcal
totalKilocalories number (≥ 0) kcal
activeSeconds number (≥ 0) s
sedentarySeconds number (≥ 0) s
restingHeartRateBpm number bpm
averageHeartRateBpm number bpm
maxHeartRateBpm number bpm
hrvRmssdMs number ms (RMSSD) Daily HRV (RMSSD), usually measured overnight
averageStressScore number (≥ 0, ≤ 100)
bodyBatteryOrReadiness number (≥ 0, ≤ 100) Garmin Body Battery, Oura readiness, WHOOP recovery… on 0-100
readinessLabel string The provider's own name for that score
spo2Percent number (≥ 0, ≤ 100) %
respiratoryRateBrpm number breaths/min
skinTemperatureDeviationCelsius number °C
vo2Max number

Sleep session (sleep_session)

GET /v1/members/{memberId}/sleep-sessions · SDK latido.members.sleepSessions

Field Type Always present Unit Notes
type "sleep_session" yes
date string (YYYY-MM-DD) yes The day the member woke up
startTime string (ISO 8601 instant) yes
endTime string (ISO 8601 instant) yes
utcOffsetMinutes integer min
isNap boolean yes
durationSeconds number (≥ 0) yes s In bed, start to end
asleepSeconds number (≥ 0) s
awakeSeconds number (≥ 0) s
lightSeconds number (≥ 0) s
deepSeconds number (≥ 0) s
remSeconds number (≥ 0) s
latencySeconds number (≥ 0) s
efficiencyPercent number (≥ 0, ≤ 100) %
score number (≥ 0, ≤ 100) The provider's sleep score, 0-100
averageHeartRateBpm number bpm
lowestHeartRateBpm number bpm
averageHrvRmssdMs number ms (RMSSD)
respiratoryRateBrpm number breaths/min
stages { stage, startTime, endTime }[]

stages[] items:

Field Type Always present Unit Notes
stage awake | light | deep | rem | unknown yes
startTime string (ISO 8601 instant) yes
endTime string (ISO 8601 instant) yes

Body measurement (body_measurement)

GET /v1/members/{memberId}/body-measurements · SDK latido.members.bodyMeasurements

Field Type Always present Unit Notes
type "body_measurement" yes
measuredAt string (ISO 8601 instant) yes
weightKg number (> 0) kg
bodyFatPercent number (≥ 0, ≤ 100) %
muscleMassKg number (> 0) kg
boneMassKg number (> 0) kg
waterPercent number (≥ 0, ≤ 100) %
bmi number (> 0)
heightMeters number (> 0) m
systolicMmHg number (> 0) mmHg
diastolicMmHg number (> 0) mmHg
restingHeartRateBpm number bpm
bodyTemperatureCelsius number °C

Heart-rate samples (heart_rate_sample)

GET /v1/members/{memberId}/heart-rate · SDK latido.members.heartRate

Field Type Always present Unit Notes
type "heart_rate_sample" yes
startTime string (ISO 8601 instant) yes
endTime string (ISO 8601 instant) yes
samples { t, bpm }[] yes One series per record, so a day of samples is one row, not 86,400

samples[] items:

Field Type Always present Unit Notes
t integer yes s Seconds since startTime
bpm number yes bpm

Glucose sample (glucose_sample)

GET /v1/members/{memberId}/glucose · SDK latido.members.glucose

Field Type Always present Unit Notes
type "glucose_sample" yes
measuredAt string (ISO 8601 instant) yes
mgPerDl number (> 0) yes mg/dL
trend rising_quickly | rising | flat | falling | falling_quickly | unknown

Providers and what they carry

From the provider catalog (packages/common/lib/health/provider-catalog.ts). AI is allowsAiProcessing: when no, MCP tools never return the provider's records.

Slug Name Connect Delivery Data types Planned workouts Max backfill AI
garmin Garmin oauth webhook workout, daily_summary, sleep_session, body_measurement, heart_rate_sample yes 90 days yes
strava Strava oauth webhook workout, heart_rate_sample 365 days no
polar Polar oauth webhook workout, daily_summary, sleep_session, heart_rate_sample 30 days yes
suunto Suunto oauth webhook workout, daily_summary, sleep_session yes 365 days yes
coros COROS oauth webhook workout, daily_summary, sleep_session yes 30 days yes
oura Oura oauth webhook workout, daily_summary, sleep_session, heart_rate_sample 365 days no
whoop WHOOP oauth webhook workout, daily_summary, sleep_session, body_measurement 365 days yes
withings Withings oauth webhook workout, daily_summary, sleep_session, body_measurement, heart_rate_sample 365 days yes
fitbit Fitbit oauth webhook workout, daily_summary, sleep_session, body_measurement, heart_rate_sample 365 days yes
google_health Google Health oauth polling workout, daily_summary, sleep_session, body_measurement, heart_rate_sample 365 days yes
wahoo Wahoo oauth webhook workout yes 365 days yes
dexcom Dexcom oauth polling glucose_sample 90 days yes
huawei Huawei Health oauth polling workout, daily_summary, sleep_session, body_measurement, heart_rate_sample 30 days yes
apple_health Apple Health mobile_sdk device_upload workout, daily_summary, sleep_session, body_measurement, heart_rate_sample, glucose_sample 365 days yes
health_connect Health Connect mobile_sdk device_upload workout, daily_summary, sleep_session, body_measurement, heart_rate_sample, glucose_sample 30 days yes
latido_demo Latido Demo oauth webhook workout, daily_summary, sleep_session, body_measurement, heart_rate_sample yes 90 days yes

Sports (SportType)

Every provider's activity types map onto these; anything unmapped is other, with the original kept in providerSport.

running, trail_running, treadmill_running, track_running, walking, hiking, cycling, mountain_biking, gravel_cycling, indoor_cycling, virtual_cycling, e_biking, pool_swimming, open_water_swimming, triathlon, rowing, indoor_rowing, kayaking, stand_up_paddling, surfing, cross_country_skiing, alpine_skiing, snowboarding, strength_training, hiit, crossfit, yoga, pilates, elliptical, stair_climbing, climbing, bouldering, tennis, padel, soccer, basketball, golf, martial_arts, boxing, dance, meditation, breathwork, mobility, other