Face Only HTTPS · REST · TLS 1.3 JSON · Base64 Per-Transaction Billed

IDBIO Face Cloud API

IDBIO's managed cloud service for face matching and active/passive liveness detection. Header authentication, JSON over HTTPS.

Managed cloud service for face matching and active/passive liveness detection. Endpoints are region-specific (api-in.idbio.ai, api-eu.idbio.ai, …) and assigned at onboarding. Also available for on-premise deployment on request — full root access to target hosts is required for installation; contact sales for either path.

API Authentication

All authentication and request correlation travels in HTTP headers. Every request carries two required identifiers: X-IDBIO-API-Key for credentials and X-IDBIO-Txn-Id for transaction correlation. The request body carries only the biometric payload — keys and IDs never appear in the URL, query string, or body.

HeaderValueRequired
X-IDBIO-API-KeyAPI key issued at onboarding · idb_test_… sandbox  ·  idb_live_… productionRequired
X-IDBIO-Txn-IdUnique per request · 16–64 chars · [A-Za-z0-9_-] · UUID v4 recommended. Echoed in the response. Reusing an ID returns 409 DUPLICATE_TXN_ID.Required
Content-Typeapplication/jsonRequired
X-IDBIO-Client-VersionCapture-side SDK identifier, e.g. idbio-capture-android/2.4.1Recommended

Face Match

1:1 comparison of a probe face image against a reference. Returns a similarity score and a match decision against a configurable threshold. Each image carries an independent liveness_check flag — when enabled, single-frame passive liveness is run on that image and its result is included in the response. The lens_focus_distance and min_focus_distance fields (in diopters) are optional but recommended for best accuracy when liveness_check is true; they are ignored otherwise.

POST /v1/face/match

Request Headers

X-IDBIO-API-Key: idb_live_…
X-IDBIO-Txn-Id:  a3f1c8b24e6f4a5d9b8c7e0f1d2a3b4c
Content-Type:    application/json

Request Body

{
  "probe": {
    "image_data_b64":      "<base64-encoded image bytes>", // required
    "image_format":        "JPEG",                       // required · JPEG | PNG | BMP | WEBP
    "liveness_check":      true,                         // optional · default false
    "lens_focus_distance": 0.50,                         // recommended when liveness_check=true · diopters
    "min_focus_distance":  0.10                          // recommended when liveness_check=true · diopters
  },
  "reference": {
    "image_data_b64":      "<base64-encoded image bytes>",
    "image_format":        "JPEG",
    "liveness_check":      false                         // e.g. stored ID-document photo — skip liveness
  },
  "match_threshold":    0.75                              // optional · 0.0–1.0 · defaults to tenant-configured value
}
import requests, base64, uuid

def face_match(probe_path, ref_path, api_key, *, check_probe_liveness=True,
               lens_focus_distance=None, min_focus_distance=None):
    def b64(p): return base64.b64encode(open(p,"rb").read()).decode()
    probe = {
        "image_data_b64": b64(probe_path),
        "image_format":   "JPEG",
        "liveness_check": check_probe_liveness,
    }
    if check_probe_liveness:
        probe["lens_focus_distance"] = lens_focus_distance   # diopters, from camera
        probe["min_focus_distance"]  = min_focus_distance
    r = requests.post(
        "https://<endpoint>/v1/face/match",
        headers={
            "X-IDBIO-API-Key": api_key,
            "X-IDBIO-Txn-Id":  uuid.uuid4().hex,    # unique per request
            "Content-Type":    "application/json",
        },
        json={
            "probe":           probe,
            "reference":       {"image_data_b64": b64(ref_path),
                                  "image_format":   "JPEG",
                                  "liveness_check": False},
            "match_threshold": 0.75,
        },
        timeout=15,
    )
    r.raise_for_status()
    return r.json()
import java.net.http.*; import java.net.URI; import java.nio.file.*;
import java.util.UUID;

String b64(String path) throws Exception {
    return java.util.Base64.getEncoder().encodeToString(
        Files.readAllBytes(Path.of(path)));
}
String txnId = UUID.randomUUID().toString().replace("-", "");
String body = """
    {"probe":{"image_data_b64":"%s","image_format":"JPEG",
              "liveness_check":true,
              "lens_focus_distance":0.50,"min_focus_distance":0.10},
     "reference":{"image_data_b64":"%s","image_format":"JPEG",
                  "liveness_check":false},
     "match_threshold":0.75}""".formatted(b64(probePath), b64(refPath));

