Reference

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.

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:

ParameterFormatDescription
start_dateYYYY-MM-DDFirst day to include. Defaults to the start of the 30-day window ending on end_date.
end_dateYYYY-MM-DDLast day to include. Defaults to today.
  • Both dates are inclusive. The range can be at most 400 days.
  • If start_date is after end_date, or a date isn't valid, you get 400 with invalid_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:

JSON
{
  "user_id": 123,
  "data": [ ... ],
  "count": 7,
  "start_date": "2026-10-01",
  "end_date": "2026-10-07"
}
FieldTypeDescription
user_idintegerThe Arkaayu user the token belongs to. Stable for your app — use it to match responses to your own user records.
dataarrayThe rows, newest first. An empty array means no data in the range (for example, the watch hasn't synced yet).
countintegerNumber of rows in data.
start_date, end_datestringThe 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

GET/api/meread: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.

Shell
curl https://api.arkaayu.health/api/me -H "Authorization: Bearer $ACCESS_TOKEN"
Response
{
  "data": {
    "id": 123,
    "firstName": "Priya",
    "lastName": "Sharma",
    "email": "priya.sharma@example.com"
  },
  "scope": "read:profile read:sleep read:steps"
}

Sleep

GET/api/sleep-dataread:sleep

Sleep sessions recorded by the watch, with time in each stage and a sleep score. Accepts date ranges.

FieldTypeDescription
idintegerSleep session ID.
start_time, end_timetimestampWhen the session started and ended.
sleep_scoreinteger | null0–100 sleep score, if one was calculated.
deep_minutes, light_minutes, rem_minutes, awake_minutesintegerMinutes in each stage.
total_sleep_minutesintegerTotal minutes asleep.
Shell
curl "https://api.arkaayu.health/api/sleep-data?start_date=2026-10-05&end_date=2026-10-07" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
Response
{
  "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

GET/api/stepsread:steps

One row per day with steps, calories and exercise minutes. Accepts date ranges.

FieldTypeDescription
activity_datedateThe calendar day.
stepsintegerTotal steps that day.
caloriesintegerCalories burned that day (kcal).
exercise_minutesintegerMinutes of exercise recorded that day.
Response
{
  "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

GET/api/heart-rateread:heart_rate

Daily heart-rate summaries (default) or individual readings. Accepts date ranges plus:

ParameterDefaultDescription
dailytruetrue 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)

FieldTypeDescription
activity_datedateThe calendar day.
avg_hr, min_hr, max_hrintegerAverage, lowest and highest heart rate (bpm).
resting_hrinteger | nullResting heart rate (bpm).
hrv_msnumber | nullHeart-rate variability in milliseconds.
Response
{
  "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)

Shell
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"
Response
{
  "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₂)

GET/api/spo2read:spo2

Blood-oxygen saturation readings — spot checks and overnight measurements. Accepts date ranges.

FieldTypeDescription
recorded_attimestampWhen the reading was taken.
spo2_pctnumberOxygen saturation in percent.
Response
{
  "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

GET/api/blood-pressureread:heart_rate

Blood-pressure readings. Note that this endpoint uses the read:heart_rate scope. Accepts date ranges.

FieldTypeDescription
recorded_attimestampWhen the reading was taken.
systolic, diastolicintegerPressure in mmHg.
pulseinteger | nullPulse at the time of the reading (bpm).
sourcestringDescribes where the reading came from, for example "band" or "manual".
Response
{
  "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

GET/api/workoutsread:workouts

Workouts the user recorded on the watch or in the app. Accepts date ranges.

FieldTypeDescription
idintegerWorkout ID.
activity_typestringFor example "walking", "running", "cycling", "yoga".
start_time, end_timetimestampWhen the workout started and ended.
avg_hr, max_hrinteger | nullAverage and peak heart rate (bpm).
caloriesinteger | nullCalories burned (kcal).
Response
{
  "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

GET/api/ecgread:ecg

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.

FieldTypeDescription
idintegerRecording ID — pass it to /api/ecg/{id}.
recorded_attimestampWhen the recording started.
duration_secondsnumberLength of the recording.
sample_countintegerNumber of samples in the waveform.
sampling_hznumberSamples per second.
device_bpminteger | nullHeart rate the device calculated during the recording.
device_namestring | nullThe device that made the recording.
Response
{
  "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

GET/api/ecg/{id}read:ecg

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.

Shell
curl https://api.arkaayu.health/api/ecg/902 -H "Authorization: Bearer $ACCESS_TOKEN"
Response (samples shortened)
{
  "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

GET/api/body-measurementsread:profile

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.

FieldTypeDescription
metricstring"height" or "weight".
valuenumberThe measurement, in unit.
unitstring"cm" for height, "kg" for weight.
entered_unitstringThe unit the user entered, for example "ft" or "lb".
measured_attimestampWhen the measurement was taken.
sourcestringDescribes where the measurement came from, for example "band" or "manual".
Response
{
  "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:

Response · 400 Bad Request
{
  "error": "invalid_request",
  "error_description": "start_date must be on or before end_date"
}
StatusErrorWhen
400invalid_requestBad date, range over 400 days, or start_date after end_date.
401invalid_tokenMissing, expired or revoked access token.
403insufficient_scopeThe 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".
404not_foundThe 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.