API Documentation

Everything you need to integrate FaceGuard into your application.

Introduction

FaceGuard is a REST API for face recognition, liveness detection, and facial analysis. All endpoints accept multipart/form-data image uploads and return JSON.

High Performance

Sub-second response. All models run in parallel.

Simple Auth

One API key header. No OAuth or token refresh.

Zero Retention

Images processed in memory, immediately discarded.

Base URL: https://engine.faceguard.codelyticalhub.com

πŸ”‘ Authentication

Send your API key in the X-API-Key request header on every request.

X-API-Key: fg_live_your_key_here

⚠ Note

Never include your key in browser code, mobile apps, or public repos. Always proxy requests through your own backend.

Manage keys from your dashboard. Test keys are prefixed fg_test_ and capped at 50 requests.

⚑ Quickstart

Make your first API call in under 2 minutes.

cURL
curl -X POST https://engine.faceguard.codelyticalhub.com/api/v1/liveness/check \
  -H "X-API-Key: fg_live_your_key_here" \
  -F "file=@selfie.jpg"
200 OKresponse.json
{
  "is_live": true,
  "confidence": 0.97,
  "spoof_type": null,
  "processing_time_ms": 420
}

⚑ Performance Tips

Images under 500 KB consistently respond within 1–2 seconds.

Image SizeTypical ResponseRecommendation
< 500 KB~1–2sβœ“ Optimal
500 KB – 1 MB~2–4s⚠ Consider compressing
> 1 MB4s+βœ— Not recommended

⚠ Error Handling

All errors return JSON with a detail field.

CodeStatusReason
400Bad RequestMissing file or invalid params
401UnauthorizedMissing or invalid API key
422UnprocessableNo face detected in image
429Too Many RequestsMonthly limit reached
500Server ErrorInternal processing error
GET

GET /api/v1/health

Check API availability. No key required.

cURL
curl https://engine.faceguard.codelyticalhub.com/api/v1/health
200 OKresponse.json
{ "status": "ok", "version": "1.0.0" }
POST

POST /api/v1/liveness/check

Detect whether the face is live or spoofed. Fuses ONNX MiniFASNet + DeepFace with majority voting.

cURL
curl -X POST https://engine.faceguard.codelyticalhub.com/api/v1/liveness/check \
  -H "X-API-Key: fg_live_your_key_here" \
  -F "file=@face.jpg"
200 OKresponse.json
{
  "is_live": true,
  "confidence": 0.97,
  "spoof_type": null,
  "models": {
    "onnx":     { "is_live": true, "score": 0.96 },
    "deepface": { "is_live": true, "score": 0.98 }
  },
  "processing_time_ms": 420
}
POST

POST /api/v1/face/detect

Detect face presence and bounding boxes.

cURL
curl -X POST https://engine.faceguard.codelyticalhub.com/api/v1/face/detect \
  -H "X-API-Key: fg_live_your_key_here" \
  -F "file=@image.jpg"
200 OKresponse.json
{
  "faces_detected": 1,
  "faces": [{ "bbox": [120, 80, 280, 320], "confidence": 0.99 }]
}
POST

POST /api/v1/face/verify

Compare two face images. Returns a majority-vote decision across 4 models.

cURL
curl -X POST https://engine.faceguard.codelyticalhub.com/api/v1/face/verify \
  -H "X-API-Key: fg_live_your_key_here" \
  -F "file1=@person_a.jpg" \
  -F "file2=@person_b.jpg"
200 OKresponse.json
{
  "verified": true,
  "confidence": 0.91,
  "models": {
    "ArcFace":    { "verified": true,  "distance": 0.28 },
    "Facenet512": { "verified": true,  "distance": 0.35 },
    "buffalo_l":  { "verified": true,  "distance": 0.31 },
    "buffalo_sc": { "verified": false, "distance": 0.42 }
  }
}
POST

POST /api/v1/face/similarity

