What the timing biomarkers measure
Three timestamps that locate the main sleep of the night.
| Biomarker | What it is |
|---|---|
sleep_start_time | When the main sleep session began |
sleep_mid_time | The midpoint between start and end |
sleep_end_time | When the main sleep session ended |
All three are reported daily as ISO 8601 datetimes with the profile’s local offset, dated by the morning the sleep ended, and none requires a wearable.
| Field | sleep_start_time, sleep_mid_time, sleep_end_time |
|---|---|
| Unit | Datetime with local offset |
| Reported | Daily, one value per night |
| Window | 6pm to 6pm, profile local time. Dated by the morning the window ends. |
| Wearable required | No |
| API | GET /api/v1/profile/biomarker/{externalId}?categories=sleep plus one types parameter per field and a date range |
| Webhook | BiomarkerCreatedIntegrationEvent. Entries update in place, upsert on id. |
Data sources
The same sources and ranking as sleep duration: stage data first, asleep periods second, in-bed only third, the phone estimate last. What differs is which record supplies the timestamp:
| Source type | Start time comes from | End time comes from |
|---|---|---|
| Stages (watch, ring, Health Connect writers) | Earliest light, deep, or REM record in the main session | Latest such record |
| Asleep only | Earliest asleep record | Latest asleep record |
| In-bed only, including the phone estimate | Start of the in-bed period | End of the in-bed period |
How Sahha calculates sleep timing
- Group the night’s records into sessions. Provider-asserted sessions are used as is. Otherwise records are grouped by time, with a gap of more than 60 minutes starting a new session.
- Take the longest session as the main sleep.
sleep_start_timeis the earliest asleep-stage record in that session, falling back to the earliest asleep record, then the in-bed start.sleep_end_timeis the latest, by the same precedence.sleep_mid_timeis the midpoint of the two stored values. It is written only once both exist.
There are no timing biomarkers for a night with no sleep record. Absence means no data.
Interpreting the values
Mid time is the most stable of the three. Bedtime varies with evenings out; wake time varies with alarms. The midpoint absorbs both and is the usual proxy for chronotype in research and in Sahha’s bed_schedule archetype.
In-bed-only sources shift start earlier. A phone estimate or an iPhone Sleep schedule reports the in-bed window, so its start precedes true sleep onset by however long the user took to fall asleep.
Times carry the profile’s local offset. Travel across time zones shows up as a shift in the offset, not a shift in the clock time.
Related scores and insights
Score factor: circadian alignment. The Sleep, Wellbeing, and Mental Wellbeing scores carry a factor named circadian_alignment, unit minute, goal 0. Its value is the average of two distances: how far the night’s start sits from the user’s usual bedtime, and how far its end sits from the user’s usual wake time, both in minutes, with the usual times taken from the profile’s own recent nights. Credit decays with distance:
| Average distance from the user’s usual times (minutes) | Factor score |
|---|---|
| 0 | 1.00 |
| 30 | 0.90 |
| 60 | 0.81 |
| 120 | 0.66 |
| 180 | 0.54 |
A consistent 1am to 9am sleeper scores well, because the reference is their own pattern. What the factor penalises is a night that departs from it.
Trend. A circadian_alignment trend is available on the factor score.
Comparison. None.
Archetypes. bed_schedule (very_early_sleeper to very_late_sleeper) and sleep_pattern (consistent_early_riser, inconsistent_late_sleeper, and others) are derived from timing.
Use cases
Notification timing. Send the morning message 30 to 60 minutes after the user’s median sleep_end_time over the last 7 days, and never before it. This is the single highest-value use of these fields and needs no wearable.
GET /api/v1/profile/biomarker/{externalId}?categories=sleep&types=sleep_end_time&startDateTime=2026-08-26&endDateTime=2026-09-02A wind-down prompt. Fire it 45 minutes before the median sleep_start_time, suppressed on nights where the user is still generating activity past that point.
Jet lag support. When the local offset changes, show the user’s mid time in the new zone against their home-zone median, and how many nights it has taken to converge.
Limitations
- The circadian factor follows the user’s recent pattern. A deliberate schedule change scores as misalignment until the reference catches up over the following nights.
- Naps are invisible here. Split sleepers get the longer half only.
- Sessions split on 60-minute gaps. A 90-minute wake at 3am produces two sessions, and the longer one becomes the main sleep. Start or end can jump by hours on such nights.
- In-bed-only starts are early. Latency is baked into the start for the phone estimate and iPhone Sleep schedule sources.
- Absence is not zero.
Related guides
- Sleep duration for how the night’s source and main session are chosen
- Sleep regularity for how much these times move from night to night
- Sleep latency for the gap between in-bed and asleep
- Sleep Score explained for the seven sleep factors
- Data dictionary in the developer docs