# REST API

The VitalTrends API lets you query your own health data programmatically. Build scripts, notebooks, automations, or pipe your data into any tool you like.

> **Pro feature.** API access requires an active VitalTrends subscription. Generate your API key in Settings → Developer after logging in at https://vitaltrends.net/login.

## Authentication

All API requests must include your API key in the `Authorization` header:

```
Authorization: Bearer YOUR_API_KEY
```

Keep your API key secret. Regenerate it from the Developer settings page if it is ever exposed.

## Base URL

```
https://vitaltrends.net/api/v1
```

All endpoints return JSON. Dates are ISO 8601 strings (`YYYY-MM-DD`). Timestamps are UTC.

## Endpoints

### WHOOP

| Method | Path | Description |
|--------|------|-------------|
| GET | `/whoop/daily` | Recovery, HRV, RHR, sleep score, and strain per day |
| GET | `/whoop/workouts` | Workout records with sport, duration, and heart rate zones |
| GET | `/whoop/sleep` | Sleep sessions with stages and performance scores |

### Withings

| Method | Path | Description |
|--------|------|-------------|
| GET | `/withings/measurements` | Weight, body fat, muscle mass, and bone mass readings |

### Apple Health

| Method | Path | Description |
|--------|------|-------------|
| GET | `/apple-health/daily-summary` | Everything for a single day in one call: activity, heart, sleep stages, body, workouts |
| GET | `/apple-health/daily` | Paginated daily totals across a date range (legacy wide-table shape) |
| GET | `/apple-health?type=<type>` | Per-type time series. Append `&include=metadata` to surface sleep stages and workout details. |

#### Daily summary, one request for a full picture

Use this when you want the morning-dashboard shape: every metric for a given date, including a sleep stage breakdown (`deep`, `rem`, `core`, `awake`, `in_bed`) and full workout metadata (sport, duration, HR min/avg/max, energy, flights climbed).

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `date` | date | today (UTC) | Single day, `YYYY-MM-DD` |
| `types` | csv | all | Subset to include. Any of: `activity`, `heart`, `sleep`, `body`, `workouts` |

```bash
curl -s "https://vitaltrends.net/api/v1/apple-health/daily-summary?date=2026-04-21" \
  -H "Authorization: Bearer YOUR_API_KEY" | jq .
```

```json
{
  "data": {
    "date": "2026-04-21",
    "activity": {
      "steps": 12500,
      "distance_km": 8.234,
      "active_energy_kcal": 487.2,
      "basal_energy_kcal": 1789.4,
      "apple_move_time_min": 45,
      "apple_exercise_time_min": 28,
      "apple_stand_time_min": 180,
      "flights_climbed": 12,
      "time_in_daylight_min": 87,
      "apple_stand_hours": 12
    },
    "heart": {
      "avg_heart_rate": 68,
      "min_heart_rate": 52,
      "max_heart_rate": 134,
      "resting_heart_rate": 56,
      "hrv_ms": 45.2,
      "respiratory_rate": 14.2,
      "spo2_pct": 97.8,
      "vo2_max": 42.5
    },
    "sleep": {
      "total_minutes": 452,
      "start_time": "2026-04-20T23:14:00+00:00",
      "end_time": "2026-04-21T06:46:00+00:00",
      "stages": {
        "deep_min": 68,
        "rem_min": 92,
        "core_min": 256,
        "light_min": 0,
        "awake_min": 36,
        "in_bed_min": 480,
        "unknown_min": 0
      },
      "sources": ["Apple Watch"]
    },
    "body": {
      "weight_kg": 75.20,
      "body_fat_pct": 18.40,
      "lean_body_mass_kg": 61.30,
      "bmi": 23.4,
      "height_m": 1.79
    },
    "workouts": [
      {
        "uuid": "018f0a3b-1c7e-7d9a-8a2b-8c5c47b3d401",
        "type": "Functional Strength Training",
        "sport_key": "functional_strength_training",
        "start_time": "2026-04-21T07:02:00+00:00",
        "end_time": "2026-04-21T07:47:00+00:00",
        "duration_min": 45,
        "active_energy_kcal": 320,
        "distance_m": null,
        "avg_heart_rate": 132,
        "min_heart_rate": 85,
        "max_heart_rate": 165,
        "flights_climbed": 3,
        "elevation_gain_m": null,
        "source": "Apple Watch"
      }
    ]
  }
}
```

> **Multi-source.** When both iPhone and Apple Watch record the same metric (e.g. steps), the summary returns the larger per-source total instead of summing them, so two devices never double-count.

#### Per-type time series, with optional metadata

