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 wellness:write scope, or reproductive_health:write for menstrual_cycle_day.

The API returns the stored daily:

{
    "date": "2026-05-06",
    "value": 72.4,
    "status": "stored",
    "source": "application",
    "application_id": "01KKK…"
}

If the day already holds the value you send, nothing is written, and the daily keeps the source it had.

Who wrote a daily

source says who wrote a stored daily. SweatStack sets it from the access token, never from the request:

source Written by
manual The athlete: in SweatStack, with the CLI or the Python library on their own account, or with an API key. A coach writing for the athlete counts as the athlete.
application An app. application_id is the app; it is null when the app was deleted.
garmin_connect, intervals_icu, ... A connected platform. Integrations lists which platform sends which measure.

New values may be added to source, so don't treat an unknown one as an error.

A value from a connected platform never replaces a value the athlete or an app wrote. Your upsert replaces any value.

Write many dailies (beta)

Beta

Writing many dailies in one request is in beta and is subject to breaking changes. If you plan to use it in production, email info@sweatstack.no before you integrate, so we can tell you about changes in advance and help you migrate.

POST /api/v1/dailies/{measure}/batch writes up to 1,000 values of one measure in one request. Use it to import history: it never replaces a stored value, so it's safe to send the same values again.

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

Body fields:

  • values (required): 1 to 1,000 values, each with a date and a value as for a single upsert. Each date at most once.
  • existing (required): what to do with a day that already holds another value. Only keep is accepted: the stored value stays, whoever wrote it, including a day older than your app can read.

Requires the wellness:write scope, or reproductive_health:write for menstrual_cycle_day.

The API returns one result per value, in the order you sent them:

{
    "results": [
        {"date": "2026-05-05", "outcome": "written"},
        {"date": "2026-05-06", "outcome": "kept"}
    ]
}
outcome Meaning
written The day was empty. It holds your value now.
unchanged The day already held this value, so nothing was written. Sending a request again gives unchanged for every value the first one wrote.
kept The day holds another value, which stays.

A request is written in full or not at all. If a value is outside the measure's range or a date appears twice, the API returns 422, lists every such value by its index, and writes nothing:

{
    "detail": [
        {"loc": ["body", "values", 17, "value"], "msg": "Body mass must be a value in the range [0, 500] kg, got 725.0", "type": "value_error"}
    ]
}

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 wellness:read scope, or reproductive_health:read for menstrual_cycle_day.

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", "application_id": null},
    {"date": "2026-04-02", "value": 47.8, "status": "stored", "source": "garmin_connect", "application_id": null},
    {"date": "2026-04-03", "value": null, "status": "missing", "source": null, "application_id": null}
]

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

status value source Meaning
stored The stored number Who wrote it 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 wellness:write scope, or reproductive_health:write for menstrual_cycle_day.

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.