Measurement Result
The canonical measurement JSON — the shape every SDK emits and the API returns.
There is one measurement result shape. It is what the SDKs deliver on completion, what the REST API stores and returns, and what the console renders. This page documents it field by field.
The envelope
API responses wrap the result in the standard envelope:
{ "ok": true, "data": { /* the measurement, below */ } }Errors use { "ok": false, "error": { "code", "message" } } — see
Errors.
The measurement object
{
"id": "meas_9c1f4b7ae2d05138b6a37f42", // string
"keyId": "nk_live_sRwG9z2hK2hdV4Il83J7Cw", // key that produced this measurement (or null)
"source": "web", // "web" | "ios" | "playground"
"origin": "cloud", // "cloud" | "on_device" — who computed the metrics
"attested": true, // true only when we computed them
"createdAt": 1751810602000, // epoch milliseconds, number
"durationSec": 32.4, // capture length in seconds
"isComplete": true, // boolean
"sdkVersion": "0.1.0", // client SDK that produced it, optional
"modelsVersion": "wv-5", // model bundle it ran, optional
"requestId": "req_9a1c4e77b0d2358fc6e4", // the API call that stored it, when there was one
"userRef": "u_42", // your own end-user id, when you sent one
"quality": { // REQUIRED, never null
"level": "good", // "good" | "fair" | "poor"
"score": 0.82, // 0..1
"usable": true // boolean
},
"cardiac": {
"heartRate": {
"value": 72, // int | null
"unit": "bpm",
"confidence": 0.88 // 0..1, present when the SDK emits one
}
},
"hrv": { // all int | null unless noted
"sdnnMs": 48, // headline
"rmssdMs": 41,
"pnn50Pct": 12.5, // number | null
"sd1Ms": 29.1, // Poincare, number | null
"sd2Ms": 61.4,
"sd1Sd2Ratio": 0.47,
"csi": 2.11,
"cvi": 3.25,
"lfhf": 1.4, // frequency domain, number | null
"lfPower": 812,
"hfPower": 580,
"vlfPower": 210,
"totalPower": 1602
},
"respiratory": {
"breathingRate": { "value": 15, "unit": "brpm" } // int | null
},
"stress": {
"index": 34, // int 0..100 | null
"level": "low", // "low" | "moderate" | "high" | null
"unit": "index"
},
// ── Beta sections. Wellness only; each carries tier and disclaimer. ──
"bloodPressure": {
"systolicMmHg": 118, // int | null
"diastolicMmHg": 77, // int | null
"mapMmHg": 90, // derived, int | null
"ppMmHg": 41, // derived, int | null
"unit": "mmHg",
"confidence": 0.51,
"tier": "beta",
"disclaimer": "Estimate for wellness use only. Not a medical device."
},
"spo2": {
"value": 97, // int | null
"unit": "%",
"confidence": 0.60,
"tier": "beta",
"disclaimer": "Estimate for wellness use only. Not a medical device."
},
"emotion": {
"dominant": "Neutral", // string | null
"dominantConfidence": 0.64, // 0..1 | null
"probabilities": { "Neutral": 0.64 }, // object | null
"tier": "beta",
"disclaimer": "Estimate for wellness use only. Not a medical device."
},
"behavior": {
"attentionPercent": 82, // int | null
"fatiguePercent": 24, // int | null
"blinkRate": 17, // per minute, int | null
"cardiacOutput": 5.1, // number | null
"gaze": { "yawDeg": -2.4, "pitchDeg": 1.1 },
"headPose": { "yawDeg": -3.0, "pitchDeg": 2.2, "rollDeg": 0.4 },
"tier": "beta",
"disclaimer": "Estimate for wellness use only. Not a medical device."
},
"raw": { // OPT-IN ONLY — omitted unless requested
"rrIntervalsMs": [812, 799, 834],
"ppgSignal": [0.12, 0.14, 0.11],
"sampleRateHz": 30
}
}Field rules
qualityis required and never null. See Signal quality.- Every section key is always present, but individual metric values may be
null. Always handlenull— hide or dim, never render a guessed number. - When
quality.levelis"poor", every metric isnull. That is "no measurement", not "a bad measurement". bloodPressure,spo2,emotionandbehaviorare Beta. Each carriestier: "beta"and adisclaimer. Never render them at the same visual authority as the Core sections.rawis absent by default. It appears only when you explicitly opt in — pass?advanced=trueon the query string or{ "includeAdvanced": true }in the request body when submitting. It contains RR intervals and the pulse signal for advanced use. It never contains pixels: camera frames are not part of a measurement in either tier.originandattestedare server-owned. The server sets them; a client cannot. They are stripped from anything you submit, exactly likeid,keyIdandcreatedAt.attestedistrueonly for cloud measurements, where we computed the metrics ourselves — see Attestation.sdkVersionandmodelsVersionsay which engine produced the numbers. Both are optional strings of at most 64 characters, matching^[A-Za-z0-9][A-Za-z0-9._+-]*$; a value outside that shape is rejected withvalidation_error. Unlikeoriginandattestedthey are client-owned: you send them, we store them and hand them back. The web SDK fills both in for you — see Submitting measurements. They are what the console groups by when it shows which versions are live in production, so a fleet that never sends them is invisible there.emotionis a separate, optional module with its own use restrictions, and the physical-state fields inbehavior(fatigue, blink rate, head pose) are not emotion inference. See Metrics.test: trueappears only on measurements produced by a test key. On real measurements the field is absent, neverfalse. See test keys.cloudJobIdappears only on cloud measurements and links back to the job that produced them. Server-owned likeorigin, absent on on-device measurements.tokensChargedappears only where a charge exists — the tokens this measurement cost, as a number. On-device measurements are not billed and have no such field. See Pricing.requestIdnames the API call the record came from. It is the value of theX-Request-Idheader on the response toPOST /sdk/v1/measurements, and the same value appears on that call's row in the usage log — one id ties the call, its log row and the stored record together. Server-owned; absent when no signed call produced the record.userRefis present when you sent one. Your opaque end-user identifier, echoed back — see End users.
Assessment
An optional interpretation block on top of the raw numbers: a wellness score,
per-metric norm badges, and machine-readable disclaimers. Console reads always
include it; API responses include it only when you ask with
?include=assessment on the query string.
"assessment": {
"wellnessScore": 74,
"badges": [
{ "metric": "hr", "state": "in_range", "value": 72, "range": { "low": 55, "high": 100 } },
{ "metric": "bp", "state": "above", "value": { "systolic": 132, "diastolic": 78 }, "range": { "systolic": { "low": 90, "high": 129 }, "diastolic": { "low": 60, "high": 84 } } },
{ "metric": "spo2", "state": "unknown", "value": null, "range": { "low": 95, "high": 100 } },
{ "metric": "breathing", "state": "in_range", "value": 15, "range": { "low": 10, "high": 20 } },
{ "metric": "stress", "state": "in_range", "value": 34, "range": { "low": 0, "high": 60 } }
],
"disclaimers": [
{ "code": "not_medical_device", "text": "Estimate for wellness use only. Not a medical device." },
{ "code": "bp_estimate", "text": "Blood pressure is a beta estimate with a typical error around ±10 mmHg. Ranges are informational, not diagnostic." }
]
}stateis one ofin_range | above | below | unknown. Anullmetric value maps tounknown— never to a guessed state.wellnessScoreis0..100, andnullwhenquality.levelis"poor".- A
low_qualitydisclaimer is added whenquality.levelisfairorpoor. - The badges are wellness reference ranges, not diagnostics. The disclaimer codes are machine-readable and are there to be shown: render the matching text next to the numbers it qualifies — the BP disclaimer next to the BP values in particular.
Confidence
A per-metric confidence (0..1) is included only where the SDK emits one —
today heart rate, blood pressure, and SpO2. HRV, breathing rate, and stress have
no per-metric confidence field and inherit trust from the top-level quality.
emotion reports its own dominantConfidence.
Metrics reference
For what each value means, its unit, range, and when it works, see Metrics.