Browse the docs

API reference

Mobile ingest: Apple Health and Health Connect

Apple Health (HealthKit) and Health Connect have no cloud API: the data lives on the phone. Your app reads it with the user's permission and uploads it to Latido, which stores it next to every other provider's data — same model, same read endpoints, same webhooks, same MCP tools.

Status. Latido's native iOS / Android SDKs are not available yet. What exists today is the upload endpoint below, which your own app calls (and the native SDKs will call when they ship).

The endpoint

POST /v1/members/{memberId}/ingest — latido.ingest.upload(memberId, input)

  • Live API keys only. A test key gets 403 FORBIDDEN ("Test keys can only use the demo provider").
  • Call it from your backend, not from the phone: the request carries a project API key. The phone sends what it read to your server, which forwards it.
  • The member must exist (create it with POST /v1/members or a connect session).
json
{
  "provider": "apple_health",
  "records": [
    {
      "type": "workout",
      "providerRecordId": "9C1D3E8A-0F4B-4C5D-8E6F-1A2B3C4D5E6F",
      "sport": "running",
      "providerSport": "HKWorkoutActivityTypeRunning",
      "startTime": "2026-10-20T07:00:00+02:00",
      "endTime": "2026-10-20T07:48:00+02:00",
      "utcOffsetMinutes": 120,
      "durationSeconds": 2880,
      "distanceMeters": 8200,
      "activeKilocalories": 610,
      "averageHeartRateBpm": 151,
      "hasFile": false,
      "source": { "appName": "com.apple.health", "deviceName": "Apple Watch" }
    }
  ]
}
Field Notes
provider apple_health or health_connect: the on-device store the records were read from.
records 1-1000 records. Each is a normalized record of the data model without the fields Latido assigns: no id, memberId, connectionId, provider, createdAt, updatedAt. The type field selects the family; both stores accept all six (workout, daily_summary, sleep_session, body_measurement, heart_rate_sample, glucose_sample).
providerRecordId The store's own id — the HealthKit object UUID, the Health Connect metadata.id (for a day's summary or a heart-rate batch, a stable id you choose, such as 2026-10-20). With the data type it is unique per connection, so uploading the same one again updates the record instead of duplicating it. That also makes the call safe to retry.

The whole body is validated against the contract: one invalid record refuses the upload with 422 UNPROCESSABLE_CONTENT (the message says which path). A record whose family the store does not carry is 400 INVALID_ARGUMENT_ERROR.

Response:

json
{ "data": { "connectionId": "conn_…", "created": 1, "updated": 0, "unchanged": 0 } }

What happens on upload

  • The first upload of a member for a store creates its connection — no OAuth, no tokens; the device stands for the account (providerUserId: "device:<memberId>"). It counts towards the plan's connected-members limit (403 PLAN_LIMIT_REACHED when full) and sends connection.created.
  • Each upload marks the connection synced and runs the same writer as provider syncs: new records raise *.created, changed ones *.updated, identical ones count as unchanged.
  • The data then reads like any provider's: GET /v1/members/{id}/workouts?provider=apple_health.

Mapping the stores (guidance)

The upload carries normalized records, so the mapping happens in your app. Latido's units are fixed: meters, seconds, kg, kcal, bpm, ms (HRV RMSSD), mg/dL, °C, percentages on 0-100. Instants are ISO 8601; send them with the phone's offset and set utcOffsetMinutes, so the member's wall clock is kept.

Data HealthKit Health Connect
Workout HKWorkout (+ HKStatistics for heart rate) ExerciseSessionRecord + aggregates over the session (distance, calories, heart rate)
Sleep HKCategoryValueSleepAnalysis samples grouped into one night per record (stages) SleepSessionRecord with stages
Daily summary HKStatisticsCollectionQuery per local day aggregateGroupByPeriod per local day
Body BodyMass, BodyFatPercentage, Height, BodyMassIndex, BodyTemperature, RestingHeartRate, blood-pressure correlation WeightRecord, HeightRecord, BodyFatRecord, BoneMassRecord, BloodPressureRecord, BodyTemperatureRecord, RestingHeartRateRecord
Heart rate quantity samples batched into one series (samples: [{ t, bpm }], t = seconds since startTime) HeartRateRecord.samples
Glucose BloodGlucose (mmol/L × 18.0156 → mg/dL) BloodGlucoseRecord.level.inMilligramsPerDeciliter

Unit traps: HealthKit percentages are 0-1 fractions (× 100); energy in kJ ÷ 4.184; Apple's HRV is SDNN — do not put it in hrvRmssdMs (Health Connect's HeartRateVariabilityRmssdRecord is RMSSD and fits). Keep the writing app in source.appName (bundle id / package name) and the device in source.deviceName / deviceManufacturer, so a Garmin workout synced through Apple Health can be told from an Apple Watch one.

Not supported yet

  • Raw uploads. The endpoint accepts only the normalized { provider, records } body described on this page. A raw format (HealthKit / Health Connect objects converted on the server) is planned for the native SDKs; it is not something the API accepts today.
  • Deletions. A record deleted on the phone cannot be uploaded as a deletion yet. To remove a member's data, delete the member (DELETE /v1/members/{memberId}).