API reference
The Arkaayu API is a small set of read-only JSON endpoints at https://api.arkaayu.health. Every request acts on behalf of one user — the person who granted your app its access token.
- GET
/api/me - GET
/api/sleep-data - GET
/api/steps - GET
/api/heart-rate - GET
/api/spo2 - GET
/api/blood-pressure - GET
/api/workouts - GET
/api/ecg - GET
/api/ecg/{id} - GET
/api/body-measurements
Basics
Authentication#
Every endpoint is a GET and needs an access token from the OAuth flow, sent in the Authorization header:
curl
curl "https://api.arkaayu.health/api/steps?start_date=2026-10-01&end_date=2026-10-07" \
-H "Authorization: Bearer $ACCESS_TOKEN"Node.js
const params = new URLSearchParams({ start_date: "2026-10-01", end_date: "2026-10-07" });
const res = await fetch(`https://api.arkaayu.health/api/steps?${params}`, {
headers: { Authorization: `Bearer ${accessToken}` },
});
if (res.status === 401) { /* refresh the token and retry once */ }
const body = await res.json();
for (const day of body.data) console.log(day.activity_date, day.steps);Python
import requests
res = requests.get(
"https://api.arkaayu.health/api/steps",
params={"start_date": "2026-10-01", "end_date": "2026-10-07"},
headers={"Authorization": f"Bearer {access_token}"},
timeout=10,
)
res.raise_for_status()
for day in res.json()["data"]:
print(day["activity_date"], day["steps"])Each endpoint needs a particular scope. Calling one your token doesn't cover returns 403 with insufficient_scope. Responses carry Cache-Control: no-store: don't cache them in shared caches or CDNs.
Date ranges
All list endpoints except /api/me accept the same query parameters:
| Parameter | Format | Description |
|---|---|---|
start_date | YYYY-MM-DD | First day to include. Defaults to the start of the 30-day window ending on end_date. |
end_date | YYYY-MM-DD | Last day to include. Defaults to today. |
- Both dates are inclusive. The range can be at most 400 days.
- If
start_dateis afterend_date, or a date isn't valid, you get400withinvalid_request. - A response holds at most 5,000 rows, newest first. For long ranges of dense data, request smaller windows.
Response envelope
List endpoints wrap rows in the same envelope:
{
"user_id": 123,
"data": [ ... ],
"count": 7,
"start_date": "2026-10-01",
"end_date": "2026-10-07"
}| Field | Type | Description |
|---|---|---|
user_id | integer | The Arkaayu user the token belongs to. Stable for your app — use it to match responses to your own user records. |
data | array | The rows, newest first. An empty array means no data in the range (for example, the watch hasn't synced yet). |
count | integer | Number of rows in data. |
start_date, end_date | string | The range that was actually used, after defaults were applied. |
Timestamps (*_time, *_at) are ISO 8601 strings without a time-zone suffix, for example "2026-10-07T22:41:00". Times are in the watch's local time as recorded; treat them as local timestamps and don't convert them as if they were UTC. Dates (activity_date) are plain YYYY-MM-DD calendar days.
Profile
Returns the connected user's basic profile and the scopes your token carries. Takes no parameters. Useful right after connecting, to confirm which account was linked.
curl https://api.arkaayu.health/api/me -H "Authorization: Bearer $ACCESS_TOKEN"{
"data": {
"id": 123,
"firstName": "Priya",
"lastName": "Sharma",
"email": "priya.sharma@example.com"
},
"scope": "read:profile read:sleep read:steps"
}Sleep
Sleep sessions recorded by the watch, with time in each stage and a sleep score. Accepts date ranges.
| Field | Type | Description |
|---|---|---|
id | integer | Sleep session ID. |
start_time, end_time | timestamp | When the session started and ended. |
sleep_score | integer | null | 0–100 sleep score, if one was calculated. |
deep_minutes, light_minutes, rem_minutes, awake_minutes | integer | Minutes in each stage. |
total_sleep_minutes | integer | Total minutes asleep. |
curl "https://api.arkaayu.health/api/sleep-data?start_date=2026-10-05&end_date=2026-10-07" \
-H "Authorization: Bearer $ACCESS_TOKEN"{
"user_id": 123,
"data": [
{
"id": 88213,
"start_time": "2026-10-06T17:12:00",
"end_time": "2026-10-07T00:41:00",
"sleep_score": 82,
"deep_minutes": 74,
"light_minutes": 251,
"rem_minutes": 96,
"awake_minutes": 28,
"total_sleep_minutes": 421
},
{
"id": 88157,
"start_time": "2026-10-05T17:48:00",
"end_time": "2026-10-06T00:22:00",
"sleep_score": 71,
"deep_minutes": 58,
"light_minutes": 232,
"rem_minutes": 71,
"awake_minutes": 33,
"total_sleep_minutes": 361
}
],
"count": 2,
"start_date": "2026-10-05",
"end_date": "2026-10-07"
}Steps & activity
One row per day with steps, calories and exercise minutes. Accepts date ranges.
| Field | Type | Description |
|---|---|---|
activity_date | date | The calendar day. |
steps | integer | Total steps that day. |
calories | integer | Calories burned that day (kcal). |
exercise_minutes | integer | Minutes of exercise recorded that day. |
{
"user_id": 123,
"data": [
{ "activity_date": "2026-10-07", "steps": 9412, "calories": 412, "exercise_minutes": 38 },
{ "activity_date": "2026-10-06", "steps": 6120, "calories": 287, "exercise_minutes": 21 },
{ "activity_date": "2026-10-05", "steps": 11873, "calories": 535, "exercise_minutes": 52 }
],
"count": 3,
"start_date": "2026-10-05",
"end_date": "2026-10-07"
}Heart rate
Daily heart-rate summaries (default) or individual readings. Accepts date ranges plus:
| Parameter | Default | Description |
|---|---|---|
daily | true | true for one summary row per day; false for individual readings. |
The envelope also has truncated: true when there were more rows than the 5,000-row limit and the oldest were left out. Individual readings are dense, so with daily=false ask for a few days at a time.
Daily summaries (daily=true)
| Field | Type | Description |
|---|---|---|
activity_date | date | The calendar day. |
avg_hr, min_hr, max_hr | integer | Average, lowest and highest heart rate (bpm). |
resting_hr | integer | null | Resting heart rate (bpm). |
hrv_ms | number | null | Heart-rate variability in milliseconds. |
{
"user_id": 123,
"data": [
{ "activity_date": "2026-10-07", "avg_hr": 74, "resting_hr": 61, "min_hr": 52, "max_hr": 148, "hrv_ms": 42.5 },
{ "activity_date": "2026-10-06", "avg_hr": 71, "resting_hr": 60, "min_hr": 50, "max_hr": 131, "hrv_ms": 46.1 }
],
"count": 2,
"start_date": "2026-10-06",
"end_date": "2026-10-07",
"truncated": false
}Individual readings (daily=false)
curl "https://api.arkaayu.health/api/heart-rate?daily=false&start_date=2026-10-07&end_date=2026-10-07" \
-H "Authorization: Bearer $ACCESS_TOKEN"{
"user_id": 123,
"data": [
{ "recorded_at": "2026-10-07T09:45:00", "bpm": 78 },
{ "recorded_at": "2026-10-07T09:40:00", "bpm": 81 },
{ "recorded_at": "2026-10-07T09:35:00", "bpm": 76 }
],
"count": 3,
"start_date": "2026-10-07",
"end_date": "2026-10-07",
"truncated": false
}Blood oxygen (SpO₂)
Blood-oxygen saturation readings — spot checks and overnight measurements. Accepts date ranges.
| Field | Type | Description |
|---|---|---|
recorded_at | timestamp | When the reading was taken. |
spo2_pct | number | Oxygen saturation in percent. |
{
"user_id": 123,
"data": [
{ "recorded_at": "2026-10-07T21:02:00", "spo2_pct": 97 },
{ "recorded_at": "2026-10-07T20:31:00", "spo2_pct": 96 },
{ "recorded_at": "2026-10-07T04:15:00", "spo2_pct": 98 }
],
"count": 3,
"start_date": "2026-10-07",
"end_date": "2026-10-07"
}Blood pressure
Blood-pressure readings. Note that this endpoint uses the read:heart_rate scope. Accepts date ranges.
| Field | Type | Description |
|---|---|---|
recorded_at | timestamp | When the reading was taken. |
systolic, diastolic | integer | Pressure in mmHg. |
pulse | integer | null | Pulse at the time of the reading (bpm). |
source | string | Describes where the reading came from, for example "band" or "manual". |
{
"user_id": 123,
"data": [
{ "recorded_at": "2026-10-07T03:10:00", "systolic": 118, "diastolic": 76, "pulse": 68, "source": "band" },
{ "recorded_at": "2026-10-06T14:22:00", "systolic": 124, "diastolic": 81, "pulse": 72, "source": "manual" }
],
"count": 2,
"start_date": "2026-10-06",
"end_date": "2026-10-07"
}Workouts
Workouts the user recorded on the watch or in the app. Accepts date ranges.
| Field | Type | Description |
|---|---|---|
id | integer | Workout ID. |
activity_type | string | For example "walking", "running", "cycling", "yoga". |
start_time, end_time | timestamp | When the workout started and ended. |
avg_hr, max_hr | integer | null | Average and peak heart rate (bpm). |
calories | integer | null | Calories burned (kcal). |
{
"user_id": 123,
"data": [
{
"id": 5521,
"activity_type": "running",
"start_time": "2026-10-07T00:35:00",
"end_time": "2026-10-07T01:08:00",
"avg_hr": 146,
"max_hr": 171,
"calories": 318
},
{
"id": 5498,
"activity_type": "yoga",
"start_time": "2026-10-05T01:00:00",
"end_time": "2026-10-05T01:45:00",
"avg_hr": 92,
"max_hr": 118,
"calories": 141
}
],
"count": 2,
"start_date": "2026-10-01",
"end_date": "2026-10-07"
}ECG recordings
A list of the user's ECG recordings. To keep responses small, the list contains summaries only — no waveform. Fetch a single recording to get its samples. Accepts date ranges.
| Field | Type | Description |
|---|---|---|
id | integer | Recording ID — pass it to /api/ecg/{id}. |
recorded_at | timestamp | When the recording started. |
duration_seconds | number | Length of the recording. |
sample_count | integer | Number of samples in the waveform. |
sampling_hz | number | Samples per second. |
device_bpm | integer | null | Heart rate the device calculated during the recording. |
device_name | string | null | The device that made the recording. |
{
"user_id": 123,
"data": [
{
"id": 902,
"recorded_at": "2026-10-06T08:14:22",
"duration_seconds": 30,
"sample_count": 15000,
"sampling_hz": 500,
"device_bpm": 68,
"device_name": "Arkaayu Watch"
}
],
"count": 1,
"start_date": "2026-09-08",
"end_date": "2026-10-07"
}ECG recording with waveform
One ECG recording, including its waveform in samples — an array of integers, sample_count long, at sampling_hz samples per second. If the recording doesn't exist or belongs to another user, you get 404 with not_found.
curl https://api.arkaayu.health/api/ecg/902 -H "Authorization: Bearer $ACCESS_TOKEN"{
"user_id": 123,
"data": {
"id": 902,
"recorded_at": "2026-10-06T08:14:22",
"duration_seconds": 30,
"sample_count": 15000,
"sampling_hz": 500,
"device_bpm": 68,
"device_name": "Arkaayu Watch",
"samples": [12, 14, 15, 13, 9, 4, -2, -6, 31, 188, 412, 296, 47, -38, -21, -9, 0, 6, 11, 15]
}
}Handle ECG data with extra careECG waveforms are sensitive health data. Don't present your own analysis of them as a diagnosis unless your product has the required regulatory approvals — see the Developer Terms.
Body measurements
Height and weight entries. Values are always returned in metric units; entered_unit tells you what the user typed, so you can show it back to them the same way. Accepts date ranges.
| Field | Type | Description |
|---|---|---|
metric | string | "height" or "weight". |
value | number | The measurement, in unit. |
unit | string | "cm" for height, "kg" for weight. |
entered_unit | string | The unit the user entered, for example "ft" or "lb". |
measured_at | timestamp | When the measurement was taken. |
source | string | Describes where the measurement came from, for example "band" or "manual". |
{
"user_id": 123,
"data": [
{
"metric": "weight",
"value": 64.2,
"unit": "kg",
"entered_unit": "kg",
"measured_at": "2026-10-04T02:30:00",
"source": "manual"
},
{
"metric": "height",
"value": 162.6,
"unit": "cm",
"entered_unit": "ft",
"measured_at": "2026-09-15T06:00:00",
"source": "manual"
}
],
"count": 2,
"start_date": "2026-09-08",
"end_date": "2026-10-07"
}Errors
Errors are top-level JSON in the same RFC 6749 shape as the OAuth endpoints:
{
"error": "invalid_request",
"error_description": "start_date must be on or before end_date"
}| Status | Error | When |
|---|---|---|
| 400 | invalid_request | Bad date, range over 400 days, or start_date after end_date. |
| 401 | invalid_token | Missing, expired or revoked access token. |
| 403 | insufficient_scope | The token doesn't include this endpoint's scope. The WWW-Authenticate header names the scope needed, e.g. Bearer error="insufficient_scope", scope="read:sleep". |
| 404 | not_found | The ECG recording doesn't exist or isn't this user's. |
| 429 | — | Too many requests. Back off and retry. |
See Errors & rate limits for retry advice and data freshness.
Questions or something unclear? Write to developers@arkaayu.com.