Features Integrations AI & API Pricing Blog Docs
Overview WHOOP Oura Withings Apple Health Hevy REST API AI & Claude MCP Server

REST API v1

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.

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 public REST endpoints are versioned by URL path. The current stable version is v1.

All endpoints return JSON. Dates are ISO 8601 strings (YYYY-MM-DD). Timestamps are UTC unless a field is explicitly documented as a local date.


Versioning

FieldValue
API versionv1
Docs version2026-05-09
Base path/api/v1
StabilityAdditive fields may be added to v1; breaking response or behavior changes require a new versioned path such as /api/v2.

v1 changelog

DateChange
2026-05-19/workouts/unified now includes stable session_id, source_records, and field_sources fields so clients can show one physical session while preserving raw provider provenance. Add include=heart_rate_series to return per-source HR overlays on session rows.
2026-05-11/whoop/daily now includes the current local-day open cycle when present and adds an additive is_partial flag. Closed-cycle fields keep their existing meaning.
2026-05-09Hevy workout endpoints added: /workouts/unified now includes Hevy, and /workouts/hevy returns Hevy workouts with exercises and sets.
2026-05-09/whoop/recovery-status added recovery freshness timestamps for agents and automations; /whoop/daily now includes recovery sync/change timestamps.
2026-05-08Oura data endpoints added for daily sleep, sleep sessions, readiness, activity, workouts, SpO2, stress, resilience, and VO2 max.
2026-05-05Date-only start and end filters on timestamp-backed list endpoints now cover the full calendar day in the user's profile timezone.
2026-05-05/whoop/daily added cycle_start_date and cycle_end_date while keeping the legacy date field unchanged.
2026-05-05Rate limit documentation corrected to match the active API limiter: 120 requests per minute per API key.

Endpoints

Workouts

MethodPathDescription
GET /workouts/unified Cross-source workout feed with WHOOP, Apple Health, Oura, Strava, and Hevy deduplicated into sessions. Each row includes session_id, canonical provider fields, source_records, and field_sources; append include=heart_rate_series for per-source HR overlay samples.
GET /workouts/hevy Hevy workout records with nested exercises and sets
curl -s "https://vitaltrends.net/api/v1/workouts/hevy?start=2026-05-01&per_page=10" \
  -H "Authorization: Bearer YOUR_API_KEY" | jq .

WHOOP

MethodPathDescription
GET /whoop/daily Recovery, HRV, RHR, sleep score, and strain per day
GET /whoop/recovery-status Latest recovery freshness, sync, and upstream update timestamps
GET /whoop/workouts Workout records with sport, duration, and heart rate zones
GET /whoop/sleep Sleep sessions with stages and performance scores

Oura

MethodPathDescription
GET /oura/daily-sleep Daily sleep scores and sleep score contributors
GET /oura/sleep Sleep sessions with stages, duration, HRV, heart rate, and respiratory rate
GET /oura/daily-readiness Daily readiness scores, temperature deviation, and readiness contributors
GET /oura/daily-activity Activity scores, calories, steps, distance, active minutes, and activity contributors
GET /oura/workouts Workout sessions with activity, intensity, source, calories, distance, and timestamps
GET /oura/daily-spo2 Daily SpO2 averages and breathing disturbance index
GET /oura/daily-stress Daily stress, recovery, and Oura day summary
GET /oura/daily-resilience Daily resilience level and resilience contributors
GET /oura/vo2-max VO2 max estimates by day

Withings

MethodPathDescription
GET /withings/measurements Weight, body fat, muscle mass, and bone mass readings

Apple Health

MethodPathDescription
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).

