September 3, 2026 · 5 min read

Sleep timing

How the main sleep session is chosen, how bed and wake times are derived, the circadian alignment factor they feed, and what to build.

What the timing biomarkers measure

Three timestamps that locate the main sleep of the night.

BiomarkerWhat it is
sleep_start_timeWhen the main sleep session began
sleep_mid_timeThe midpoint between start and end
sleep_end_timeWhen 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.

Fieldsleep_start_time, sleep_mid_time, sleep_end_time
UnitDatetime with local offset
ReportedDaily, one value per night
Window6pm to 6pm, profile local time. Dated by the morning the window ends.
Wearable requiredNo
APIGET /api/v1/profile/biomarker/{externalId}?categories=sleep plus one types parameter per field and a date range
WebhookBiomarkerCreatedIntegrationEvent. 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 typeStart time comes fromEnd time comes from
Stages (watch, ring, Health Connect writers)Earliest light, deep, or REM record in the main sessionLatest such record
Asleep onlyEarliest asleep recordLatest asleep record
In-bed only, including the phone estimateStart of the in-bed periodEnd of the in-bed period

How Sahha calculates sleep timing

  1. 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.
  2. Take the longest session as the main sleep.
  3. sleep_start_time is the earliest asleep-stage record in that session, falling back to the earliest asleep record, then the in-bed start. sleep_end_time is the latest, by the same precedence.
  4. sleep_mid_time is 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.

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
01.00
300.90
600.81
1200.66
1800.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-02

A 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