Circadify

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-sdk
bash

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',
});
javascript

Basic 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>
html

The 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.

Tip

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;
  },
});
javascript

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;
}
javascript

See SDK Configuration for the full callback list.

Face / heat-glow overlay

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.');
  }
}
javascript

Cleanup

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();
javascript
Caution

Always 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();
});
javascript

Next Steps