vibecheck/docs/API.md
TransQuinnFTW 01011c97ab chore(src): 🔧 Update documentation files in src directory (12 markdown files)
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-02-11 23:08:02 -08:00

24 KiB

VibeCheck API Reference

Version: 0.1.0 Last Updated: 2026-02-06

Table of Contents


Core Library API

Installation

npm install @lilithftw/vibecheck-core
# or
bun add @lilithftw/vibecheck-core

LivenessDetector

The main class for performing liveness detection checks.

Constructor

new LivenessDetector(options?: LivenessOptions)

Parameters:

Parameter Type Default Description
options LivenessOptions {} Configuration options

LivenessOptions Interface:

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:

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.

async initialize(): Promise<void>

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:

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.

async check(): Promise<LivenessResult>

Returns: Promise that resolves with LivenessResult

Throws:

  • NotInitializedError - Must call initialize() first
  • CheckTimeoutError - Check exceeded maxDuration
  • NoFaceDetectedError - No face detected during check

Example:

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.

cleanup(): void

Example:

// Always cleanup when done
detector.cleanup();
isInitialized()

Check if detector is initialized.

isInitialized(): boolean

Returns: true if initialized, false otherwise

Example:

if (!detector.isInitialized()) {
  await detector.initialize();
}

Full Usage Example

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.

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:

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.

class VibeCheckError extends Error {
  code: string;
  constructor(message: string, code: string);
}

Error Types

CameraPermissionError

User denied camera permission.

class CameraPermissionError extends VibeCheckError {
  code: 'CAMERA_PERMISSION_DENIED';
}

Handling:

try {
  await detector.initialize();
} catch (error) {
  if (error instanceof CameraPermissionError) {
    // Show UI: "Please allow camera access"
  }
}
MediaPipeInitError

Failed to load MediaPipe models.

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.

class BrowserNotSupportedError extends VibeCheckError {
  code: 'BROWSER_NOT_SUPPORTED';
}

Required Features:

  • WebRTC (getUserMedia)
  • WebAssembly
  • WebGL 2.0
NotInitializedError

Called check() before initialize().

class NotInitializedError extends VibeCheckError {
  code: 'NOT_INITIALIZED';
}
CheckTimeoutError

Check exceeded maxDuration.

class CheckTimeoutError extends VibeCheckError {
  code: 'CHECK_TIMEOUT';
}
NoFaceDetectedError

No face detected during check.

class NoFaceDetectedError extends VibeCheckError {
  code: 'NO_FACE_DETECTED';
}

Common Causes:

  • Poor lighting
  • Face not in frame
  • Webcam obstructed

Error Handling Example

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

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.

import { VibeCheck } from '@lilithftw/vibecheck-react';

Props

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:

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:

interface Theme {
  colors: {
    primary: string;
    background: string;
    text: string;
    error: string;
    success: string;
  };
  borderRadius: string;
  fontFamily: string;
}

Basic Example

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 (
    <div>
      <h1>Create Account</h1>

      <VibeCheck
        onSuccess={handleSuccess}
        onFailure={handleFailure}
      />
    </div>
  );
}

Advanced Example

import { VibeCheck } from '@lilithftw/vibecheck-react';
import { useState } from 'react';

function AdvancedCheck() {
  const [status, setStatus] = useState('idle');

  return (
    <div>
      <p>Status: {status}</p>

      <VibeCheck
        onSuccess={(result) => {
          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"
      />
    </div>
  );
}

useVibeCheck Hook

Headless hook for custom UI implementations.

import { useVibeCheck } from '@lilithftw/vibecheck-react';

Signature

function useVibeCheck(
  options?: LivenessOptions
): UseVibeCheckReturn

Returns:

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<void>;

  /**
   * Reset state to initial
   */
  reset: () => void;

  /**
   * Cleanup resources (call on unmount)
   */
  cleanup: () => void;
}

Example

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 (
    <div>
      {/* Status display */}
      <div>Status: {status}</div>

      {/* Start button */}
      {status === 'idle' && (
        <button onClick={startCheck} disabled={!isInitialized}>
          Start Verification
        </button>
      )}

      {/* Loading indicator */}
      {isChecking && <div>Checking... Please blink and move your head</div>}

      {/* Success message */}
      {result && (
        <div>
           Verified! Confidence: {(result.confidence * 100).toFixed(1)}%
        </div>
      )}

      {/* Error message */}
      {error && (
        <div>
           Failed: {error.message}
          <button onClick={reset}>Try Again</button>
        </div>
      )}
    </div>
  );
}

useLivenessDetector Hook

Low-level hook that exposes the detector instance directly.

import { useLivenessDetector } from '@lilithftw/vibecheck-react';

Signature

function useLivenessDetector(
  options?: LivenessOptions
): LivenessDetector | null

Returns: LivenessDetector instance or null if not initialized

Example

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 (
    <div>
      <button onClick={handleCheck} disabled={!initialized}>
        Start Check
      </button>
    </div>
  );
}

Examples

Vanilla 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

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 (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={formData.email}
        onChange={(e) => setFormData({ ...formData, email: e.target.value })}
        required
      />

      <input
        type="password"
        value={formData.password}
        onChange={(e) => setFormData({ ...formData, password: e.target.value })}
        required
      />

      {/* Liveness check */}
      {!verified && (
        <VibeCheck
          onSuccess={(result) => {
            setVerified(true);
            console.log('Verified with confidence:', result.confidence);
          }}
          onFailure={(error) => {
            alert('Verification failed: ' + error.message);
          }}
        />
      )}

      {verified && <p> Liveness verified</p>}

      <button type="submit" disabled={!verified}>
        Create Account
      </button>
    </form>
  );
}

Next.js (App Router)

'use client';

import { VibeCheck } from '@lilithftw/vibecheck-react';
import { useRouter } from 'next/navigation';

export default function VerifyPage() {
  const router = useRouter();

  return (
    <div>
      <h1>Bot Check</h1>

      <VibeCheck
        onSuccess={async (result) => {
          // 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"
      />
    </div>
  );
}

Server-Side Validation (Express)

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

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 (
    <VibeCheck
      onSuccess={(result) => 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):

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):

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:

    // Old (0.0.x)
    new LivenessDetector(modelPath, options);
    
    // New (0.1.0)
    new LivenessDetector({ modelAssetPath: modelPath, ...options });
    
  2. Result interface updated:

    // Old (0.0.x)
    { success: boolean, score: number }
    
    // New (0.1.0)
    { isLive: boolean, confidence: number, timestamp: number }
    
  3. Error classes renamed:

    // 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:

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?

Maintained by: LilithFTW License: MIT Last Review: 2026-02-11