Vanilla JavaScript
Use the headless Circadify SDK without any framework — bring your own video element and overlays.
The Circadify SDK works without any framework. The SDK is headless: you render the <video> element and any overlays, the SDK drives the camera, measurement preparation, and upload, and emits per-frame events for you to consume.
Install the SDK
@circadify/web-sdk is a private package — complete the .npmrc + Circadify SDK access token setup first, then install:
npm install @circadify/web-sdkbashThe 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',
});javascriptBasic Setup
Render a <video> element. Pass it as videoElement to measureVitals(). The SDK binds the camera stream to that element and uses it during measurement.
<!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>htmlThe measureVitals() call handles the full lifecycle: camera access, measurement, upload, and result delivery. It returns a VitalSignsResult with heartRate, respiratoryRate, hrv, spo2, systolicBp, diastolicBp, confidence, sessionId, and timestamp.
If you don't pass videoElement, the SDK creates an off-DOM video and emits onCameraReady({ stream, video }) so you can mirror the live stream into your own UI.
Lifecycle Callbacks
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);
},
// Normalized face landmarks. Render your own overlay if you want one —
// prefer a 2D canvas, or use the Web SDK's React bindings' CircadifyScanView for a ready-made glow overlay.
onLandmarks: (landmarks) => {
canvasRenderer.drawOverlay(landmarks);
},
// Fires once after the camera is acquired (only when no videoElement is passed)
onCameraReady: ({ stream, video }) => {
document.getElementById('start-btn').disabled = false;
},
});javascriptSuccessful 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;
}javascriptSee SDK Configuration for the full callback list.
Don't build a face or heat-glow overlay from onLandmarks by hand. Use CircadifyScanView from the Web SDK's React bindings for a ready-made glow overlay, or — for a framework-less custom overlay — a 2D <canvas> in immediate mode (see Custom UI → Face overlay). Avoid a hand-rolled three.js / @react-three/fiber overlay; the Advanced section documents the rules and a known freeze bug. Landmarks are normalized [0,1] and not mirrored.
Cancelling a Scan
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.');
}
}javascriptCleanup
Call sdk.destroy() when you are done with the SDK to release all resources. This stops active camera streams and releases SDK runtime resources.
// When navigating away or tearing down
sdk.destroy();javascriptAlways call destroy() when the SDK is no longer needed. Failing to do so can leave the camera active and cause memory leaks, especially in single-page applications where the page does not fully reload between views.
For pages that load and unload dynamically (SPA routing), tie cleanup to your router's lifecycle:
window.addEventListener('beforeunload', () => {
sdk.destroy();
});javascriptNext Steps
- Custom UI — Build a fully-custom scan UI from the SDK callbacks
- SDK Configuration — All available options
- Python SDK — Measure from a webcam, video file, or your own frames in Python
- Troubleshooting — Common issues and fixes