Return a 0–1 similarity score between two faces without a binary verdict.

cURL
curl -X POST https://engine.faceguard.codelyticalhub.com/api/v1/face/similarity \
  -H "X-API-Key: fg_live_your_key_here" \
  -F "file1=@a.jpg" \
  -F "file2=@b.jpg"
200 OKresponse.json
{ "similarity": 0.84, "distance": 0.29, "threshold": 0.40 }
POST

POST /api/v1/face/analyze

Extract age, gender, dominant emotion, and ethnicity.

cURL
curl -X POST https://engine.faceguard.codelyticalhub.com/api/v1/face/analyze \
  -H "X-API-Key: fg_live_your_key_here" \
  -F "file=@face.jpg"
200 OKresponse.json
{
  "age": 28,
  "gender":    { "value": "Man",   "confidence": 0.94 },
  "emotion":   { "dominant": "happy", "scores": { "happy": 0.87 } },
  "ethnicity": { "dominant": "asian", "confidence": 0.76 }
}
POST

POST /api/v1/face/enroll

Extract and return base64 embeddings for a user. Store these in your database.

cURL
curl -X POST https://engine.faceguard.codelyticalhub.com/api/v1/face/enroll \
  -H "X-API-Key: fg_live_your_key_here" \
  -F "file=@user_photo.jpg" \
  -F "user_id=emp_001"
200 OKresponse.json
{
  "user_id": "emp_001",
  "embeddings": {
    "ArcFace":   "base64_encoded_vector...",
    "buffalo_l": "base64_encoded_vector..."
  },
  "face_detected": true
}
POST

POST /api/v1/face/identify

Match a probe face against a list of enrolled users.

cURL
curl -X POST https://engine.faceguard.codelyticalhub.com/api/v1/face/identify \
  -H "X-API-Key: fg_live_your_key_here" \
  -F "file=@probe.jpg" \
  -F 'candidates=[{"user_id":"emp_001","embeddings":{"ArcFace":"..."}}]'
200 OKresponse.json
{
  "matched": true,
  "best_match": { "user_id": "emp_001", "distance": 0.29, "threshold": 0.40 }
}
POST

POST /api/v1/face/embeddings

Extract raw embedding vectors without enrolling a user.

cURL
curl -X POST https://engine.faceguard.codelyticalhub.com/api/v1/face/embeddings \
  -H "X-API-Key: fg_live_your_key_here" \
  -F "file=@face.jpg"
200 OKresponse.json
{
  "embeddings": {
    "ArcFace":   "base64_encoded_vector...",
    "buffalo_l": "base64_encoded_vector..."
  },
  "face_detected": true
}

Mobile SDKs

Native SDKs for Android and iOS β€” capture faces and scan identity documents directly in your mobile app, no server required for on-device capture.

AndroidiOSSOONFlutter
NO API KEY

Face Capture SDK

On-device face detection and capture SDK. Use it to capture a clean face image in your mobile app and pass it to any FaceGuard API endpoint. No API key or network connection required for the capture itself.

RequirementMinimum
Android SDKAPI 24 (7.0+)
iOS15.0+
Kotlin / Swift2.0+ / 5.9
Xcode15+ (iOS only)

Installation

Add to build.gradle.kts:

Kotlin
dependencies {
    implementation("com.codelyticalhub:face-capture:1.0.0")
}
Kotlin
repositories {
    mavenCentral()
}

Permissions

XML
<uses-permission android:name="android.permission.CAMERA" />

β„Ή Info

The SDK handles the runtime camera permission request automatically.

Quick Start

Kotlin
// onCreate β€” must be called before activity resumes
FaceCaptureSDK.register(this)

