MyToFit Health Score API

Developer Integration Guide

API v2.1

Overview

The MyToFit Health Score API calculates comprehensive health risk scores based on biometric and clinical data. It provides three scores — Total, Wearable, and Clinical — each mapped to a color-coded risk zone.

Score Zones

Score RangeColorLabelMeaning
85–100 Blue Superior Excellent health status
70–84 Green Good Good health status
55–69 Yellow Moderate Moderate health risks present
40–54 Orange Risk Elevated health risks
0–39 Red High Risk Significant health risks detected

Endpoints Summary

EndpointPurposeCall FrequencyLatency
POST /calculate Score calculation Multiple times/day ~100ms
POST /partner/daily-refresh Score + AI summaries + impact drivers Once/day ~3–5s

Authentication

All API access is via the GCP API Gateway with per-partner API keys issued by MyToFit.

Required Headers

HeaderValue
X-API-Key Your GCP API key (provided by MyToFit)
Content-Type application/json
https://mytofit-partner-gw-202hslfc.an.gateway.dev

Rate Limits

Per-API-key rate limits are enforced to ensure fair usage:

EndpointLimitRationale
/calculate 30 requests/minute Lightweight (~100ms), high-frequency
/partner/daily-refresh 15 requests/minute AI-powered (~3-5s), once-per-day design
429 Too Many Requests: If you exceed the rate limit, the API returns
{"error": "Rate limit exceeded: 30 per 1 minute"}
Wait and retry after the limit window resets (1 minute).

Endpoint 1: Calculate Score

POST /calculate High Frequency

Calculate MyToFit health scores. Returns all 3 scores with color-coded risk zones. All input fields are optional — partial data is accepted and missing fields use neutral defaults.

Request Body

FieldTypeRequiredDescriptionExample
agenumberNoAge in years35
sexstringNoBiological sex (M, F, Male, Female)"M"
weight_kgnumberNoWeight in kilograms (20–500)70
height_cmnumberNoHeight in centimeters (50–300)170
rhrnumberNoResting Heart Rate (bpm)60
hrv_rmssdnumberNoHRV RMSSD (ms)50
vo2maxnumberNoVO2 Max (ml/kg/min)45
spo2_minnumberNoMinimum nocturnal SpO2 (%)96
steps_7d_avgnumberNoAverage daily steps (7-day). Also accepts steps8000
sleep_hours_7d_avgnumberNoAverage sleep hours (7-day). Also accepts sleep_hours7.5
sbpnumberNoSystolic Blood Pressure (mmHg)120
triglyceridesnumberNoTriglycerides (mg/dL)150
ldlnumberNoLDL Cholesterol (mg/dL)120
hba1cnumberNoHbA1c (%)5.5
smokingstringNoSmoking status code (see below)"never"
alcoholstringNoAlcohol consumption code (see below)"none"

Smoking Codes

CodeDescription
neverNever smoked
former_gt20Quit > 20 years ago
former_5to_lt20Quit 5–20 years ago
former_lt5Quit < 5 years ago
current_lt20Currently smoking < 20 cigs/day
current_20to40Currently smoking 20–40 cigs/day
current_ge40Currently smoking > 40 cigs/day

Alcohol Codes

CodeDescription
noneDon't drink
0_1_to_1_80.1 to 1.8 drinks/day
1_8_to_3_11.8 to 3.1 drinks/day
3_2_to_4_63.2 to 4.6 drinks/day
gt_4_6> 4.6 drinks/day

Response (200 OK)

