Data model¶
This page describes the entities in SweatStack, the words we use for them, and how they relate. Every other page in the Data section builds on it.
SweatStack organizes athletic data in four entities:
- Activities: recorded sessions and their metadata.
- Activity data: the timeseries of one activity.
- Longitudinal data: the timeseries of many activities.
- Traces: manually recorded point-in-time values (lactate, RPE, notes).
- Tests: structured performance evaluations (lactate tests, VO2max tests, FTP tests) with a defined results schema.
- Dailies: once-per-day health and lifestyle values (body mass, resting heart rate, HRV, sleep).
Each entity serves a different analytical purpose. All of them share the same metrics and the same sport vocabulary, described below.
Metrics¶
A metric is a quantity SweatStack records, like power or heart rate. Metrics are the same across every entity: a power column in activity data and a power field on a trace mean the same thing in the same unit.
| Metric | Unit |
|---|---|
power |
Watt |
heart_rate |
Beats per minute |
speed |
Meters per second |
cadence |
Revolutions or steps per minute |
altitude |
Meters |
temperature |
Degrees Celsius |
core_temperature |
Degrees Celsius |
smo2 |
Percentage |
distance |
Meters |
latitude |
Degrees |
longitude |
Degrees |
The Python SDK exposes the metrics as the sweatstack.Metric enum:
from sweatstack import Metric
print(Metric.power)
print(Metric.heart_rate)
Use it, or the plain strings, to choose the metrics in a longitudinal query:
from datetime import date
from sweatstack import Client, Metric
client = Client()
longitudinal_data = client.activities.longitudinal.data(
sport="cycling",
start=date(2024, 1, 1),
metrics=[Metric.power, Metric.heart_rate],
)
Sports¶
Sports are classified with the Open Sport Taxonomy (OST), an open, hierarchical sport vocabulary. A sport is a dotted code that goes from broad to specific (cycling, cycling.road), optionally followed by one or more + modifiers that describe the circumstances (cycling.road+stationary, running+race).
Everywhere the API accepts or returns a sport, it uses these OST strings. The same strings work in the Python SDK, and sweatstack.Sport is OST's Sport class re-exported for convenience.
cycling = client.activities.list(sport=["cycling"])
road = client.activities.list(sport=["cycling.road"])
mixed = client.activities.list(sport=["cycling.road", "running.trail"])
For the full taxonomy and the Sport API (modifiers, hierarchy, matching), see open-sport-taxonomy.sweatstack.no. If you need a sport that is not listed, email [email protected].
SweatStack normalizes the source device's sport label into this taxonomy. If the label is not recognized, the activity arrives as sport: "generic".
Activities¶
An activity has two parts: its metadata, and its timeseries.
Activity metadata¶
Activity metadata describes one recorded session: an id, a sport, start and end times, summary statistics, and so on. It is a small object that gives an overview of a session without the timeseries.
sweatstack.activities.list(), sweatstack.activities.retrieve() and sweatstack.activities.latest() all return activity objects with the metadata only.
activity = client.activities.latest()
print(f"Activity: {activity.id}")
print(f"Sport: {activity.sport}")
Summary statistics¶
Every activity carries a summary object with per-metric aggregates computed from the timeseries. The paths you'll use most:
summary.power.mean,summary.power.max(watts)summary.heart_rate.mean,summary.heart_rate.min,summary.heart_rate.max(bpm)summary.speed.mean,summary.speed.max(m/s)summary.distance.sum(meters)summary.altitude.gain,summary.altitude.loss,summary.altitude.min,summary.altitude.max(meters)summary.cadence.mean,summary.cadence.max(rpm or steps/min, excluding stopped periods)
A metric the activity did not record is absent from summary or null. The same fields appear on each entry of the laps array. For an example payload, see activities, response shape. For every available subfield, see the ActivitySummarySummary schema in the API reference.
Laps¶
When the source recording contained lap markers, GET /api/v1/activities/{id}, /activities/latest and /tests/{id} include a laps array. Each lap has start, end, duration, and the same summary.* fields as the activity itself. The list endpoint GET /api/v1/activities/ always returns laps: null. To read laps, fetch the activity. SweatStack computes laps from the stored timeseries when you ask for them, so they always agree with the timeseries you can download. See the Lap schema for the full field list.
Activity data¶
Activity data is the timeseries of one activity, usually sampled at 1 Hz. It holds every metric recorded during the activity, and gives you the detail for an in-depth analysis of a single session.
data = client.activities.data("activity-id-123")
print(data.head())
Longitudinal data¶
Longitudinal data is the timeseries of many activities in one DataFrame.
from datetime import date
from sweatstack import Client
client = Client()
road_cycling_data = client.activities.longitudinal.data(
sport="cycling.road",
start=date(2024, 1, 1),
end=date(2024, 3, 31),
metrics=["power", "heart_rate"],
)
Traces¶
A trace is a point-in-time value that an athlete or an app records by hand: a blood lactate reading, an RPE, a note. A trace can exist on its own. SweatStack links it to an activity automatically when its timestamp falls inside one.
Traces are the place for values that a device does not measure continuously during an activity.
from datetime import date, datetime, timedelta, timezone
new_trace = client.traces.create(
timestamp=datetime.now(timezone.utc),
lactate=2.5,
)
traces = client.traces.list(
start=date.today() - timedelta(days=30),
end=date.today(),
output="pandas",
)
The timestamp must carry an offset. See Writing timestamps.