Skip to content

Data & Units

Michal Zaniewicz edited this page Jul 22, 2026 · 3 revisions

Reference for the backend fields and - importantly - their units, which are non-obvious and have bitten this project before. These are confirmed against a live account. If you build templates or debug values, trust this page.

Backends

Two Sports Tracker hosts (the same backend the Suunto app uses):

Host Carries Notes
api.sports-tracker.com/apiserver/v1/ Workouts, lifetime stats ASKO envelope {error, metadata, payload}. Auth header STTAuthorization: <sessionkey>.
247.sports-tracker.com/ 24/7 wellness streams (sleep, recovery, activity) gzipped NDJSON, often without a Content-Encoding header (detected by the 1f 8b magic and gunzipped). Paths: v1/{sleep|recovery|activity|sleepstages}/export?since=<ms>.

See Architecture for how these feed the two coordinators.


Units cheat-sheet (the gotchas)

Domain Field Stored as Convert
Sleep hrAvg, hrMin Hz x60 → bpm
quality, maxSpo2 0..1 x100 → %
avgHrv ms -
duration, deepSleepDuration, ... seconds /60 → min, /3600 → h
Recovery balance 0..1 x100 → %
stressState enum int not a fraction
Activity (24/7) hr Hz x60 → bpm
energyConsumption joules /4186.8 → kcal. Active energy (above rest), so idle intervals are exactly 0.
stepCount steps sum today
Workouts avgSpeed, maxSpeed m/s x3.6 → km/h
avgPace already min/km -
hrdata.workoutAvgHR/MaxHR/userMaxHR already bpm no Hz here!
timeInZone0..5 centiseconds /6000 → min
totalDistance, totalAscent, totalDescent m
minAltitude, maxAltitude decimetres /10 → m. Unlike ascent/descent, which really are metres. 0/0 means no barometer reading (indoor).
extensions[].SummaryExtension.pte 1..5 Peak Training Effect, Suunto's own session-impact rating.
extensions[].SummaryExtension.ascentTime/descentTime s /60 → min
totalTime s
energyConsumption kcal (per workout) -
recoveryTime s /3600 → h
Lifetime stats totalDistanceSum m
totalTimeSum s
totalEnergyConsumptionSum kcal

⚠️ Three traps, all found the hard way:

  • HR is represented two ways: 24/7 and sleep HR are in Hz (x60), workout HR is already bpm.
  • Energy is joules in the 24/7 activity stream (/4186.8 → kcal) but kcal per workout. Reading the stream as calories inflated every figure by 4.19x (fixed in 1.0.14).
  • Altitude is decimetres (minAltitude/maxAltitude, /10) while ascent and descent are metres. Reading it as metres turned an alpine hike into "26788 m" (fixed in 1.0.14).

Zone times are centiseconds (/6000 → min, and they sum to roughly totalTime).


Sleep night aggregation

A single night is returned as multiple fragments - one per wake-up. The Suunto app shows their sum. The integration reproduces that:

  • Fragments are grouped by a noon-to-noon key: (local_timestamp - 12h).date(). This keeps a night that crosses midnight together and splits consecutive nights correctly.
  • Durations are summed; rates (HR, HRV, SpO₂) are duration-weighted across the fragments.
  • Wake-up time is derived as the end of the last fragment (the API has no explicit wake field).

Some fields (light/REM sleep, SpO₂, HRV) are absent on some watch models - the corresponding sensors then read unknown.


Sample cadences (probed live)

Stream Cadence
24/7 activity (HR / steps / energy) every ~10 min
recovery (balance / stress) every ~30 min
Workout HR (workouts/{key}/dataheartrates) ~25 s ({t, hr, d})
Workout FIT export (workout/exportFit/{key}) ~1 s (binary; not used - no FIT dependency)
sleepstages empty on some watches

These cadences are why the Long-term Statistics are bucketed hourly - that's the granularity HA supports for backdated ingestion, and it's plenty for these sources.


Workout list eventual consistency

The Sports Tracker workouts list is occasionally eventually-consistent: a whole workout can vanish from one response and reappear the next cycle, which would wobble every workout-derived sensor (counts, weekly volume, CTL/ATL/TSB, statistics). The integration keeps a per-key cache with a 24-hour grace window and re-adds a transiently-missing workout, so a single flaky fetch can't drop it. A genuinely deleted workout falls out after the grace window. (Trade-off: a real deletion/edit takes up to ~24 h to reflect.)


Lifetime stats per activity (allStats[])

The lifetime-stats payload carries a top-level total plus an allStats[] list with one entry per sport. Its keys differ from the top-level *Sum fields (confirmed live):

Field Meaning
_id the activityId (see Activity Types)
totalDistance metres
totalTime seconds
energyConsumption kcal
numberOfWorkouts count
maxDepth metres (diving)

This feeds the Lifetime by activity sensor: state = number of sports, per-sport totals in the activities attribute.

Workout stop / recovery fields

Each workout carries stopTime (epoch ms) and recoveryTime (seconds). The Recovered-at sensor is derived as stopTime + recoveryTime - the moment Suunto's recovery countdown finishes.

Clone this wiki locally