HttpRequest req = HttpRequest.newBuilder()
    .uri(URI.create("https://<endpoint>/v1/face/match"))
    .header("X-IDBIO-API-Key", apiKey)
    .header("X-IDBIO-Txn-Id",  txnId)
    .header("Content-Type",    "application/json")
    .timeout(java.time.Duration.ofSeconds(15))
    .POST(HttpRequest.BodyPublishers.ofString(body)).build();
var resp = HttpClient.newHttpClient().send(req,
    HttpResponse.BodyHandlers.ofString());
// resp.body() → JSON envelope (see Response Body below)
#include <curl/curl.h>
#include <nlohmann/json.hpp>
using json = nlohmann::json;

json payload = {
    {"probe", {
        {"image_data_b64",      base64_encode(probe_bytes)},
        {"image_format",        "JPEG"},
        {"liveness_check",      true},
        {"lens_focus_distance", 0.50},
        {"min_focus_distance",  0.10}
    }},
    {"reference", {
        {"image_data_b64", base64_encode(ref_bytes)},
        {"image_format",   "JPEG"},
        {"liveness_check", false}
    }},
    {"match_threshold", 0.75}
};
std::string body  = payload.dump();
std::string txnId = make_uuid_hex();   // unique per request
std::string txnHdr = "X-IDBIO-Txn-Id: " + txnId;

CURL* curl = curl_easy_init();
struct curl_slist* h = nullptr;
h = curl_slist_append(h, "Content-Type: application/json");
h = curl_slist_append(h, "X-IDBIO-API-Key: <api-key>");
h = curl_slist_append(h, txnHdr.c_str());
curl_easy_setopt(curl, CURLOPT_URL, "https://<endpoint>/v1/face/match");
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, h);
curl_easy_perform(curl);

Response Body · 200 OK

{
  "txn_id": "a3f1c8b24e6f4a5d9b8c7e0f1d2a3b4c",
  "match": {
    "decision": "MATCHED",                                 // MATCHED | NOT_MATCHED
    "score":    0.924                                       // 0.0–1.0
  },
  "liveness": {                                                // only present if any image had liveness_check=true
    "probe":     { "decision": "GENUINE", "spoof_score": 0.03 } // only the images that were checked appear
  },
  "processed_at":       "2026-05-09T12:34:56.789Z",            // ISO 8601 UTC
  "processing_time_ms": 312                                  // server-side latency, excludes network
}

Request Fields (body)

FieldTypeNotes
probe.image_data_b64stringRequired · base64 image · min 100×100 px · ≤ 4 MB decoded
probe.image_formatstringRequired · JPEG · PNG · BMP · WEBP
probe.liveness_checkbooleanOptional · default false · runs single-frame passive liveness
probe.lens_focus_distancefloatDiopters · optional · recommended when liveness_check = true
probe.min_focus_distancefloatDiopters · recommended when liveness_check = true
reference.*objectSame shape as probe · independent liveness_check
match_thresholdfloatOptional · 0.0–1.0 · tenant default if omitted

Response Fields (body)

FieldTypeNotes
txn_idstringEchoes the X-IDBIO-Txn-Id header
match.decisionstringMATCHED · NOT_MATCHED
match.scorefloat0.0–1.0 — higher = more similar
livenessobjectOmitted when neither image had liveness_check=true
liveness.probe.decisionstringGENUINE · SPOOF
liveness.probe.spoof_scorefloat0.0–1.0 · higher = more likely spoof
liveness.reference.*—Same shape as liveness.probe · only present when checked
processed_atstringISO 8601 UTC timestamp
processing_time_msintServer-side latency (excludes network)

Face Liveness

Anti-spoof verification on a face capture. Two modes are supported:

  • ·passive — single-frame texture & reflectance analysis. No user interaction. 1 frame.
  • ·strong — multi-frame analysis with optional camera-optics input. Defeats high-resolution print, replay, mask, and deepfake-on-screen attacks. 2 frames.

Each frame may optionally include camera optics metadata — lens_focus_distance and min_focus_distance (in diopters). These fields are optional — requests succeed without them, but providing them is recommended for best accuracy. For strong mode, capture frame 0 at near focus and frame 1 at far focus.

POST /v1/face/liveness

Request Headers

X-IDBIO-API-Key: idb_live_…
X-IDBIO-Txn-Id:  a3f1c8b24e6f4a5d9b8c7e0f1d2a3b4c
Content-Type:    application/json

Request Body — strong mode