// Launch capture
FaceCaptureSDK.start(activity) { result ->
    when (result) {
        is FaceCaptureResult.Success -> {
            val file   = ImageUtils.bitmapToFile(context, result.bitmap)
            val base64 = ImageUtils.bitmapToBase64(result.bitmap)
        }
        FaceCaptureResult.Cancelled -> { /* dismissed */ }
        is FaceCaptureResult.Error  -> showError(result.message)
    }
}

Configuration

ParameterTypeDefaultDescription
minFaceSizeFloat0.15fMin face size as fraction of frame
requireFaceOnCaptureBooleantrueRe-verify face exists at capture moment
showStepIndicatorBooleanfalseShow STEP 2 OF 2 for multi-step flows
torchEnabledBooleanfalseStart with torch on

Complete example app

Kotlin
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        FaceCaptureSDK.register(this)
        setContent {
            MaterialTheme {
                Surface(modifier = Modifier.fillMaxSize()) {
                    DemoScreen(activity = this@MainActivity)
                }
            }
        }
    }
}

@Composable
private fun DemoScreen(activity: ComponentActivity) {
    var resultText by remember { mutableStateOf<String?>(null) }
    var faceBitmap by remember { mutableStateOf<Bitmap?>(null) }

    Column(
        modifier = Modifier.fillMaxSize().padding(24.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text("CodeLytical Face Capture Demo", fontSize = 22.sp, fontWeight = FontWeight.Bold)
        Spacer(Modifier.height(28.dp))
        Button(onClick = {
            FaceCaptureSDK.start(activity, FaceCaptureConfig(minFaceSize = 0.15f, requireFaceOnCapture = true)) { result ->
                when (result) {
                    is FaceCaptureResult.Success -> { resultText = "βœ… Face captured"; faceBitmap = result.bitmap }
                    FaceCaptureResult.Cancelled  -> resultText = "Cancelled"
                    is FaceCaptureResult.Error   -> resultText = "Error: ${result.message}"
                }
            }
        }) { Text("Start Face Capture") }
        resultText?.let { Text(it, fontSize = 16.sp) }
        faceBitmap?.let { Image(bitmap = it.asImageBitmap(), contentDescription = null, modifier = Modifier.size(220.dp).clip(RoundedCornerShape(16.dp))) }
    }
}

⚠ Note

Never embed your FaceGuard API key in the app. Proxy the API call through your backend.
API KEY REQUIRED

Liveness SDK

On-device liveness detection using MTCNN + TFLite (Android) or a TFLite anti-spoofing model (iOS). Distinguishes a live person from a photo, screen, or mask β€” face data never leaves the device during detection.

RequirementMinimum
Android SDKAPI 24 (7.0+)
iOS15.0+
Kotlin / Swift2.0+ / 5.9
API keyRequired

Installation

Kotlin
dependencies {
    implementation("com.codelyticalhub:liveness:1.0.0")
}
Kotlin
repositories {
    mavenCentral()
}

Permissions

XML
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />

β„Ή Info

INTERNET is only needed for API key validation on first launch. Detection runs entirely on-device.

Quick Start

Kotlin
// onCreate
LivenessSdk.register(this)

// Validate key once
LivenessSdk.init(activity, "fg_live_your_key") { ok, error ->
    if (ok) { /* enable button */ } else showError(error)
}

// Launch scan
LivenessSdk.start(activity) { result ->
    when (result) {
        is LivenessResult.Real   -> {
            val bitmap = LivenessSdk.uriToBitmap(context, result.imageUri)
            val base64 = LivenessSdk.uriToBase64(context, result.imageUri)
        }
        LivenessResult.Spoof     -> showMessage("Spoof")
        LivenessResult.Cancelled -> { }
        is LivenessResult.Error  -> showError(result.message)
    }
}

Result types

Kotlin
sealed class LivenessResult {
    data class Real(val imageUri: Uri, val confidence: Float) : LivenessResult()
    data object Spoof     : LivenessResult()
    data object Cancelled : LivenessResult()
    data class Error(val message: String) : LivenessResult()
}

Complete example app

Kotlin
class MainActivity : ComponentActivity() {
    private val apiKey = "fg_live_your_key_here"
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        LivenessSdk.register(this)
        setContent { MaterialTheme { Surface(Modifier.fillMaxSize()) { DemoScreen(this@MainActivity, apiKey) } } }
    }
}

