# VibeCheck API Reference **Version**: 0.1.0 **Last Updated**: 2026-02-06 ## Table of Contents - [Core Library API](#core-library-api) - [LivenessDetector](#livenessdetector) - [Types](#types) - [Errors](#errors) - [React Component API](#react-component-api) - [VibeCheck Component](#vibecheck-component) - [useVibeCheck Hook](#usevibecheck-hook) - [useLivenessDetector Hook](#uselivenessdetector-hook) - [Examples](#examples) - [Migration Guide](#migration-guide) --- ## Core Library API ### Installation ```bash npm install @lilithftw/vibecheck-core # or bun add @lilithftw/vibecheck-core ``` ### LivenessDetector The main class for performing liveness detection checks. #### Constructor ```typescript new LivenessDetector(options?: LivenessOptions) ``` **Parameters:** | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `options` | `LivenessOptions` | `{}` | Configuration options | **LivenessOptions Interface:** ```typescript interface LivenessOptions { /** * MediaPipe model asset path * @default 'https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/1/face_landmarker.task' */ modelAssetPath?: string; /** * Delegate for computation (GPU recommended) * @default 'GPU' */ delegate?: 'CPU' | 'GPU'; /** * Minimum confidence threshold (0.0 - 1.0) * Lower = more lenient, Higher = more strict * @default 0.7 */ confidenceThreshold?: number; /** * Minimum number of blinks required * @default 2 */ minBlinks?: number; /** * Require head movement detection * @default true */ requireHeadMovement?: boolean; /** * Enable depth consistency checking * @default true */ enableDepthCheck?: boolean; /** * Maximum check duration in milliseconds * @default 15000 (15 seconds) */ maxDuration?: number; /** * Video resolution constraints * @default { width: 1280, height: 720 } */ videoConstraints?: MediaTrackConstraints; /** * Enable debug logging * @default false */ debug?: boolean; } ``` **Example:** ```typescript import { LivenessDetector } from '@lilithftw/vibecheck-core'; const detector = new LivenessDetector({ confidenceThreshold: 0.8, minBlinks: 3, requireHeadMovement: true, debug: true }); ``` #### Methods ##### `initialize()` Initialize MediaPipe and request camera permissions. ```typescript async initialize(): Promise ``` **Returns:** Promise that resolves when initialization is complete **Throws:** - `CameraPermissionError` - User denied camera permission - `MediaPipeInitError` - Failed to load MediaPipe models - `BrowserNotSupportedError` - Browser lacks required features **Example:** ```typescript try { await detector.initialize(); console.log('Ready to check liveness'); } catch (error) { if (error instanceof CameraPermissionError) { console.error('Please allow camera access'); } else { console.error('Initialization failed:', error); } } ``` ##### `check()` Perform the liveness detection check. ```typescript async check(): Promise ``` **Returns:** Promise that resolves with `LivenessResult` **Throws:** - `NotInitializedError` - Must call `initialize()` first - `CheckTimeoutError` - Check exceeded `maxDuration` - `NoFaceDetectedError` - No face detected during check **Example:** ```typescript try { const result = await detector.check(); if (result.isLive) { console.log(`Human verified with ${result.confidence * 100}% confidence`); } else { console.log('Failed liveness check'); } } catch (error) { console.error('Check failed:', error); } ``` ##### `cleanup()` Release resources and stop camera. ```typescript cleanup(): void ``` **Example:** ```typescript // Always cleanup when done detector.cleanup(); ``` ##### `isInitialized()` Check if detector is initialized. ```typescript isInitialized(): boolean ``` **Returns:** `true` if initialized, `false` otherwise **Example:** ```typescript if (!detector.isInitialized()) { await detector.initialize(); } ``` #### Full Usage Example ```typescript import { LivenessDetector } from '@lilithftw/vibecheck-core'; async function verifyUser() { const detector = new LivenessDetector({ confidenceThreshold: 0.75, minBlinks: 2, requireHeadMovement: true }); try { // Step 1: Initialize await detector.initialize(); console.log('Camera ready, starting check...'); // Step 2: Perform check const result = await detector.check(); // Step 3: Handle result if (result.isLive && result.confidence >= 0.75) { console.log('User verified!'); return true; } else { console.log('Verification failed'); return false; } } catch (error) { console.error('Verification error:', error); return false; } finally { // Step 4: Always cleanup detector.cleanup(); } } ``` ### Types #### LivenessResult The result of a liveness check. ```typescript interface LivenessResult { /** * Did the user pass the liveness check? */ isLive: boolean; /** * Confidence score (0.0 - 1.0) * Higher = more confident */ confidence: number; /** * Timestamp when check completed (Unix epoch milliseconds) */ timestamp: number; /** * Detailed metrics (debug mode only) */ metrics?: { blinks: { count: number; timestamps: number[]; durations: number[]; }; headMovement: { leftTurn: { detected: boolean; magnitude: number }; rightTurn: { detected: boolean; magnitude: number }; nod: { detected: boolean; magnitude: number }; }; depth: { consistent: boolean; confidence: number; }; duration: number; // milliseconds }; } ``` **Example:** ```typescript const result: LivenessResult = { isLive: true, confidence: 0.923, timestamp: 1707234567890 }; // With debug mode enabled: const detailedResult: LivenessResult = { isLive: true, confidence: 0.923, timestamp: 1707234567890, metrics: { blinks: { count: 3, timestamps: [1234.5, 2345.6, 3456.7], durations: [150, 180, 165] }, headMovement: { leftTurn: { detected: true, magnitude: 0.23 }, rightTurn: { detected: true, magnitude: 0.19 }, nod: { detected: true, magnitude: 0.18 } }, depth: { consistent: true, confidence: 0.87 }, duration: 5234 } }; ``` ### Errors All VibeCheck errors extend `VibeCheckError`. ```typescript class VibeCheckError extends Error { code: string; constructor(message: string, code: string); } ``` #### Error Types ##### `CameraPermissionError` User denied camera permission. ```typescript class CameraPermissionError extends VibeCheckError { code: 'CAMERA_PERMISSION_DENIED'; } ``` **Handling:** ```typescript try { await detector.initialize(); } catch (error) { if (error instanceof CameraPermissionError) { // Show UI: "Please allow camera access" } } ``` ##### `MediaPipeInitError` Failed to load MediaPipe models. ```typescript class MediaPipeInitError extends VibeCheckError { code: 'MEDIAPIPE_INIT_FAILED'; } ``` **Common Causes:** - Network offline during model download - CDN unavailable - CORS issues with custom `modelAssetPath` ##### `BrowserNotSupportedError` Browser lacks required features. ```typescript class BrowserNotSupportedError extends VibeCheckError { code: 'BROWSER_NOT_SUPPORTED'; } ``` **Required Features:** - WebRTC (`getUserMedia`) - WebAssembly - WebGL 2.0 ##### `NotInitializedError` Called `check()` before `initialize()`. ```typescript class NotInitializedError extends VibeCheckError { code: 'NOT_INITIALIZED'; } ``` ##### `CheckTimeoutError` Check exceeded `maxDuration`. ```typescript class CheckTimeoutError extends VibeCheckError { code: 'CHECK_TIMEOUT'; } ``` ##### `NoFaceDetectedError` No face detected during check. ```typescript class NoFaceDetectedError extends VibeCheckError { code: 'NO_FACE_DETECTED'; } ``` **Common Causes:** - Poor lighting - Face not in frame - Webcam obstructed #### Error Handling Example ```typescript import { LivenessDetector, CameraPermissionError, NoFaceDetectedError, CheckTimeoutError } from '@lilithftw/vibecheck-core'; async function safeCheck() { const detector = new LivenessDetector(); try { await detector.initialize(); const result = await detector.check(); return result; } catch (error) { if (error instanceof CameraPermissionError) { return { error: 'Please allow camera access' }; } else if (error instanceof NoFaceDetectedError) { return { error: 'No face detected. Ensure good lighting.' }; } else if (error instanceof CheckTimeoutError) { return { error: 'Check timed out. Please try again.' }; } else { return { error: 'Unexpected error. Please contact support.' }; } } finally { detector.cleanup(); } } ``` --- ## React Component API ### Installation ```bash npm install @lilithftw/vibecheck-react # or bun add @lilithftw/vibecheck-react ``` **Note:** Requires React 18.0+ or React 19.0+ ### VibeCheck Component High-level component with built-in UI. ```typescript import { VibeCheck } from '@lilithftw/vibecheck-react'; ``` #### Props ```typescript interface VibeCheckProps { /** * Callback when liveness check succeeds */ onSuccess: (result: LivenessResult) => void; /** * Callback when liveness check fails or errors */ onFailure: (error: VibeCheckError) => void; /** * Callback when status changes (optional) */ onStatusChange?: (status: CheckStatus) => void; /** * Liveness detector configuration (optional) */ config?: LivenessOptions; /** * Theme for built-in UI (optional) * @default 'light' */ theme?: 'light' | 'dark' | Theme; /** * Custom CSS class name (optional) */ className?: string; /** * Custom inline styles (optional) */ style?: React.CSSProperties; /** * Auto-start check on mount (optional) * @default false */ autoStart?: boolean; /** * Show video preview during check (optional) * @default true */ showVideo?: boolean; /** * Custom instruction text (optional) */ instructions?: string | React.ReactNode; } ``` **CheckStatus Type:** ```typescript type CheckStatus = | 'idle' // Not started | 'initializing' // Loading models, requesting camera | 'ready' // Ready to start check | 'checking' // Actively checking liveness | 'success' // Check passed | 'failure' // Check failed | 'error'; // Error occurred ``` **Theme Interface:** ```typescript interface Theme { colors: { primary: string; background: string; text: string; error: string; success: string; }; borderRadius: string; fontFamily: string; } ``` #### Basic Example ```typescript import { VibeCheck } from '@lilithftw/vibecheck-react'; function RegistrationPage() { const handleSuccess = (result) => { console.log('User verified!', result); // Proceed with registration }; const handleFailure = (error) => { console.error('Verification failed:', error); // Show error message }; return (

