Wearable devices collect continuous streams of health metrics: heart rate, step count, sleep stages, and — relevant to body dimension prediction — body weight. Some devices also measure height at setup, though it’s rarely updated. A few advanced platforms (Apple Watch with body composition features, smart scales) collect additional circumference approximations.
If your application already integrates a wearable API and needs to add body dimension prediction, the wearable data is a natural input source. Getting the integration right requires understanding what each platform actually provides, the normalization challenges across different data schemas, and how to handle data freshness.
What wearable APIs provide
Apple HealthKit (iOS)
Apple HealthKit exposes user health data via the iOS HealthKit framework and, for server-side access, via the Health Records API (for clinical data) or third-party integrations. Relevant body metrics:
HKQuantityTypeIdentifierHeight— user-entered height (rarely updated)HKQuantityTypeIdentifierBodyMass— weight, typically from a connected smart scale or manual entryHKQuantityTypeIdentifierBodyMassIndex— BMI, derivedHKQuantityTypeIdentifierWaistCircumference— available but rarely populated outside clinical appsHKQuantityTypeIdentifierLeanBodyMass— from compatible body composition scales
Google Health Connect (Android)
Android’s Health Connect provides similar data types:
HeightRecord— height in metersWeightRecord— weight in kilograms, with time seriesBodyFatRecord— body fat percentage from compatible scalesWaistCircumferenceRecord— rarely populated
Garmin Connect API
REST API for Garmin device users who have authorized your application:
dailiesendpoint includesweightInGrams(if connected to a Garmin Index scale)bodyCompsendpoint includes weight, BMI, and body fat if the user has a Garmin Index S2 scale
Oura Ring API
- No direct body dimensions; provides biometric signals (HRV, sleep, activity)
- Some users link Oura to a weight app; Oura doesn’t natively capture body dimensions
Normalization challenges
Each platform uses different units, data structures, and update frequencies. Before feeding wearable data into a body measurement prediction, normalize it.
from dataclasses import dataclass
from datetime import datetime
from typing import Optional
@dataclass
class NormalizedBodyMetrics:
height_cm: Optional[float]
weight_kg: Optional[float]
waist_cm: Optional[float]
body_fat_pct: Optional[float]
recorded_at: datetime
source: str # "apple_health", "google_health_connect", "garmin", "manual"
height_confidence: str # "measured" | "user_entered" | "estimated"
weight_confidence: str # "scale" | "user_entered" | "estimated"
def normalize_apple_health(raw_data: dict) -> NormalizedBodyMetrics:
"""
Normalize Apple HealthKit data.
raw_data: dict of HKQuantityType samples, keyed by type identifier.
"""
height_sample = raw_data.get("HKQuantityTypeIdentifierHeight")
weight_sample = raw_data.get("HKQuantityTypeIdentifierBodyMass")
waist_sample = raw_data.get("HKQuantityTypeIdentifierWaistCircumference")
height_cm = None
if height_sample:
# HealthKit returns height in meters by default
value = height_sample["value"]
unit = height_sample.get("unit", "m")
if unit == "m":
height_cm = value * 100
elif unit == "cm":
height_cm = value
elif unit == "ft":
height_cm = value * 30.48
weight_kg = None
if weight_sample:
value = weight_sample["value"]
unit = weight_sample.get("unit", "kg")
if unit == "kg":
weight_kg = value
elif unit == "lb":
weight_kg = value * 0.453592
elif unit == "g":
weight_kg = value / 1000
waist_cm = None
if waist_sample:
value = waist_sample["value"]
unit = waist_sample.get("unit", "cm")
if unit == "cm":
waist_cm = value
elif unit == "m":
waist_cm = value * 100
elif unit == "in":
waist_cm = value * 2.54
recorded_at_str = weight_sample.get("endDate") or height_sample.get("endDate", "")
recorded_at = datetime.fromisoformat(recorded_at_str) if recorded_at_str else datetime.now()
return NormalizedBodyMetrics(
height_cm=round(height_cm, 1) if height_cm else None,
weight_kg=round(weight_kg, 2) if weight_kg else None,
waist_cm=round(waist_cm, 1) if waist_cm else None,
body_fat_pct=None,
recorded_at=recorded_at,
source="apple_health",
height_confidence="user_entered", # HealthKit height is almost always manually entered
weight_confidence="scale" if weight_sample else "user_entered"
)
def normalize_garmin(dailies_data: dict, body_comps_data: dict) -> NormalizedBodyMetrics:
"""Normalize Garmin Connect API data."""
weight_kg = None
weight_grams = dailies_data.get("weightInGrams")
if weight_grams:
weight_kg = weight_grams / 1000
body_fat_pct = body_comps_data.get("bodyFat")
return NormalizedBodyMetrics(
height_cm=None, # Garmin doesn't provide height in most endpoints
weight_kg=round(weight_kg, 2) if weight_kg else None,
waist_cm=None,
body_fat_pct=body_fat_pct,
recorded_at=datetime.now(),
source="garmin",
height_confidence="estimated",
weight_confidence="scale" if weight_grams else "user_entered"
)
Data freshness and staleness
The biggest practical problem with wearable body metrics is that they’re often stale. Users enter their height once during app setup and never update it. Weight is more current if the user has a smart scale, but many users update weight manually and infrequently.
Track the age of each metric and communicate this to users:
from datetime import datetime, timedelta, timezone
def assess_data_freshness(metrics: NormalizedBodyMetrics) -> dict:
"""
Returns a freshness assessment for body metrics.
"""
now = datetime.now(timezone.utc)
age_days = (now - metrics.recorded_at.replace(tzinfo=timezone.utc)).days
freshness = {}
if metrics.height_cm:
# Height changes very slowly in adults; stale is acceptable
if metrics.height_confidence == "user_entered":
freshness["height"] = {
"age_days": age_days,
"status": "acceptable",
"note": "Height rarely changes for adults; user-entered value is typically reliable"
}
if metrics.weight_kg:
# Weight can change significantly week-to-week
if age_days > 30:
status = "stale"
note = f"Weight was recorded {age_days} days ago. Ask user to confirm current weight."
elif age_days > 7:
status = "moderate"
note = f"Weight recorded {age_days} days ago. Reasonably current."
else:
status = "fresh"
note = "Weight recently recorded."
freshness["weight"] = {"age_days": age_days, "status": status, "note": note}
return freshness
When weight data is stale (>30 days), prompt the user to confirm or update before generating a size recommendation. A 5kg weight change can shift size predictions by one size.
Combining wearable data with body dimension prediction
Once you have normalized, fresh metrics, the integration with a body dimension prediction API is straightforward. The wearable data provides height and weight; the prediction API returns the full dimensional profile.
import requests
def get_dimensions_from_wearable(
metrics: NormalizedBodyMetrics,
gender: str,
region: str = "GLOBAL"
) -> dict:
"""
Combine wearable body metrics with a prediction API.
Requires height and weight at minimum.
"""
if not metrics.height_cm or not metrics.weight_kg:
raise ValueError("Height and weight are required for body dimension prediction")
anchors = {
"body_height": int(metrics.height_cm * 10), # cm → mm
"body_mass": metrics.weight_kg
}
# If waist is available from wearable (rare but possible), include it
if metrics.waist_cm:
anchors["waist_circumference"] = int(metrics.waist_cm * 10) # cm → mm
response = requests.post(
"https://dimensionspot-bodysize-engine.p.rapidapi.com/v1/predict",
json={
"input_data": {
"input_unit_system": "metric",
"subject": {
"gender": gender,
"input_origin_region": region
},
"anchors": anchors
},
"output_settings": {
"calculation": {"target_region": region, "body_build_type": "CIVILIAN"},
"requested_dimensions": {"bundle": "TORSO"},
"output_format": {
"include_range_95": True,
"confidence_score_threshold": 60
}
}
},
headers={
"X-RapidAPI-Key": "YOUR_API_KEY",
"X-RapidAPI-Host": "dimensionspot-bodysize-engine.p.rapidapi.com"
}
)
result = response.json()
result["data_source"] = {
"wearable": metrics.source,
"height_confidence": metrics.height_confidence,
"weight_confidence": metrics.weight_confidence,
"data_age_days": (datetime.now(timezone.utc) - metrics.recorded_at.replace(tzinfo=timezone.utc)).days
}
return result
Privacy considerations for wearable data integration
Wearable APIs gate access behind user consent and OAuth scopes. Best practices:
Request minimal scopes. If you only need height and weight, request only HKQuantityTypeIdentifierHeight and HKQuantityTypeIdentifierBodyMass. Don’t request full health data access for a sizing feature.
Don’t persist wearable data. Use the wearable data to generate a prediction, present the result, and discard the raw metrics from the wearable. Store only what your application needs (the prediction inputs, not the full wearable health record).
Be explicit about the data flow. Users should understand that their wearable data is being used for sizing. This is typically not surprising if framed clearly in the UI, but it should be stated: “We’re using your height and weight from Apple Health to recommend your size.”
Handle revoked access gracefully. Users can revoke wearable permissions at any time. Build a fallback to manual height/weight input that’s always available.
The practical value of wearable integration for sizing is primarily convenience: users who already have their height and weight logged don’t need to re-enter it in your sizing form. The dimensional prediction is the same regardless of whether the inputs came from manual entry or a smart scale — the wearable integration just removes friction from data collection.