Skip to content

Vanilla JavaScript

The Circadify SDK works without any framework. The SDK is headless: you render the <video> element and any overlays, the SDK drives the camera, extraction, and upload, and emits per-frame events for you to consume.

@circadify/web-sdk is a private package — complete the .npmrc + PAT setup first, then install:

Terminal window
npm install @circadify/web-sdk

The SDK ships as ESM with a CommonJS fallback. Use a bundler (Vite, webpack, esbuild, Parcel) to pull it into your page:

import { CircadifySDK } from '@circadify/web-sdk';
const sdk = new CircadifySDK({
apiKey: 'ck_live_your_key_here',
});

Render a <video> element. Pass it as videoElement to measureVitals(). The SDK binds the camera stream to that element and processes frames out of it.

<!DOCTYPE html>
<html>
<body>
<video id="preview" autoplay playsinline muted style="width: 480px"></video>
<button id="start-btn">Start Scan</button>
<pre id="results"></pre>
<script type="module">
import { CircadifySDK } from '@circadify/web-sdk';
const videoEl = document.getElementById('preview');
const sdk = new CircadifySDK({
apiKey: 'ck_live_your_key_here',
onProgress: (e) => console.log(`${e.phase}: ${e.percent}%`),
onQualityWarning: (w) => console.warn('Quality issue:', w),
});
document.getElementById('start-btn').addEventListener('click', async () => {
try {
const result = await sdk.measureVitals({ videoElement: videoEl });
document.getElementById('results').textContent =
JSON.stringify(result, null, 2);
} catch (error) {
console.error('Scan failed:', error.message);
}
});
</script>
</body>
</html>

The measureVitals() call handles the full lifecycle: camera access, face detection, measurement, and result delivery. It returns a VitalSignsResult with heartRate, respiratoryRate, hrv, spo2, systolicBp, diastolicBp, confidence, sessionId, and timestamp.

The SDK reports session progress and per-frame state through callbacks set at construction time. There is no .on() event-emitter API — subscribe by configuring callbacks on the CircadifySDK constructor:

const sdk = new CircadifySDK({
apiKey: 'ck_live_your_key_here',
// Phase + percent throughout the scan
onProgress: (event) => {
const bar = document.getElementById('progress');
bar.style.width = `${event.percent}%`;
bar.textContent = `${event.phase} (${Math.round(event.percent)}%)`;
},
// Full per-frame quality state (lighting, motion, pose, readiness)
onQualityState: (q) => {
setLightingPill(q.lighting.isOk);
setMotionPill(q.motion.isStill);
setPosePill(q.pose.isFacingForward);
},
// Transient quality warnings — good for toast notifications
onQualityWarning: (warning) => {
showToast(warning.message);
},
// 468-point face mesh (MediaPipe). Render your own overlay if you want one.
onLandmarks: (landmarks) => {
canvasRenderer.drawMesh(landmarks);
},
// Fires once after the camera is acquired (only when no videoElement is passed)
onCameraReady: ({ stream, video }) => {
document.getElementById('start-btn').disabled = false;
},
});

Successful results and errors are surfaced through measureVitals()’s returned promise — there is no separate completion event:

try {
const result = await sdk.measureVitals({ videoElement: videoEl });
document.getElementById('result').textContent =
`Heart Rate: ${result.heartRate} BPM`;
} catch (error) {
document.getElementById('error').textContent = error.message;
document.getElementById('error').hidden = false;
}

See SDK Configuration for the full callback list.

Use an AbortController to let the user cancel a scan mid-session:

const controller = new AbortController();
document.getElementById('cancel-btn').addEventListener('click', () => {
controller.abort();
});
try {
const result = await sdk.measureVitals({
videoElement: videoEl,
signal: controller.signal,
});
} catch (error) {
if (error.code === 'CANCELLED') {
console.log('Scan cancelled by user.');
}
}

Call sdk.destroy() when you are done with the SDK to release all resources. This stops active camera streams and unloads WASM modules.

// When navigating away or tearing down
sdk.destroy();

For pages that load and unload dynamically (SPA routing), tie cleanup to your router’s lifecycle:

window.addEventListener('beforeunload', () => {
sdk.destroy();
});