ParameterTypeDefaultDescription
datedatetoday (UTC)Single day, YYYY-MM-DD
typescsvallSubset to include. Any of: activity, heart, sleep, body, workouts
curl -s "https://vitaltrends.net/api/v1/apple-health/daily-summary?date=2026-04-21" \
  -H "Authorization: Bearer YOUR_API_KEY" | jq .
{
  "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,
      "body_comp_date": "2026-04-18"
    },
    "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).

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 .
{
  "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

CategoryValues
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:

ParameterTypeDefaultDescription
startdate or datetimenoneInclusive lower bound. Date-only values use YYYY-MM-DD.
enddate or datetimenoneInclusive upper bound. Date-only values use YYYY-MM-DD.
per_pageinteger50Records per page (max 200)
pageinteger1Page number (1-indexed)

For timestamp-backed endpoints, date-only start and end values are interpreted as full calendar-day boundaries in your profile timezone. Date-backed Apple Health aggregate endpoints and Oura daily endpoints compare against the stored date value directly.

On /whoop/daily, date remains the legacy cycle-end date for closed cycles; use cycle_start_date and cycle_end_date when you need explicit local cycle dates. If the user's current local-day cycle is still open, the endpoint may include it with is_partial: true and cycle_end_date: null. For morning automations, /whoop/daily is the canonical endpoint for deciding whether today's WHOOP recovery and sleep row is usable: select the row where cycle_start_date equals the user's local date and require both recovery_score and sleep_duration_minutes.

WHOOP recovery freshness

Use /whoop/recovery-status as advisory sync metadata only. It is useful when an automation needs to know whether VitalTrends recently checked, changed, or received upstream recovery data, but it is not a current-day completeness gate. Its latest_recovery and latest_cycle summaries use raw WHOOP cycle timestamps and do not apply the /whoop/daily sleep wake-day normalization for open cycles. last_recovery_data_changed_at advances only when stored recovery values change. last_recovery_synced_at advances when VitalTrends checks/applies recovery data, even if values are unchanged.

curl -s "https://vitaltrends.net/api/v1/whoop/recovery-status" \
  -H "Authorization: Bearer YOUR_API_KEY" | jq .
{
  "data": {
    "connected": true,
    "needs_reconnection": false,
    "latest_recovery": {
      "cycle_id": "123e4567-e89b-12d3-a456-426614174000",
      "cycle_start_date": "2026-05-08",
      "cycle_end_date": "2026-05-09",
      "recovery_score": 82,
      "hrv_rmssd_milli": 68.4,
      "resting_heart_rate": 48,
      "recovery_synced_at": "2026-05-09T07:06:00+00:00",
      "recovery_data_changed_at": "2026-05-09T07:06:00+00:00",
      "whoop_recovery_updated_at": "2026-05-09T07:05:00+00:00"
    },
    "latest_cycle": {
      "cycle_id": "123e4567-e89b-12d3-a456-426614174000",
      "cycle_start_date": "2026-05-08",
      "cycle_end_date": "2026-05-09",
      "recovery_score": 82,
      "hrv_rmssd_milli": 68.4,
      "resting_heart_rate": 48,
      "recovery_synced_at": "2026-05-09T07:06:00+00:00",
      "recovery_data_changed_at": "2026-05-09T07:06:00+00:00",
      "whoop_recovery_updated_at": "2026-05-09T07:05:00+00:00"
    },
    "last_recovery_synced_at": "2026-05-09T07:06:00+00:00",
    "last_recovery_data_changed_at": "2026-05-09T07:06:00+00:00",
    "last_whoop_recovery_updated_at": "2026-05-09T07:05:00+00:00",
    "is_recovery_data_stale": false,
    "stale_after_hours": 24
  }
}

If is_recovery_data_stale is true or last_recovery_data_changed_at is more than 24 hours old, treat recovery freshness as uncertain and try again later. Do not conclude that today's recovery or sleep row is missing until you have checked /whoop/daily for the user's current local cycle_start_date. Opening the VitalTrends WHOOP dashboard also triggers a small recent-window WHOOP refresh when the latest recovery appears stale.

WHOOP daily date is the legacy cycle-end date for closed cycles; use cycle_start_date, cycle_end_date, and is_partial when you need explicit local cycle boundaries. WHOOP workout fields ending in _milli are durations in milliseconds, and kilojoule is energy in kilojoules.

Oura duration fields are returned in the units provided by Oura. Sleep durations are seconds; workout distance is meters; calorie fields are kilocalories.

On /oura/workouts, source is Oura's own workout source value. For example, confirmed means the workout was saved in Oura after confirmation in the Oura app. It is not a VitalTrends confidence score or a cross-device verification status. Other common values include autodetected, manual, and workout_heart_rate.

Withings measurement fields ending in _kg are stored in kilograms, and fat_ratio_pct is a percentage. Empty Withings fields mean the connected device did not provide that metric for the measurement, not that VitalTrends rejected it.

Apple Health source is the HealthKit device or app label that wrote the sample, not a confidence or verification status. Pass include=metadata on /apple-health?type=<type> to see sleep stages and workout details. For cumulative daily metrics such as steps, distance, and energy, VitalTrends avoids double-counting by choosing the largest per-source daily total by default.


Example request

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

Example response

{
  "data": [
    {
      "date": "2024-01-07",
      "cycle_start_date": "2024-01-06",
      "cycle_end_date": "2024-01-07",
      "is_partial": false,
      "recovery_score": 82,
      "hrv_rmssd_milli": 68.4,
      "resting_heart_rate": 48,
      "sleep_performance_pct": 89,
      "sleep_duration_minutes": 450,
      "strain": 12.4,
      "whoop_recovery_updated_at": "2024-01-07T07:05:00+00:00",
      "recovery_synced_at": "2024-01-07T07:06:00+00:00",
      "recovery_data_changed_at": "2024-01-07T07:06:00+00:00"
    }
  ],
  "meta": {
    "current_page": 1,
    "last_page": 1,
    "per_page": 7,
    "total": 7,
    "from": 1,
    "to": 7
  }
}

Rate limits

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


Error responses

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

Need an endpoint that is not listed here? Get in touch and we will consider adding it.