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 adateand avalueas for a single upsert. Each date at most once.existing(required): what to do with a day that already holds another value. Onlykeepis 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, defaultfalse): whentrue, 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 is0until 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:
- Upsert
1for the earlier period start. - Upsert
1for the later period start.
For a period start and a cycle length:
- Upsert
1for the period start. - Calculate the previous period start: the period start minus the cycle length in days.
- Upsert
1for 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:
- Upsert
0for the first day without a cycle. - Upsert
1for 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.