Results
After a session completes, vital signs results are returned directly in the POST /sdk/session/upload-complete response. By default, this is the only way results are delivered — no server-side storage occurs. When PERSIST_VITALS=true is configured for async/polling workflows, results are also available via GET /sdk/session/result/{sessionId} for the duration of the configured TTL.
Result Object
Section titled “Result Object”{ "session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "status": "completed", "vitals": { "heart_rate": 72, "respiratory_rate": 16, "hrv": 45.2, "spo2": 98.1, "systolic_bp": 122, "diastolic_bp": 78, "confidence": 0.87 }, "completed_at": 1712001900}Vital Signs Fields
Section titled “Vital Signs Fields”| Field | Type | Unit | Description |
|---|---|---|---|
heart_rate | number | BPM | Heart rate in beats per minute |
respiratory_rate | number | breaths/min | Respiratory rate in breaths per minute |
hrv | number | ms | Heart rate variability (SDNN) in milliseconds |
spo2 | number | % | Blood oxygen saturation percentage |
systolic_bp | number | mmHg | Systolic blood pressure |
diastolic_bp | number | mmHg | Diastolic blood pressure |
confidence | number | 0–1 | Measurement reliability score |
All vital sign fields are present on every completed result. The confidence score applies to the measurement as a whole.
Typical Ranges
Section titled “Typical Ranges”These are the expected ranges for healthy adults. Values outside these ranges are still valid measurements — they may indicate a health condition.
| Vital | Typical Range |
|---|---|
| Heart Rate | 60–100 BPM |
| Respiratory Rate | 12–20 breaths/min |
| HRV (SDNN) | 20–100 ms |
| SpO2 | 95–100% |
| Systolic BP | 90–140 mmHg |
| Diastolic BP | 60–90 mmHg |
Confidence Score
Section titled “Confidence Score”The confidence field indicates how reliable the measurement is, on a scale from 0.0 to 1.0:
| Range | Meaning | Recommendation |
|---|---|---|
| 0.7–1.0 | High confidence | Results are reliable for most use cases |
| 0.4–0.7 | Moderate confidence | Results are usable but may benefit from a re-scan |
| 0.1–0.4 | Low confidence | Results may be unreliable — poor lighting, motion, or face detection issues |
| 0.0 | Fallback values | Inference failed; synthetic values were generated. Do not treat as real measurements |
Handling confidence in your application
Section titled “Handling confidence in your application”const result = await sdk.measureVitals({ container: document.getElementById('scan-container'),});
if (result.confidence === 0) { // Fallback values — inference failed showError('Measurement could not be completed. Please retry.');} else if (result.confidence < 0.4) { // Low confidence — quality issues showWarning('Low confidence. Try again with better lighting and hold still.');} else { // Good measurement displayResults(result);}Result Availability
Section titled “Result Availability”In the default configuration, results are returned once in the upload-complete response and are not persisted server-side. Your application should handle the results immediately upon receiving the response.
When PERSIST_VITALS=true is enabled, results are cached for the configured TTL (default 15 minutes) and can be retrieved via polling. After the TTL expires, results are automatically and irreversibly deleted.
Uploaded tensor data is automatically deleted after processing is complete regardless of mode.
Polling for Results
Section titled “Polling for Results”If you are using persist mode and calling the API directly (without the SDK), poll GET /sdk/session/result/{sessionId} every 2 seconds until the status is completed:
async function pollResults(sessionId, apiKey) { const maxAttempts = 60; // 120 seconds at 2s intervals
for (let i = 0; i < maxAttempts; i++) { const response = await fetch( `https://api.circadify.com/sdk/session/result/${sessionId}`, { headers: { 'X-API-Key': apiKey } } );
const data = await response.json();
if (data.status === 'completed' || data.status === 'failed') { return data; }
await new Promise(resolve => setTimeout(resolve, 2000)); }
throw new Error('Polling timed out');}