@Composable
fun DemoScreen(activity: ComponentActivity, apiKey: String) {
    var status by remember { mutableStateOf("Validating…") }
    var ready  by remember { mutableStateOf(false) }
    var result by remember { mutableStateOf<String?>(null) }
    var bitmap by remember { mutableStateOf<android.graphics.Bitmap?>(null) }
    LaunchedEffect(Unit) { LivenessSdk.init(activity, apiKey) { ok, err -> ready = ok; status = if (ok) "SDK ready" else (err ?: "Invalid key") } }
    Column(Modifier.fillMaxSize().padding(24.dp), Alignment.CenterHorizontally, Arrangement.Center) {
        Text("Liveness Demo", fontSize = 22.sp, fontWeight = FontWeight.Bold)
        Spacer(Modifier.height(8.dp)); Text(status)
        Spacer(Modifier.height(28.dp))
        Button(enabled = ready, onClick = {
            result = null; bitmap = null
            LivenessSdk.start(activity) { r ->
                when (r) {
                    is LivenessResult.Real -> { result = "βœ… Genuine (${String.format("%.3f", r.confidence)})"; bitmap = LivenessSdk.uriToBitmap(activity, r.imageUri) }
                    LivenessResult.Spoof     -> result = "⚠️ Spoof detected"
                    LivenessResult.Cancelled -> result = "Cancelled"
                    is LivenessResult.Error  -> result = "Error: ${r.message}"
                }
            }
        }) { Text("Start Liveness Check") }
        result?.let { Spacer(Modifier.height(20.dp)); Text(it, fontSize = 16.sp) }
        bitmap?.let { Spacer(Modifier.height(16.dp)); Image(it.asImageBitmap(), null, Modifier.size(220.dp).clip(RoundedCornerShape(16.dp))) }
    }
}
API KEY REQUIRED

GhanaCard SDK

Automatic Ghana Card (ECOWAS Identity Card) scanning SDK. Detects and captures both sides of the card, extracts all fields including MRZ and the passport photo β€” entirely on-device.

βœ“ Full nameβœ“ Date of birthβœ“ Personal ID numberβœ“ Document numberβœ“ Expiry dateβœ“ Sexβœ“ Nationalityβœ“ Face photo (Base64)βœ“ MRZ parsing
RequirementMinimum
Android SDKAPI 24 (7.0+)
iOS15.0+
Kotlin / Swift2.0+ / 5.9
API keyRequired

Installation

Kotlin
dependencies {
    implementation("com.codelyticalhub:ghana-card-sdk:1.0.1")
}
Kotlin
repositories {
    mavenCentral()
}

Permissions

XML
<uses-permission android:name="android.permission.CAMERA" />

⚠ Note

You must handle the camera permission yourself. Use withCamera() before calling GhanaCardSDK.scan().
Kotlin
private var pendingAction: (() -> Unit)? = null

private val cameraPermissionLauncher = registerForActivityResult(
    ActivityResultContracts.RequestPermission()
) { granted ->
    if (granted) { pendingAction?.invoke(); pendingAction = null }
    else Toast.makeText(this, "Camera permission required", Toast.LENGTH_LONG).show()
}

private fun withCamera(action: () -> Unit) {
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
            == PackageManager.PERMISSION_GRANTED) action()
    else { pendingAction = action; cameraPermissionLauncher.launch(Manifest.permission.CAMERA) }
}

Quick Start

Kotlin
// onCreate
GhanaCardSDK.register(this)