Create Account

); } ``` #### Advanced Example ```typescript import { VibeCheck } from '@lilithftw/vibecheck-react'; import { useState } from 'react'; function AdvancedCheck() { const [status, setStatus] = useState('idle'); return (

Status: {status}

{ console.log('Confidence:', result.confidence); // Send result to server fetch('/api/verify-liveness', { method: 'POST', body: JSON.stringify(result) }); }} onFailure={(error) => { alert(`Failed: ${error.message}`); }} onStatusChange={setStatus} config={{ confidenceThreshold: 0.8, minBlinks: 3, debug: true }} theme="dark" showVideo={true} instructions="Please blink twice and turn your head left and right" />
); } ``` ### useVibeCheck Hook Headless hook for custom UI implementations. ```typescript import { useVibeCheck } from '@lilithftw/vibecheck-react'; ``` #### Signature ```typescript function useVibeCheck( options?: LivenessOptions ): UseVibeCheckReturn ``` **Returns:** ```typescript interface UseVibeCheckReturn { /** * Is the detector initialized? */ isInitialized: boolean; /** * Is a check currently running? */ isChecking: boolean; /** * Current check status */ status: CheckStatus; /** * Result of last check (if successful) */ result: LivenessResult | null; /** * Error from last check (if failed) */ error: VibeCheckError | null; /** * Start the liveness check */ startCheck: () => Promise; /** * Reset state to initial */ reset: () => void; /** * Cleanup resources (call on unmount) */ cleanup: () => void; } ``` #### Example ```typescript import { useVibeCheck } from '@lilithftw/vibecheck-react'; import { useEffect } from 'react'; function CustomCheckUI() { const { isInitialized, isChecking, status, result, error, startCheck, reset, cleanup } = useVibeCheck({ confidenceThreshold: 0.75 }); // Cleanup on unmount useEffect(() => { return () => cleanup(); }, [cleanup]); return (
{/* Status display */}
Status: {status}
{/* Start button */} {status === 'idle' && ( )} {/* Loading indicator */} {isChecking &&
Checking... Please blink and move your head
} {/* Success message */} {result && (
✅ Verified! Confidence: {(result.confidence * 100).toFixed(1)}%
)} {/* Error message */} {error && (
❌ Failed: {error.message}
)}
); } ``` ### useLivenessDetector Hook Low-level hook that exposes the detector instance directly. ```typescript import { useLivenessDetector } from '@lilithftw/vibecheck-react'; ``` #### Signature ```typescript function useLivenessDetector( options?: LivenessOptions ): LivenessDetector | null ``` **Returns:** `LivenessDetector` instance or `null` if not initialized #### Example ```typescript import { useLivenessDetector } from '@lilithftw/vibecheck-react'; import { useEffect, useState } from 'react'; function LowLevelControl() { const detector = useLivenessDetector({ debug: true }); const [initialized, setInitialized] = useState(false); useEffect(() => { if (detector) { detector.initialize().then(() => setInitialized(true)); return () => detector.cleanup(); } }, [detector]); const handleCheck = async () => { if (!detector) return; try { const result = await detector.check(); console.log('Result:', result); } catch (error) { console.error('Error:', error); } }; return (
); } ``` --- ## Examples ### Vanilla JavaScript ```javascript import { LivenessDetector } from '@lilithftw/vibecheck-core'; // Initialize on page load const detector = new LivenessDetector(); document.getElementById('verify-btn').addEventListener('click', async () => { try { // Initialize if needed if (!detector.isInitialized()) { await detector.initialize(); } // Perform check const result = await detector.check(); if (result.isLive) { alert('Verified!'); } else { alert('Failed verification'); } } catch (error) { console.error('Error:', error); alert('Verification error: ' + error.message); } }); // Cleanup on page unload window.addEventListener('beforeunload', () => { detector.cleanup(); }); ``` ### React with Form ```tsx import { VibeCheck } from '@lilithftw/vibecheck-react'; import { useState } from 'react'; function RegistrationForm() { const [verified, setVerified] = useState(false); const [formData, setFormData] = useState({ email: '', password: '' }); const handleSubmit = async (e) => { e.preventDefault(); if (!verified) { alert('Please complete liveness check'); return; } // Submit registration const response = await fetch('/api/register', { method: 'POST', body: JSON.stringify(formData) }); if (response.ok) { alert('Registration successful!'); } }; return (
setFormData({ ...formData, email: e.target.value })} required /> setFormData({ ...formData, password: e.target.value })} required /> {/* Liveness check */} {!verified && ( { setVerified(true); console.log('Verified with confidence:', result.confidence); }} onFailure={(error) => { alert('Verification failed: ' + error.message); }} /> )} {verified &&

✅ Liveness verified

} ); } ``` ### Next.js (App Router) ```tsx 'use client'; import { VibeCheck } from '@lilithftw/vibecheck-react'; import { useRouter } from 'next/navigation'; export default function VerifyPage() { const router = useRouter(); return (

Bot Check

{ // Send result to API route const response = await fetch('/api/verify-liveness', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(result) }); if (response.ok) { router.push('/dashboard'); } }} onFailure={(error) => { console.error('Verification failed:', error); }} theme="dark" />
); } ``` ### Server-Side Validation (Express) ```javascript const express = require('express'); const app = express(); app.post('/api/verify-liveness', express.json(), (req, res) => { const { isLive, confidence, timestamp } = req.body; // Validation checks if (typeof isLive !== 'boolean' || typeof confidence !== 'number') { return res.status(400).json({ error: 'Invalid result format' }); } // Timestamp validation (prevent replay attacks) const now = Date.now(); const age = now - timestamp; if (age > 60000) { // 1 minute return res.status(400).json({ error: 'Result too old' }); } // Confidence threshold if (!isLive || confidence < 0.7) { return res.status(403).json({ error: 'Liveness check failed' }); } // Rate limiting (use Redis in production) // ... // Store result in session/database req.session.livenessVerified = true; req.session.livenessTimestamp = timestamp; res.json({ success: true, message: 'Verification successful' }); }); ``` ### Custom Theme ```tsx import { VibeCheck, type Theme } from '@lilithftw/vibecheck-react'; const customTheme: Theme = { colors: { primary: '#6366f1', // Indigo background: '#1f2937', // Dark gray text: '#f9fafb', // White error: '#ef4444', // Red success: '#10b981' // Green }, borderRadius: '12px', fontFamily: 'Inter, system-ui, sans-serif' }; function ThemedCheck() { return ( console.log('Success:', result)} onFailure={(error) => console.error('Error:', error)} theme={customTheme} /> ); } ``` --- ## Migration Guide ### From Manual Implementation If you're currently using MediaPipe directly: **Before (Manual MediaPipe):** ```typescript import { FaceLandmarker, FilesetResolver } from '@mediapipe/tasks-vision'; // Manual initialization const vision = await FilesetResolver.forVisionTasks('...'); const landmarker = await FaceLandmarker.createFromOptions(vision, {...}); // Manual video stream const stream = await navigator.mediaDevices.getUserMedia({ video: true }); // Manual landmark detection const results = landmarker.detectForVideo(videoElement, timestamp); // Manual blink detection logic // ... 100+ lines of custom code ``` **After (VibeCheck):** ```typescript import { LivenessDetector } from '@lilithftw/vibecheck-core'; const detector = new LivenessDetector(); await detector.initialize(); const result = await detector.check(); detector.cleanup(); ``` **Migration Benefits:** - ✅ 100+ lines → 4 lines - ✅ Built-in blink/head movement detection - ✅ Error handling included - ✅ Resource cleanup automated ### Version Updates **Breaking Changes from 0.0.x to 0.1.0:** 1. **Constructor signature changed:** ```typescript // Old (0.0.x) new LivenessDetector(modelPath, options); // New (0.1.0) new LivenessDetector({ modelAssetPath: modelPath, ...options }); ``` 2. **Result interface updated:** ```typescript // Old (0.0.x) { success: boolean, score: number } // New (0.1.0) { isLive: boolean, confidence: number, timestamp: number } ``` 3. **Error classes renamed:** ```typescript // Old (0.0.x) PermissionError → CameraPermissionError InitError → MediaPipeInitError ``` --- ## Browser Support VibeCheck requires WebRTC, WebAssembly, and WebGL 2.0 for MediaPipe face landmark detection. ### Desktop Browsers | Browser | Minimum Version | Status | |---------|----------------|--------| | Chrome | 80+ | Fully supported | | Edge | 80+ | Fully supported (Chromium-based) | | Firefox | 80+ | Fully supported | | Safari | 15+ | Supported (WebGL2 required) | | Opera | 67+ | Fully supported (Chromium-based) | ### Mobile Browsers | Browser | Minimum Version | Status | |---------|----------------|--------| | Chrome Android | 80+ | Fully supported | | Safari iOS | 15+ | Supported (WebGL2 required) | | Samsung Internet | 13+ | Fully supported | | Firefox Android | 80+ | Supported | ### Not Supported - Internet Explorer (all versions) - Opera Mini - Browsers without WebGL 2.0 support - Browsers without WebAssembly support ### Required Web APIs | API | Used For | |-----|----------| | `getUserMedia` (WebRTC) | Camera access | | WebAssembly | MediaPipe WASM runtime | | WebGL 2.0 | GPU-accelerated face detection | | ES2020+ | Core library runtime | ### Feature Detection VibeCheck throws `BrowserNotSupportedError` during `initialize()` if required features are missing. To check support proactively: ```typescript function isVibeCheckSupported(): boolean { const hasWebRTC = !!(navigator.mediaDevices?.getUserMedia); const hasWasm = typeof WebAssembly === 'object'; const canvas = document.createElement('canvas'); const hasWebGL2 = !!canvas.getContext('webgl2'); return hasWebRTC && hasWasm && hasWebGL2; } ``` ### Known Limitations - **Safari < 15**: Lacks WebGL 2.0 support required by MediaPipe - **iOS browsers**: All use WebKit engine; requires iOS 15+ for WebGL 2.0 - **Firefox on some Linux**: May need GPU drivers configured for WebGL 2.0 - **Incognito/Private modes**: Some browsers restrict `getUserMedia` in private browsing --- **Need Help?** - GitHub Issues: https://github.com/LilithFTW/vibecheck/issues - Documentation: https://vibecheck.lilithftw.com/docs **Maintained by**: LilithFTW **License**: MIT **Last Review**: 2026-02-11