Skip to content

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.