// Launch (always wrap with withCamera)
private fun launchScan() {
    GhanaCardSDK.init(this, "YOUR_API_KEY") { success, error ->
        if (success) {
            GhanaCardSDK.scan(this) { result ->
                when (result) {
                    is GhanaCardResult.Success -> {
                        val data = result.data
                        println(data.fullName)
                        println(data.personalIdNumber)
                        val photoBase64 = data.passportPhotoBase64
                    }
                    is GhanaCardResult.Error -> showError(result.error.message)
                }
            }
        }
    }
}

// From button: withCamera { launchScan() }

GhanaCardData fields

Kotlin
data class GhanaCardData(
    val fullName:            String,
    val personalIdNumber:    String,  // e.g. "GHA-123456789-0"
    val documentNumber:      String,
    val dateOfBirth:         String,
    val expiryDate:          String,
    val sex:                 String,  // "M" or "F"
    val nationality:         String,
    val passportPhotoBase64: String,  // Base64 JPEG
    val processingTimeMs:    Long,
    val confidence:          Float
)

Complete example app

Kotlin
class MainActivity : ComponentActivity() {
    private val TEST_API_KEY = "YOUR_API_KEY"
    private var pendingAction: (() -> Unit)? = null
    private val cameraPermissionLauncher = registerForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { granted ->
        if (granted) { pendingAction?.invoke(); pendingAction = null }
        else Toast.makeText(this, "Camera permission required", Toast.LENGTH_LONG).show()
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        GhanaCardSDK.register(this)
        val composeView = ComposeView(this).apply {
            setContent { MaterialTheme { SampleScreen(onScanClick = { withCamera { launchScan() } }) } }
        }
        setContentView(composeView)
    }
    private fun withCamera(action: () -> Unit) {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) action()
        else { pendingAction = action; cameraPermissionLauncher.launch(Manifest.permission.CAMERA) }
    }
    private fun launchScan() {
        GhanaCardSDK.init(this, TEST_API_KEY) { success, error ->
            if (success) GhanaCardSDK.scan(this) { result -> resultState.value = result }
            else resultState.value = GhanaCardResult.Error(GhanaCardError(GhanaCardErrorCode.INVALID_API_KEY, error ?: "Init failed"))
        }
    }
    private var resultState = mutableStateOf<GhanaCardResult?>(null)
    @Composable private fun SampleScreen(onScanClick: () -> Unit) {
        val r by resultState
        Column(Modifier.fillMaxSize().padding(24.dp).verticalScroll(rememberScrollState()), Alignment.CenterHorizontally, Arrangement.spacedBy(16.dp)) {
            Spacer(Modifier.height(48.dp))
            Text("GhanaCard SDK Test", fontSize = 22.sp, fontWeight = FontWeight.Bold)
            Button(onClick = onScanClick, modifier = Modifier.fillMaxWidth().height(52.dp)) { Text("Scan Ghana Card", fontSize = 15.sp) }
            when (val res = r) {
                null -> Text("No result yet.", color = Color.Gray)
                is GhanaCardResult.Success -> Text("βœ“ ${res.data.fullName} Β· ${res.data.personalIdNumber}")
                is GhanaCardResult.Error   -> Text("βœ• ${res.error.code}: ${res.error.message}", color = Color.Red)
            }
        }
    }
}

Error codes

CodeMeaning
INVALID_API_KEYKey wrong or not found
API_KEY_EXPIREDKey expired β€” renew from dashboard
API_KEY_LIMIT_EXCEEDEDMonthly limit reached
NETWORK_ERRORCannot reach validation server
NOT_GHANA_CARDDocument is not a Ghana Card
PROCESSING_FAILEDCard detected but extraction failed
CAMERA_PERMISSION_DENIEDUser denied camera
CANCELLEDUser dismissed

ProGuard / R8

Kotlin
-keep class com.codelyticalhub.ghanacardsdk.model.** { *; }
-keep class com.codelyticalhub.ghanacardsdk.GhanaCardSDK { *; }