Use this when you want raw samples, a date range, or per-segment sleep stages. Pass `include=metadata` to reveal the `metadata` field (sleep stage, workout type, labels).

```bash
curl -s "https://vitaltrends.net/api/v1/apple-health?type=sleep&start=2026-04-20&end=2026-04-21&include=metadata" \
  -H "Authorization: Bearer YOUR_API_KEY" | jq .
```

```json
{
  "data": [
    {
      "data_type": "sleep",
      "date": "2026-04-20",
      "start_time": "2026-04-21T01:45:00+00:00",
      "end_time": "2026-04-21T05:45:00+00:00",
      "value": 0,
      "value_min": null,
      "value_max": null,
      "sample_count": 1,
      "unit": "hr",
      "source": "Apple Watch",
      "metadata": { "stage": "core" }
    },
    {
      "data_type": "sleep",
      "date": "2026-04-20",
      "start_time": "2026-04-20T23:15:00+00:00",
      "end_time": "2026-04-21T00:15:00+00:00",
      "value": 0,
      "value_min": null,
      "value_max": null,
      "sample_count": 1,
      "unit": "hr",
      "source": "Apple Watch",
      "metadata": { "stage": "deep" }
    }
  ],
  "links": { "first": "...", "last": null, "prev": null, "next": null },
  "meta": { "current_page": 1, "from": 1, "path": "...", "per_page": 50, "to": 2 }
}
```

#### Valid `type` values

| Category | Values |
|----------|--------|
| Activity | `steps`, `distance_walking_running`, `active_energy_burned`, `basal_energy_burned`, `apple_move_time`, `apple_stand_hour`, `time_in_daylight`, `physical_effort`, `swimming_stroke_count_qty`, `workout_effort_score`, `estimated_workout_effort_score` |
| Heart & vitals | `heart_rate`, `resting_heart_rate`, `heart_rate_variability`, `heart_rate_recovery`, `respiratory_rate`, `oxygen_saturation`, `blood_pressure_systolic`, `blood_pressure_diastolic`, `blood_glucose`, `body_temperature`, `afib_burden` |
| Sleep | `sleep`, `apple_sleeping_breathing_disturbances`, `sleep_apnea_event` |
| Workouts | `workouts` |
| Body composition | `body_mass`, `body_fat_percentage`, `lean_body_mass`, `body_mass_index`, `height` |
| Mobility | `walking_double_support`, `walking_steadiness`, `stair_ascent_speed`, `stair_descent_speed`, `six_minute_walk_distance` |
| Running & cycling | `running_power`, `cycling_power`, `cycling_speed` |
| Water sports | `underwater_depth`, `water_temperature`, `distance_paddle_sports`, `paddle_sports_speed`, `distance_rowing`, `rowing_speed` |
| Winter sports | `distance_cross_country_skiing`, `cross_country_skiing_speed`, `distance_skating_sports` |
| Fitness events | `low_cardio_fitness_event` |

Availability depends on which HealthKit sensors and iOS version your device supports. Types that are defined but not yet ingested by the VitalTrends iOS companion app are listed in `meta.unavailable_types` on the daily summary response.

## Query parameters

All list endpoints support the following parameters:

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `start` | date | 30 days ago | Inclusive start date (`YYYY-MM-DD`) |
| `end` | date | today | Inclusive end date (`YYYY-MM-DD`) |
| `per_page` | integer | 50 | Records per page (max 200) |
| `page` | integer | 1 | Page number (1-indexed) |

## Example request

```bash
curl -s "https://vitaltrends.net/api/v1/whoop/daily?start=2024-01-01&per_page=7" \
  -H "Authorization: Bearer YOUR_API_KEY" | jq .
```

### Example response

```json
{
  "data": [
    {
      "date": "2024-01-07",
      "recovery_score": 82,
      "hrv_rmssd_milli": 68.4,
      "resting_heart_rate": 48,
      "sleep_performance_pct": 89,
      "sleep_duration_minutes": 450,
      "strain": 12.4
    }
  ],
  "meta": {
    "current_page": 1,
    "last_page": 1,
    "per_page": 7,
    "total": 7,
    "from": 1,
    "to": 7
  }
}
```

## Rate limits

The API allows **500 requests per minute** per API key. If you exceed the limit, requests return `429 Too Many Requests` with a `Retry-After` header.

## Error responses

| Status | Meaning |
|--------|---------|
| `401` | Missing or invalid API key |
| `403` | Valid key but subscription required |
| `422` | Invalid query parameters |
| `429` | Rate limit exceeded |
| `500` | Server error, try again shortly |

---

Need an endpoint that is not listed here? Get in touch at https://vitaltrends.net/contact and we will consider adding it.