{
  "total_score": {
    "score": 88.5,
    "color": "Blue",
    "label": "Superior",
    "explanation": "Excellent health status.",
    "range": "85-100"
  },
  "wearable_score": {
    "score": 92.0,
    "color": "Blue",
    "label": "Superior",
    "explanation": "Excellent health status.",
    "range": "85-100"
  },
  "clinical_score": {
    "score": 85.0,
    "color": "Blue",
    "label": "Superior",
    "explanation": "Excellent health status.",
    "range": "85-100"
  }
}
Wearable History: This endpoint also accepts the optional *_history arrays (e.g. steps_history, rhr_history, etc.) documented in the Wearable History section below. When provided, history values are averaged and used for scoring instead of scalar values. The same averaging rules apply (7-day avg for steps/RHR/HRV, yesterday's value for sleep/SpO2, latest for VO2Max).

Sample Request with Wearable History

{
  "age": 35, "sex": "M",
  "weight_kg": 75, "height_cm": 175,

  "steps": 3000,
  "steps_history": [10000, 8000, 9000, 4000, 8000, 6000, 5000],
  "rhr": 73,
  "rhr_history": [72, 70, 71, 74, 75, 77, 80],
  "hrv_rmssd": 65,
  "hrv_history": [67, 69, 71, 68, 72, 70, 69],
  "sleep_history": [5, 4.2, 4, 6, 6.3, 6.45, 5],
  "spo2_history": [98, 98, 98, 98, 99, 98, 98],
  "vo2max_history": [32, 32, 32, 32, 32, 33, 33],

  "sbp": 128, "hdl": 52, "ldl": 130,
  "triglycerides": 160, "hba1c": 5.6
}
How history is used for scoring:
steps (3000, today’s partial) is ignored — scoring uses steps_history avg = 7,143
rhr (73, today) is ignored — scoring uses rhr_history avg = 74
hrv_rmssd (65, today) is ignored — scoring uses hrv_history avg = 69
• Sleep = yesterday’s value from sleep_history[0] = 5
• SpO2 = yesterday’s value from spo2_history[0] = 98
• VO2Max = latest from vo2max_history[0] = 32

Endpoint 2: Daily Health Refresh

POST /partner/daily-refresh Once Per Day

Batched endpoint returning score, AI-generated wellness summaries (short + long), top 6 score impact attributes, and echoed input values. Call once per day.

Request Body

Same input fields as /calculate, plus:

FieldTypeDefaultDescription
language string "en" Output language for summaries: "en" or "th"

Wearable History (optional)

Send 7 days of wearable history to enable averaged scoring and receive formatted captions for display. All history fields are optional — if omitted, the API falls back to scalar values (backward compatible).

FieldTypeDescriptionExample
steps_history number[] Daily step counts for past 7 days [yesterday..7d ago]. null = no data. [10000, 8000, 9000, 4000, 8000, 6000, 5000]
sleep_history number[] Sleep hours for past 7 days [5, 4.2, 4, 6, 6.3, 6.45, 5]
rhr_history number[] Resting heart rate (bpm) for past 7 days [72, 70, 71, 74, 75, 77, 80]
spo2_history number[] Min nocturnal SpO2 (%) for past 7 days [98, 98, 98, 98, 99, 98, 98]
vo2max_history number[] VO2Max (ml/kg/min) for past 7 days [32, 32, 32, 32, 32, 33, 33]
hrv_history number[] HRV RMSSD (ms) for past 7 days [67, 69, 71, 68, 72, 70, 69]
Array ordering: Index 0 = yesterday, index 6 = 7 days ago. null entries mean "no data that day" — they are skipped when computing averages.

Handling Missing Days (null entries)

If the wearable had no data for a specific day, use null at that position. The API skips nulls when computing averages.

{
  "steps_history": [10000, null, 9000, null, null, 6000, 5000],
  "sleep_history": [null, 4.2, 4, 6, 6.3, 6.45, 5],
  "rhr_history":   [72, 70, 71, null, 75, 77, 80]
}

In this example:

FieldNon-null valuesResult
steps_history10000, 9000, 6000, 5000 (4 values)avg = 7,500 step/day
sleep_historyindex 0 is null → falls back to index 1 = 4.2YTD Night 4.2 Hrs
rhr_history72, 70, 71, 75, 77, 80 (6 values)avg = 74 bpm/day
⚠️ Important: null must be a bare JSON literal — not a quoted string.
// ✅ Correct — null without quotes
"steps_history": [10000, null, 9000]

// ❌ Wrong — "null" as a string → 422 validation error
"steps_history": [10000, "null", 9000]

// ❌ Wrong — empty slot → invalid JSON
"steps_history": [10000, , 9000]

Averaging Rules

MetricRuleScoring InputCaption Format
Steps7-day averageAverage of all non-null valuesavg 7,143 step/day
RHR7-day averageAverage of all non-null valuesavg 74 bpm/day
HRV7-day averageAverage of all non-null valuesavg 69 ms/day
SleepYesterday's valueFirst non-null (index 0)YTD Night 5 Hrs
SpO2Yesterday's valueFirst non-null (index 0)YTD Night 98 %
VO2MaxLatest availableFirst non-null from historyLast. 32 ml/kg/min

Response (200 OK)

{
  "score": {
    "total_score":    { "score": 44.8, "color": "Orange", "label": "Risk", ... },
    "wearable_score": { "score": 59.4, "color": "Yellow", "label": "Moderate", ... },
    "clinical_score": { "score": 22.8, "color": "Red", "label": "High Risk", ... }
  },
  "summary_short": "Your top priority is smoking cessation.",
  "summary_long": "Your health profile shows significant cardiovascular risk factors...",
  "score_impact_attributes": [
    { "rank": 1, "metric": "Smoking", "current_value": "current_ge40", "score_penalty": 12.4, "domain": "Clinical" },
    { "rank": 2, "metric": "HbA1c (%)", "current_value": 5.9, "score_penalty": 8.1, "domain": "Clinical" },
    ...
  ],
  "wearable_captions": {
    "steps": "avg 7,143 step/day",
    "sleep_duration": "YTD Night 5 Hrs",
    "rhr": "avg 74 bpm/day",
    "spo2": "YTD Night 98 %",
    "vo2max": "Last. 32 ml/kg/min",
    "hrv": "avg 69 ms/day"
  },
  "input_values": { "age": 50, "sex": "M", "steps": 7142.86, ... },
  "generated_at": "2026-04-14T10:00:00+00:00"
}
wearable_captions: Only present when at least one *_history array is provided. When using legacy scalar inputs (no history), this field is null.

Score Impact Attributes

FieldTypeDescription
rankintegerRank by impact (1 = highest penalty)
metricstringHuman-readable metric name
current_valueanyCurrent value of the metric
score_penaltynumberEstimated score points lost
domainstring"Wearable" or "Clinical"
Note: A maximum of 6 impact attributes are returned, sorted by score_penalty descending. Only metrics that are actively penalizing the score appear in this list.

Sample Request with Wearable History

{
  "age": 35, "sex": "M",
  "weight_kg": 75, "height_cm": 175,

  "steps": 3000,
  "steps_history": [10000, 8000, 9000, 4000, 8000, 6000, 5000],
  "rhr": 73,
  "rhr_history": [72, 70, 71, 74, 75, 77, 80],
  "hrv_rmssd": 65,
  "hrv_history": [67, 69, 71, 68, 72, 70, 69],
  "sleep_history": [5, 4.2, 4, 6, 6.3, 6.45, 5],
  "spo2_history": [98, 98, 98, 98, 99, 98, 98],
  "vo2max_history": [32, 32, 32, 32, 32, 33, 33],

  "sbp": 128, "hdl": 52, "ldl": 130,
  "triglycerides": 160, "hba1c": 5.6,
  "language": "en"
}
Response includes wearable_captions: When history arrays are provided, the response adds formatted display captions (e.g. "avg 7,143 step/day") alongside scores, summaries, and impact attributes.

Integration Best Practices

Recommended call pattern:
/calculate → Call on each app open or user interaction (score gauge widget)
/partner/daily-refresh → Call once on first morning launch (update summaries + drivers)

Partial Data

Both endpoints accept partial input — all fields are optional. The engine always returns all 3 scores (total, wearable, clinical) regardless of how much data is provided. Missing fields use neutral/healthy defaults in the calculation.

Field Name Aliases

For convenience, the API accepts both naming conventions:

Primary NameAlso Accepts
steps_7d_avgsteps
sleep_hours_7d_avgsleep_hours

Code Examples

Python — GCP API Gateway

import requests

url = "https://mytofit-partner-gw-202hslfc.an.gateway.dev/calculate"

payload = {
    "age": 35, "sex": "M",
    "weight_kg": 70, "height_cm": 170,
    "sbp": 120, "triglycerides": 150, "ldl": 120, "hba1c": 5.5,
    "steps_7d_avg": 8000, "sleep_hours_7d_avg": 7.5,
    "rhr": 60, "vo2max": 45, "hrv_rmssd": 50, "spo2_min": 96,
    "smoking": "never", "alcohol": "none"
}

headers = {
    "Content-Type": "application/json",
    "X-API-Key": "YOUR_GCP_API_KEY"
}

response = requests.post(url, json=payload, headers=headers)
print(response.json())

JavaScript / Fetch — GCP API Gateway

const response = await fetch("https://mytofit-partner-gw-202hslfc.an.gateway.dev/calculate", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Key": "YOUR_GCP_API_KEY"
  },
  body: JSON.stringify({
    age: 35, sex: "M",
    weight_kg: 70, height_cm: 170,
    sbp: 120, rhr: 60,
    steps_7d_avg: 8000, sleep_hours_7d_avg: 7.5,
    smoking: "never", alcohol: "none"
  })
});