{
  "liveness_mode": "strong",                                  // "passive" | "strong"
  "frames": [
    {
      "frame_index":    0,
      "image_data_b64": "<base64 of frame 0>",
      "liveness_metadata": {                                  // optional · recommended for best accuracy
        "lens_focus_distance": 0.50,                          // diopters · near focus
        "min_focus_distance":  0.10                           // diopters · device hardware constant
      }
    },
    {
      "frame_index":    1,                                     // required only when liveness_mode = "strong"
      "image_data_b64": "<base64 of frame 1>",
      "liveness_metadata": {
        "lens_focus_distance": 5.00,                          // far focus (Δ ≥ 2.0 from frame 0 when supplied)
        "min_focus_distance":  0.10
      }
    }
  ]
}
import requests, base64, uuid

def b64(p): return base64.b64encode(open(p,"rb").read()).decode()

def _headers(api_key):
    return {
        "X-IDBIO-API-Key": api_key,
        "X-IDBIO-Txn-Id":  uuid.uuid4().hex,    # unique per request
        "Content-Type":    "application/json",
    }

# ── Passive (single frame) ───────────────────────────────────────
def face_liveness_passive(frame_path, api_key, *, lens_focus_d, min_focus_d):
    r = requests.post(
        "https://<endpoint>/v1/face/liveness",
        headers=_headers(api_key),
        json={
            "liveness_mode": "passive",
            "frames": [{
                "frame_index":    0,
                "image_data_b64": b64(frame_path),
                "liveness_metadata": {
                    "lens_focus_distance": lens_focus_d,
                    "min_focus_distance":  min_focus_d,
                },
            }],
        },
        timeout=15,
    )
    return r.json()

# ── Strong (two frames at different focus distances) ────────────
def face_liveness_strong(near_path, far_path, api_key, *,
                         lens_d_near, lens_d_far, min_focus_d):
    def frame(idx, path, lens_d):
        return {
            "frame_index":    idx,
            "image_data_b64": b64(path),
            "liveness_metadata": {
                "lens_focus_distance": lens_d,
                "min_focus_distance":  min_focus_d,
            },
        }
    r = requests.post(
        "https://<endpoint>/v1/face/liveness",
        headers=_headers(api_key),
        json={
            "liveness_mode": "strong",
            "frames": [frame(0, near_path, lens_d_near),
                       frame(1, far_path,  lens_d_far)],
        },
        timeout=15,
    )
    return r.json()
import java.net.http.*; import java.net.URI; import java.nio.file.*;
import java.util.UUID;

String b64(String path) throws Exception {
    return java.util.Base64.getEncoder().encodeToString(
        Files.readAllBytes(Path.of(path)));
}
String txnId = UUID.randomUUID().toString().replace("-", "");

// Strong mode — two frames at different focus distances
String body = """
{
  "liveness_mode": "strong",
  "frames": [
    {"frame_index":0,"image_data_b64":"%s",
     "liveness_metadata":{"lens_focus_distance":0.50,"min_focus_distance":0.10}},
    {"frame_index":1,"image_data_b64":"%s",
     "liveness_metadata":{"lens_focus_distance":5.00,"min_focus_distance":0.10}}
  ]
}""".formatted(b64(nearPath), b64(farPath));

HttpRequest req = HttpRequest.newBuilder()
    .uri(URI.create("https://<endpoint>/v1/face/liveness"))
    .header("X-IDBIO-API-Key", apiKey)
    .header("X-IDBIO-Txn-Id",  txnId)
    .header("Content-Type",    "application/json")
    .timeout(java.time.Duration.ofSeconds(15))
    .POST(HttpRequest.BodyPublishers.ofString(body)).build();
var resp = HttpClient.newHttpClient().send(req,
    HttpResponse.BodyHandlers.ofString());
#include <curl/curl.h>
#include <nlohmann/json.hpp>
using json = nlohmann::json;

json payload = {
    {"liveness_mode", "strong"},
    {"frames", json::array({
        {
            {"frame_index",    0},
            {"image_data_b64", base64_encode(near_bytes)},
            {"liveness_metadata", {
                {"lens_focus_distance", 0.50},
                {"min_focus_distance",  0.10}
            }}
        },
        {
            {"frame_index",    1},
            {"image_data_b64", base64_encode(far_bytes)},
            {"liveness_metadata", {
                {"lens_focus_distance", 5.00},
                {"min_focus_distance",  0.10}
            }}
        }
    })}
};
std::string body  = payload.dump();
std::string txnId = make_uuid_hex();   // unique per request
std::string txnHdr = "X-IDBIO-Txn-Id: " + txnId;

