Skip to content

Dailies

Dailies are once-per-day health and lifestyle values that sit alongside activity data: body mass, resting heart rate, HRV, sleep, and a few others. Each measure has its own endpoint. Each daily is keyed by date.

Available measures

Measure Identifier Unit Accepted range
Body mass body_mass kg 0 to 500
Body fat percentage body_fat_pct % 0 to 100
Resting heart rate resting_hr bpm 0 to 250
Heart-rate variability hrv ms 0 to 500
Sleep duration sleep_duration seconds 0 to 86400
Sleep altitude sleep_altitude meters 0 to 10000
Menstrual cycle day menstrual_cycle_day day 0 to 90

The {measure} path parameter on every endpoint takes one of these identifiers. Both ends of each range are accepted.

Upsert a daily

POST /api/v1/dailies/{measure} writes the value for a date. If a value already exists for that date and measure, SweatStack replaces it. There is no separate update endpoint.

from datetime import date

from sweatstack import Client

client = Client()

daily = client.dailies.set("body_mass", date=date(2026, 5, 6), value=72.4)

dailies.set returns the stored daily.

curl -X POST "https://app.sweatstack.no/api/v1/dailies/body_mass" \
    -H "Authorization: Bearer {your_access_token}" \
    -H "Content-Type: application/json" \
    -d '{"date": "2026-05-06", "value": 72.4}'

Body fields:

  • date (required): the day, YYYY-MM-DD. SweatStack accepts any date, in the past or in the future.
  • value (required): a number inside the measure's accepted range.

Requires the data:write scope.

The API returns the stored daily:

{
    "date": "2026-05-06",
    "value": 72.4,
    "status": "stored",
    "source": "manual"
}

A value written through the API has source: "manual". A value that arrived from a connected platform has that platform as its source, for example garmin_connect. Integrations lists which platform sends which measure.

A manual value has priority over a platform value for the same date and measure:

  • If a platform value exists, your upsert replaces it.
  • If a manual value exists and a platform sends a value for that date, SweatStack keeps the manual value.

List dailies

GET /api/v1/dailies/{measure} returns one record per day in a date range.

from datetime import date

from sweatstack import Client

client = Client()

hrv = client.dailies.list("hrv", start=date(2026, 4, 1), end=date(2026, 5, 1))
hrv_df = client.dailies.list("hrv", start=date(2026, 4, 1), end=date(2026, 5, 1), output="pandas")

dailies.list returns a list of dailies, or a DataFrame with a date column with output="pandas" (or "polars"). Its interpolate argument defaults to True, the opposite of the API's default below.

curl -X GET "https://app.sweatstack.no/api/v1/dailies/hrv?start=2026-04-01&end=2026-05-01" \
    -H "Authorization: Bearer {your_access_token}"

Query parameters:

  • start (required): first day of the range, YYYY-MM-DD. Included.
  • end (required): last day of the range, YYYY-MM-DD. Included.
  • interpolate (optional, default false): when true, SweatStack fills in missing days for the measures that support it. See Interpolation.

Requires the data:read scope.

The response is an array with one record per day in the range, in date order, including days with no value:

[
    {"date": "2026-04-01", "value": 48.2, "status": "stored", "source": "manual"},
    {"date": "2026-04-02", "value": 47.8, "status": "stored", "source": "garmin_connect"},
    {"date": "2026-04-03", "value": null, "status": "missing", "source": null}
]

status tells you where a value came from on this read:

status value source Meaning
stored The stored number The platform or manual A value was logged for this day.
estimated An interpolated number null No value was logged. SweatStack estimated one because interpolate=true and the measure supports it.
missing null null No value was logged, and no estimate was made.

Delete a daily

DELETE /api/v1/dailies/{measure}?date={date} deletes the value for one day. To delete more days, send one request per day. There is no endpoint that deletes all values of a measure.

from datetime import date

from sweatstack import Client

client = Client()

client.dailies.delete("body_mass", date=date(2026, 5, 6))

dailies.delete raises SweatStackNotFoundError when no value exists for that day.

curl -X DELETE "https://app.sweatstack.no/api/v1/dailies/body_mass?date=2026-05-06" \
    -H "Authorization: Bearer {your_access_token}"

Query parameters:

  • date (required): the day to delete, YYYY-MM-DD.

Requires the data:write scope.

The API returns 204 on success and 404 when no value exists for that day.

Track the menstrual cycle

The menstrual_cycle_day measure is the athlete's day in their menstrual cycle:

Value Meaning
1 The first day of a period.
2 to 90 A later day in the cycle.
0 The athlete isn't cycling, for example during a pregnancy.