const data = await response.json();
console.log(data.total_score.score, data.total_score.color);

Swift (iOS) — GCP API Gateway

let url = URL(string: "https://mytofit-partner-gw-202hslfc.an.gateway.dev/calculate")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("YOUR_GCP_API_KEY", forHTTPHeaderField: "X-API-Key")

let body: [String: Any] = [
    "age": 35, "sex": "M",
    "weight_kg": 70, "height_cm": 170,
    "rhr": 60,
    "steps_7d_avg": 8000, "smoking": "never"
]
request.httpBody = try JSONSerialization.data(withJSONObject: body)

Kotlin (Android) — GCP API Gateway

val client = OkHttpClient()
val json = """{"age":35,"sex":"M","weight_kg":70,"height_cm":170,"rhr":60,"steps_7d_avg":8000}"""
val body = json.toRequestBody("application/json".toMediaType())

val request = Request.Builder()
    .url("https://mytofit-partner-gw-202hslfc.an.gateway.dev/calculate")
    .post(body)
    .addHeader("X-API-Key", "YOUR_GCP_API_KEY")
    .build()

val response = client.newCall(request).execute()

cURL — GCP API Gateway

curl --request POST \
  --url https://mytofit-partner-gw-202hslfc.an.gateway.dev/calculate \
  --header 'Content-Type: application/json' \
  --header 'X-API-Key: YOUR_GCP_API_KEY' \
  --data '{"age":35,"sex":"M","weight_kg":70,"height_cm":170,"sbp":120,"rhr":60,"steps_7d_avg":8000,"smoking":"never"}'

Error Handling

StatusMeaningAction
200 Success Parse the JSON response body
401 Invalid or missing API key Check your X-API-Key header
422 Validation error (invalid field values) Check the detail array for field-level errors
429 Rate limit exceeded Reduce call frequency. Limits: 30/min for /calculate, 15/min for /daily-refresh
500 Internal server error Retry with exponential backoff. Contact support if persistent.
422 responses never echo submitted health data. Only the field name and validation message are returned — no PHI leakage.