CURL* curl = curl_easy_init();
struct curl_slist* h = nullptr;
h = curl_slist_append(h, "Content-Type: application/json");
h = curl_slist_append(h, "X-IDBIO-API-Key: <api-key>");
h = curl_slist_append(h, txnHdr.c_str());
curl_easy_setopt(curl, CURLOPT_URL, "https://<endpoint>/v1/face/liveness");
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, h);
curl_easy_perform(curl);

Response Body · 200 OK

{
  "txn_id":   "a3f1c8b24e6f4a5d9b8c7e0f1d2a3b4c",
  "decision": "GENUINE",                                // GENUINE | SPOOF · final fused decision
  "frames": [                                            // per-frame · 1 entry passive · 2 entries strong
    { "frame_index": 0, "spoof_score": 0.04 },         // 0.0–1.0 · higher = more likely spoof
    { "frame_index": 1, "spoof_score": 0.02 }
  ],
  "processed_at":       "2026-05-09T12:34:56.789Z",           // ISO 8601 UTC
  "processing_time_ms": 247                                 // server-side latency, excludes network
}

Request Fields (body)

FieldTypeNotes
liveness_modestringRequired · passive · strong
framesarrayRequired · 1 entry (passive) · 2 entries (strong)
frames[].frame_indexintRequired · 0-based · contiguous
frames[].image_data_b64stringRequired · base64 image · ≤ 4 MB decoded · format auto-detected (JPEG/PNG/BMP/WEBP)
frames[].liveness_metadataobjectOptional · recommended for best accuracy
frames[].liveness_metadata.lens_focus_distancefloatDiopters · per-frame · optional
frames[].liveness_metadata.min_focus_distancefloatDiopters · optional

Response Fields (body)

FieldTypeNotes
txn_idstringEchoes the X-IDBIO-Txn-Id header
decisionstringGENUINE · SPOOF · final fused decision across all frames
frames[]array1 entry (passive) · 2 entries (strong)
frames[].frame_indexint0-based · echoes the request index
frames[].spoof_scorefloat0.0–1.0 per-frame spoof score · higher = more likely spoof
processed_atstringISO 8601 UTC timestamp
processing_time_msintServer-side latency (excludes network)

Error Codes

All errors return a non-2xx HTTP status with a minimal JSON body echoing the txn_id and a structured error. HTTP status is the source of truth. Generate a new X-IDBIO-Txn-Id for every retry.

// Error response envelope
{
  "txn_id": "a3f1c8b24e6f4a5d9b8c7e0f1d2a3b4c",
  "error": {
    "code":    "NO_FACE_DETECTED",
    "message": "No face detected in probe image."
  },
  "processed_at":       "2026-05-09T12:34:56.789Z",
  "processing_time_ms": 47
}

All codes apply to both /v1/face/match and /v1/face/liveness. The specific validation that failed for INVALID_REQUEST is carried in error.message.

HTTPCodeDescription
400INVALID_REQUESTMissing or malformed header, body field, or parameter — including X-IDBIO-Txn-Id format, liveness_mode, frame count, focus-distance range, and match_threshold. error.message carries the specific reason.
401UNAUTHORIZEDMissing, malformed, revoked, or environment-mismatched X-IDBIO-API-Key.
402QUOTA_EXHAUSTEDPlan quota exceeded.
403FORBIDDENAPI key is not entitled for this product or operation.
409DUPLICATE_TXN_IDX-IDBIO-Txn-Id already used by this API key. Generate a fresh ID and resubmit.
413PAYLOAD_TOO_LARGEDecoded image exceeds 4 MB or request body exceeds 10 MB.
415INVALID_IMAGEImage could not be decoded. Check base64 encoding and format.
422NO_FACE_DETECTEDNo face found. Improve framing, lighting, or pose.
422MULTIPLE_FACES_DETECTEDMore than one face found. Capture must contain exactly one.
422QUALITY_TOO_LOWImage quality below operational floor (blur, occlusion, illumination, off-pose).
429RATE_LIMIT_EXCEEDEDRate cap hit. Retry after the duration in the Retry-After header — with a new X-IDBIO-Txn-Id.
500INTERNAL_ERRORUnexpected server error. Retry with backoff and a new X-IDBIO-Txn-Id.
503SERVICE_UNAVAILABLETemporary capacity issue. Honour Retry-After and resubmit with a new X-IDBIO-Txn-Id.

Service-level commitments, throughput limits, and pricing are governed by your commercial agreement — contact sales for details.