Keely for developers
Days
A morning and the day it began, as the Keely app backs it up and as the connector answers an assistant: the latest, one date, or a range of up to 92 days.
Updated
On this page
GET /v1/days/latest
The latest morning and the day it began: the newest date the account holds, shaped exactly as GET /v1/days/{date} answers it.
GET /v1/days/{date}
One morning and the day it began, exactly as the Keely app backs it up and as the connector answers an assistant, so an assistant and your app read the same numbers for the same day.
What the parts mean:
recovery.statusisscored,calibrating(withnights_recordedandnights_needed) orunavailable(with anote).stateis Primed (67–100), Balanced (34–66) or Restore (0–33), andfocusis the app’s own line for the day.likely_rangeis where the score likely sits, from its inputs’ standard errors. Each contributor is one input against the person’s normal: itsz, itsweight_pct, and whether it washelping,normalorholding back. How Recovery is scored.night. Last night’s vitals: HRV (RMSSD) and what it was measured over, resting, lowest, early-night and average heart rate, breathing rate, the skin temperature’s deviation from normal, and the average stress while asleep.sleep. When the night ran andwhosetimes they are (the band’s, Keely’s or the person’s), the stages, wake-ups, the night’s need and its parts, performance against need, and the 14-day Sleep Regularity Index (consistency).start_rulesays what read awake at the night’s start. How sleep is read.napsthe day’s other sleeps, andasleep_with_naps_minutesthe night plus them.load. The day’s Load, its target range (provisionalwhile the person’s 28-day average is still settling), a status line, Balance with its label, and minutes in each zone. How Load is counted.stress. Averages for the day, the night and the still windows, minutes at each level (and inside workouts), and the moments: at least 10 minutes above 50 by day or 30 at night. How Stress is scored.workouts. The day’s workouts, each asGET /v1/workoutslists them.missinglists what the date has no record of, in words.
{
"date": "2026-09-27",
"recovery": {
"status": "scored",
"score_pct": 79,
"state": "Primed",
"focus": "Ready day: train as planned, hard sessions included.",
"likely_range": [74, 83],
"contributors": [
{ "input": "HRV", "value": 70.2, "unit": "ms", "normal": 61.5, "normal_range": [55.6, 68],
"z": 1.4, "weight_pct": 45, "impact": "helping", "measured_by": "Keely" },
{ "input": "Resting heart rate", "value": 49, "unit": "bpm", "normal": 51, "normal_range": [49.5, 52.5],
"z": 1.33, "weight_pct": 25, "impact": "helping", "measured_by": "Keely" },
{ "input": "Sleep", "value": 91, "unit": "% of need", "normal": 100, "normal_range": [85, 115],
"z": -0.62, "weight_pct": 20, "impact": "holding back", "measured_by": "Polar Loop" },
{ "input": "Breathing rate", "value": 14.1, "unit": "breaths/min", "normal": 14, "normal_range": [13.6, 14.4],
"z": -0.25, "weight_pct": 5, "impact": "normal", "measured_by": "Keely" },
{ "input": "Skin temperature", "value": 0.1, "unit": "°C from normal", "normal": 0, "normal_range": [-0.25, 0.25],
"z": -0.4, "weight_pct": 5, "impact": "normal", "measured_by": "Polar Loop" }
]
},
"night": {
"hrv_ms": 70.2, "hrv_measured_over": "early night",
"resting_hr_bpm": 49, "lowest_hr_bpm": 46, "early_night_hr_bpm": 51.4, "average_hr_bpm": 55,
"breathing_rate_per_min": 14.1, "skin_temp_deviation_c": 0.1, "average_stress_asleep": 12
},
"sleep": {
"start": "2026-09-26T22:20:00Z", "end": "2026-09-27T06:20:00Z", "whose": "the band",
"start_rule": { "learned": false, "taught_by_nights": 0, "corrected_nights": 2, "awake_at_stress": 40,
"heart_over_median_bpm": null, "wrist_cooler_c": null, "tells_evenings": false },
"in_bed_minutes": 480, "asleep_minutes": 440, "awake_minutes": 40,
"light_minutes": 240, "deep_minutes": 95, "rem_minutes": 105, "wake_ups": 3,
"need_minutes": 485, "need_parts_minutes": { "base": 470, "recent_debt": 15, "hard_day": 0 },
"performance_pct": 91, "consistency": 84
},
"naps": [
{ "start": "2026-09-27T14:00:00Z", "end": "2026-09-27T14:45:00Z", "asleep_minutes": 45, "whose": "Keely, from your answers" }
],
"asleep_with_naps_minutes": 485,
"load": {
"value": 98.4, "target": { "low": 132, "high": 179, "provisional": false }, "status": "34 to the target",
"balance": 1.05, "balance_label": "Steady",
"zone_minutes": { "z1": 20, "z2": 35, "z3": 10, "z4": 5, "z5": 0 }
},
"steps": 8123,
"stress": {
"day_average": 24, "day_level": "Calm", "night_average": 12, "still_average": 21,
"minutes_by_level": { "calm": 400, "mild": 120, "elevated": 30, "high": 5 },
"minutes_by_level_while_training": { "calm": 0, "mild": 10, "elevated": 20, "high": 5 },
"moments": [
{ "start": "2026-09-27T10:20:00Z", "end": "2026-09-27T10:35:00Z", "peak": 64, "average": 57, "asleep": false }
]
},
"workouts": [ "… the day’s workouts, each as GET /v1/workouts lists them" ],
"as_of": "2026-09-27T15:20:00Z",
"methodology": "https://meetkeely.com/methodology"
}
GET /v1/days
days holds one object per date, in order, each shaped as above without as_of and methodology; a date without data has null parts and a missing list, never a gap.
{
"from": "2026-09-26",
"to": "2026-09-27",
"days": [
{ "date": "2026-09-26", "recovery": { "status": "calibrating", "score_pct": null, "nights_recorded": 2, "nights_needed": 3, "note": "…" }, "…": "…" },
{ "date": "2026-09-27", "recovery": { "status": "scored", "score_pct": 79, "…": "…" }, "…": "…" }
],
"as_of": "2026-09-27T15:20:00Z",
"methodology": "https://meetkeely.com/methodology"
}