Keely for developers
Conventions.
What every answer has in common, so your app can read any of them the same way.
Updated
On this page
JSON and CORS
JSON, UTF-8, Cache-Control: no-store. CORS is open (Access-Control-Allow-Origin: *), so a browser app can call the API directly.
Dates and times
Dates (2026-09-27) are the person’s local dates, as the Keely app stores them. Times are UTC, ISO 8601 with a Z. Nothing is converted on the server, so a night’s start and end are the instants it ran, and its date is the local morning it ended on. Date ranges (from, to) are inclusive.
Units
Units are in field names: hrv_ms, resting_hr_bpm, asleep_minutes, skin_temp_deviation_c.
Missing is null
A date without a night has recovery: null, with the reason in missing. A Recovery still calibrating says so in its status and note. An activity the person added for a time the band has no heart rate for keeps its sport and time, with Load and heart rate null, never zero.
Not allowed is named
When parts of an answer need scopes you don’t have, those keys are left out and listed in not_allowed; when the whole answer does, the API answers 403 insufficient_scope. An empty table never reads as “nothing happened”. Permissions has the detail.
as_of and methodology
Every answer carries as_of, the band’s last sync to the person’s iPhone (null before the first), and methodology, the page that explains every number: meetkeely.com/methodology. GET /v1/explain/{topic} gives the same in words and as a formula, per number.
Sample data
Answers from Keely’s review account also carry sample_data: true and a sample_note. Real accounts never do: Keely shows nothing it didn’t measure.
Versions
The version is in the path, /v1, and GET /v1 answers it as version; every webhook body carries it as api_version. Within a version, fields and event types may be added (a subscription to all types hears a new one without a change), so read what you need and let the rest pass. What changes, and when, is in the changelog.