The API returns 422 for any other value, including a value that isn't a whole number.

To record a period start, upsert 1 for that date. If an athlete starts tracking in the middle of a cycle, upsert the day they're on. For example, day 12 on 2026-05-12 means that the cycle started on 2026-05-01.

Get every day of the cycle

If you request the measure with interpolate=true, SweatStack fills in every day from the first cycle:

  • The days of a cycle count up from its first day.
  • At the athlete's cycle length, the days loop back to day 1. They keep looping until the next stored value. If there is no next stored value, they keep looping with no end.
  • A stored period start ends the current cycle and starts a new one.
  • If a period start is stored less than 15 days after a predicted one, SweatStack doesn't use the predicted one. The cycle before it continues until the stored period start, unless that cycle would be longer than 90 days.
  • If a stored value is higher than the cycle length, its cycle continues to that day before the days loop.
  • After a stored 0, every day is 0 until the next stored value.

Days before the first cycle are missing. Every day that SweatStack fills in has status: "estimated". To tell a recorded period start from a predicted one, check status: a recorded one is stored. Every value, stored or estimated, is between 0 and 90.

A new, changed or deleted value applies on the next request. It never changes the days before the stored value that precedes it.

How SweatStack calculates the cycle length

SweatStack calculates the cycle length from the stored period starts. One cycle is the number of days from one period start to the next. For the days after a stored value, SweatStack uses only the values stored on or before it:

  • If there are 3 or more cycles, SweatStack uses the median of the last 3.
  • If there are 1 or 2 cycles, SweatStack uses the shortest. A missed period start makes a cycle look longer, never shorter, so the shortest cycle is the safer estimate.
  • If there are no cycles, SweatStack uses 28 days.

SweatStack uses only cycles of 15 to 90 days that started in the 365 days before the most recent period start. A cycle that contains a stored 0 doesn't count. If two stored values imply period starts less than 15 days apart, SweatStack treats them as one cycle, and the later value wins.

Get a cycle length from the first day

The cycle length needs at least 2 period starts. If your app onboards an athlete, ask for one of these:

  • The athlete's last 2 period starts.
  • The athlete's last period start and their usual cycle length.

For the last 2 period starts:

  1. Upsert 1 for the earlier period start.
  2. Upsert 1 for the later period start.

For a period start and a cycle length:

  1. Upsert 1 for the period start.
  2. Calculate the previous period start: the period start minus the cycle length in days.
  3. Upsert 1 for the previous period start.

For example, a period start of 2026-05-01 and a cycle length of 30 days give a previous period start of 2026-04-01:

from datetime import date, timedelta

from sweatstack import Client

client = Client()

period_start = date(2026, 5, 1)
cycle_length = 30

client.dailies.set("menstrual_cycle_day", date=period_start, value=1)
client.dailies.set("menstrual_cycle_day", date=period_start - timedelta(days=cycle_length), value=1)
curl -X POST "https://app.sweatstack.no/api/v1/dailies/menstrual_cycle_day" \
    -H "Authorization: Bearer {your_access_token}" \
    -H "Content-Type: application/json" \
    -d '{"date": "2026-05-01", "value": 1}'

curl -X POST "https://app.sweatstack.no/api/v1/dailies/menstrual_cycle_day" \
    -H "Authorization: Bearer {your_access_token}" \
    -H "Content-Type: application/json" \
    -d '{"date": "2026-04-01", "value": 1}'

The cycle length must be between 15 and 90 days. If it isn't, SweatStack doesn't use that cycle and uses 28 days. After the athlete stores more period starts, the cycle length follows their stored cycles.

Pause tracking

If the athlete isn't cycling, for example during a pregnancy:

  1. Upsert 0 for the first day without a cycle.
  2. Upsert 1 for the first period start after the pause.

The days between are 0. The time across a pause never counts as a cycle.

The measure records the day in the cycle. Period length, cycle phases and symptoms aren't part of it.

Interpolation

Some measures change slowly enough that filling in missing days is meaningful. Others don't, and showing made-up values would be wrong.

Measure Strategy with interpolate=true
Body mass Linear interpolation between the nearest stored values.
Body fat percentage Linear interpolation between the nearest stored values.
Sleep altitude The last stored value is carried forward.
Menstrual cycle day Counted from the stored period starts and looped at the athlete's cycle length. See Track the menstrual cycle.
Resting heart rate, HRV, sleep duration None. A missing day stays missing with value: null.

With interpolate=false, every measure returns stored values only, and every other day is missing.