This page gets you from zero to a working health score query. You'll create a Sonar user in the sandbox, explore pre-loaded health data, and query computed scores — all from your terminal.

Prerequisites

  • A Sonar account — sign up here if you don't have one
  • A sandbox API key from your dashboard
  • curl or any HTTP client (Python and TypeScript examples below)

Try It Out

Create a User

A Sonar user is the container for all device connections and health data belonging to one of your end-users. You supply your own reference_id to link them back to your system.

python
import requests

BASE = "https://api.sonarhealth.co/v1"
HEADERS = {"Authorization": "Bearer sk_sandbox_abc123", "Content-Type": "application/json"}

user = requests.post(f"{BASE}/users", headers=HEADERS, json={
    "reference_id": "user_123",
}).json()["data"]

print(user["id"])  # usr_abc123
typescript
const BASE = "https://api.sonarhealth.co/v1";
const HEADERS = {
  Authorization: "Bearer sk_sandbox_abc123",
  "Content-Type": "application/json",
};

const { data: user } = await fetch(`${BASE}/users`, {
  method: "POST",
  headers: HEADERS,
  body: JSON.stringify({
    reference_id: "user_123",
  }),
}).then(r => r.json());

console.log(user.id); // usr_abc123
json
{
  "data": {
    "id": "usr_abc123",
    "reference_id": "user_123",
    "timezone": "UTC",
    "status": "active",
    "score_status": "ready",
    "last_sync_at": null,
    "last_score_at": null,
    "created_at": "2025-01-15T10:00:00Z"
  }
}

In the sandbox, every new user is automatically seeded with 7 days of realistic wearable data and pre-computed health scores — no device connection required. That's why score_status is ready immediately. In production, new users start as calibrating until enough data arrives (typically 3–4 days). Store user.id — you'll use it in every subsequent request.

Query Health Data

Query normalized daily metrics for your sandbox user:

python
daily = requests.get(f"{BASE}/users/{user['id']}/daily", headers=HEADERS, params={
    "metrics": "steps,active_calories,resting_heart_rate,heart_rate_variability",
    "start_date": "2025-01-08",  # sandbox data covers 7 days before user creation
    "end_date": "2025-01-14",
}).json()["data"]

for row in daily:
    print(f"{row['date']}  {row['metric']:20s}  {row['value']}")
typescript
const params = new URLSearchParams({
  metrics: "steps,active_calories,resting_heart_rate,heart_rate_variability",
  start_date: "2025-01-08", // sandbox data covers 7 days before user creation
  end_date: "2025-01-14",
});

const { data: daily } = await fetch(
  `${BASE}/users/${user.id}/daily?${params}`,
  { headers: HEADERS }
).then(r => r.json());

for (const row of daily) {
  console.log(`${row.date}  ${row.metric.padEnd(20)}  ${row.value}`);
}
json
{
  "data": [
    { "date": "2025-01-14", "metric": "steps", "value": 9842, "unit": "count" },
    { "date": "2025-01-14", "metric": "active_calories", "value": 485, "unit": "kcal" },
    { "date": "2025-01-14", "metric": "resting_heart_rate", "value": 52, "unit": "bpm" },
    { "date": "2025-01-14", "metric": "heart_rate_variability", "value": 58, "unit": "ms" },
    { "date": "2025-01-13", "metric": "steps", "value": 12100, "unit": "count" }
  ]
}

Data is always in normalized units regardless of the source device. See the Data Catalog for all 70+ metric IDs and the Data Model for how normalization and deduplication work.

Query Health Scores

Health scores are computed automatically as data arrives. Query the latest scores for a user:

python
scores = requests.get(f"{BASE}/users/{user['id']}/scores", headers=HEADERS).json()["data"]

for score in scores:
    print(f"{score['type']:22s}  {score['value']:3d}/100")
typescript
const { data: scores } = await fetch(
  `${BASE}/users/${user.id}/scores`,
  { headers: HEADERS }
).then(r => r.json());

for (const score of scores) {
  console.log(`${score.type.padEnd(22)}  ${score.value}/100`);
}
json
{
  "data": [
    {
      "type": "recovery_score",
      "value": 78,
      "computed_at": "2025-01-15T07:30:00Z",
      "factors": [
        { "name": "hrv_trend", "contribution": 0.35, "direction": "positive" },
        { "name": "sleep_quality", "contribution": 0.28, "direction": "positive" },
        { "name": "resting_heart_rate", "contribution": 0.22, "direction": "neutral" }
      ]
    },
    { "type": "strain_score",         "value": 42, "computed_at": "2025-01-15T14:00:00Z", "factors": [] },
    { "type": "sleep_score",          "value": 85, "computed_at": "2025-01-15T07:30:00Z", "factors": [] },
    { "type": "stress_score",         "value": 22, "computed_at": "2025-01-15T14:00:00Z", "factors": [] },
    { "type": "nutrition_score",      "value": 65, "computed_at": "2025-01-15T12:00:00Z", "factors": [] },
    { "type": "energy_reserve_score", "value": 71, "computed_at": "2025-01-15T14:00:00Z", "factors": [] }
  ]
}

With real devices, scores require a baseline period before they become available — check the score_status field on the user object (calibrating, ready, or stale). See Health Scores for calibration timelines per score type.