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 Range | Color | Label | Meaning |
| 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
| Endpoint | Purpose | Call Frequency | Latency |
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
| Header | Value |
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:
| Endpoint | Limit | Rationale |
/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
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
| Field | Type | Required | Description | Example |
age | number | No | Age in years | 35 |
sex | string | No | Biological sex (M, F, Male, Female) | "M" |
weight_kg | number | No | Weight in kilograms (20–500) | 70 |
height_cm | number | No | Height in centimeters (50–300) | 170 |
rhr | number | No | Resting Heart Rate (bpm) | 60 |
hrv_rmssd | number | No | HRV RMSSD (ms) | 50 |
vo2max | number | No | VO2 Max (ml/kg/min) | 45 |
spo2_min | number | No | Minimum nocturnal SpO2 (%) | 96 |
steps_7d_avg | number | No | Average daily steps (7-day). Also accepts steps | 8000 |
sleep_hours_7d_avg | number | No | Average sleep hours (7-day). Also accepts sleep_hours | 7.5 |
sbp | number | No | Systolic Blood Pressure (mmHg) | 120 |
triglycerides | number | No | Triglycerides (mg/dL) | 150 |
ldl | number | No | LDL Cholesterol (mg/dL) | 120 |
hba1c | number | No | HbA1c (%) | 5.5 |
smoking | string | No | Smoking status code (see below) | "never" |
alcohol | string | No | Alcohol consumption code (see below) | "none" |
Smoking Codes
| Code | Description |
never | Never smoked |
former_gt20 | Quit > 20 years ago |
former_5to_lt20 | Quit 5–20 years ago |
former_lt5 | Quit < 5 years ago |
current_lt20 | Currently smoking < 20 cigs/day |
current_20to40 | Currently smoking 20–40 cigs/day |
current_ge40 | Currently smoking > 40 cigs/day |
Alcohol Codes
| Code | Description |
none | Don't drink |
0_1_to_1_8 | 0.1 to 1.8 drinks/day |
1_8_to_3_1 | 1.8 to 3.1 drinks/day |
3_2_to_4_6 | 3.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
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:
| Field | Type | Default | Description |
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).
| Field | Type | Description | Example |
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:
| Field | Non-null values | Result |
steps_history | 10000, 9000, 6000, 5000 (4 values) | avg = 7,500 step/day |
sleep_history | index 0 is null → falls back to index 1 = 4.2 | YTD Night 4.2 Hrs |
rhr_history | 72, 70, 71, 75, 77, 80 (6 values) | avg = 74 bpm/day |
⚠️ Important: null must be a bare JSON literal — not a quoted string.
"steps_history": [10000, null, 9000]
"steps_history": [10000, "null", 9000]
"steps_history": [10000, , 9000]
Averaging Rules
| Metric | Rule | Scoring Input | Caption Format |
| Steps | 7-day average | Average of all non-null values | avg 7,143 step/day |
| RHR | 7-day average | Average of all non-null values | avg 74 bpm/day |
| HRV | 7-day average | Average of all non-null values | avg 69 ms/day |
| Sleep | Yesterday's value | First non-null (index 0) | YTD Night 5 Hrs |
| SpO2 | Yesterday's value | First non-null (index 0) | YTD Night 98 % |
| VO2Max | Latest available | First non-null from history | Last. 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
| Field | Type | Description |
rank | integer | Rank by impact (1 = highest penalty) |
metric | string | Human-readable metric name |
current_value | any | Current value of the metric |
score_penalty | number | Estimated score points lost |
domain | string | "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 Name | Also Accepts |
steps_7d_avg | steps |
sleep_hours_7d_avg | sleep_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
| Status | Meaning | Action |
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.