Skip to content

Headless Mode

Headless mode lets you use the SDK’s processing capabilities without rendering any default UI. Build your own interface or run sessions in the background.

Headless mode is the right choice when:

  • Custom UI — You want full control over the scanning interface to match your application’s design system. See the Custom UI guide for a complete walkthrough.
  • Programmatic sessions — You need to trigger scans from application logic without user interaction (e.g., automated health checks in a kiosk or telehealth flow).
  • Existing UI integration — You already have a camera feed and UI components, and you want to add vital signs measurement without the SDK’s default overlay.
  • Server-side orchestration — Your backend controls when scans happen, and the frontend just needs to capture and process frames.
Standard ModeHeadless Mode
UI renderingSDK handles itYou handle it
Camera accessSDK manages automaticallySDK manages, or you provide a stream
Progress feedbackBuilt-in overlayYou implement via callbacks/events
Quality warningsBuilt-in promptsYou implement via onQualityWarning
Time to integrateFasterMore work, more control

To use headless mode, omit the container option when calling measureVitals(). The SDK instance itself is initialized the same way:

import { CircadifySDK } from '@circadify/sdk';
const sdk = new CircadifySDK({
apiKey: 'ck_test_your_key_here',
onProgress: (progress) => {
// Handle progress in your own UI
console.log(`${progress.phase}: ${progress.percent}%`);
},
onQualityWarning: (warning) => {
// Display quality feedback in your own UI
console.warn(warning.message);
},
});

All standard configuration options still apply in headless mode. The only difference is that measureVitals() is called without a container:

// Headless: no container
const result = await sdk.measureVitals({
demographics: { age: 35, sex: 'M' },
});
// Standard: with container
const result = await sdk.measureVitals({
container: document.getElementById('scan-container'),
});

A headless session follows the same lifecycle as a standard session. The difference is that your code is responsible for reflecting each phase in the UI.

Create the SDK instance with your callbacks:

const sdk = new CircadifySDK({
apiKey: 'ck_test_your_key_here',
onProgress: (progress) => updateProgressBar(progress.percent),
onQualityWarning: (warning) => showWarning(warning.message),
});

Call measureVitals() without a container. The SDK requests camera access (if not already granted), begins frame capture, and starts processing:

try {
const result = await sdk.measureVitals({
demographics: { age: 35, sex: 'M' },
});
displayResults(result);
} catch (error) {
handleError(error);
}

The onProgress callback fires throughout the scan with phase and percent information. Typical phases include calibration (face detection and light metering) and measurement (vital signs extraction).

measureVitals() resolves with a VitalSignsResult on success, or throws a CircadifyError on failure. Check the confidence score to determine result reliability.

Call sdk.destroy() when you are done to release all resources:

sdk.destroy();

You can also use the event API to monitor session state:

sdk.on('session:progress', (event) => {
updateProgressBar(event.progress);
});
sdk.on('session:complete', (event) => {
displayResults(event.result);
});
sdk.on('session:error', (event) => {
handleError(event.error);
});

Pass an AbortController signal to allow the user (or your application logic) to cancel a scan:

const controller = new AbortController();
// Cancel after 30 seconds
setTimeout(() => controller.abort(), 30000);
try {
const result = await sdk.measureVitals({
signal: controller.signal,
});
} catch (error) {
if (error.code === 'CANCELLED') {
console.log('Scan was cancelled.');
}
}

By default, the SDK requests camera access via getUserMedia when measureVitals() is called and releases it when the scan completes or destroy() is called.

In most cases, you do not need to manage the camera yourself. The SDK will:

  1. Request front-facing camera access (facingMode: 'user')
  2. Select an appropriate resolution for processing
  3. Release the camera stream when the session ends

If you already have a camera stream (for example, because your app displays a preview before the scan), you can provide it to avoid a second permission prompt:

// Acquire your own camera stream
const stream = await navigator.mediaDevices.getUserMedia({
video: {
facingMode: 'user',
width: { ideal: 640 },
height: { ideal: 480 },
},
});
// Display in your custom video element
const video = document.getElementById('my-video');
video.srcObject = stream;
// Pass the stream to the SDK -- it will use your stream instead of requesting its own
const result = await sdk.measureVitals({
mediaStream: stream,
});

For best results, ensure your stream meets these minimum requirements:

  • Resolution: 640x480 or higher
  • Frame rate: 15 fps or higher (30 fps recommended)
  • Facing mode: Front-facing camera (user) for face-based vital signs

Lower resolutions or frame rates may reduce measurement accuracy and confidence scores.

  • Custom UI — Build a custom visual interface on top of headless mode
  • Error Codes — Handle errors in headless mode