Documentation

Identity Verification APIs

This documentation covers both DMS Workspace APIs: IDVerifr for document recognition and LiveVerifr for face liveness and biometric matching. Both are available over HTTPS. IDVerifr is served under /idcard_recognition_v2 and LiveVerifr under /v1/livecheck.

Quickstart

Get from zero to your first successful scan in about a minute. You'll need a DMS Workspace account and an API key.

1

Create an account

Sign up at idverifr.dmsworkspace.com. The free tier includes scans for evaluation, no credit card required.

2

Create an API key

In the backoffice, open API Keys New key. Copy it now — the plaintext is shown once and never again. Optionally restrict the key to specific IP ranges for extra safety.

3

Make your first request

Run the cURL sample below against your own image. You should see the structured JSON response in seconds.

Your first scan — cURL

curl -X POST https://api.dmsworkspace.com/idcard_recognition_base64 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"image_base64": "<BASE64_OF_YOUR_ID_IMAGE>"}'

Official SDKs

Prefer not to hand-roll the HTTP plumbing? We publish thin client libraries for Node / TypeScript and Python. They wrap the API with typed response models, built-in retry, typed error classes, and a constant-time webhook signature verifier.

npm@dmsworkspace/idverifr

Node 18+ · TypeScript-first · zero runtime deps.

Install

npm install @dmsworkspace/idverifr

Use

import { IDVerifrClient } from "@dmsworkspace/idverifr";

const client = new IDVerifrClient({ apiKey: process.env.IDVERIFR_API_KEY! });
const result = await client.recognizeBase64(imageBase64);
PyPIidverifr-sdk

Python 3.9+ · only dependency is urllib3.

Install

pip install idverifr-sdk

Use

from idverifr import IDVerifrClient

client = IDVerifrClient(api_key=os.environ["IDVERIFR_API_KEY"])
result = client.recognize_base64(image_bytes)

Working in another language? The SDKs are thin wrappers over the public HTTP API documented below — any language with an HTTP client works.

Getting Started

Authentication

All requests require a Bearer token in the Authorization header. Obtain your API key from the dashboard after signup. Never expose your key in client-side code — proxy requests through your backend.

Header format

Authorization: Bearer YOUR_API_KEY

Base URLs

IDVerifr

https://api.dmsworkspace.com/idcard_recognition_v2

LiveVerifr

https://api.dmsworkspace.com/v1/livecheck

Error responses

400Bad request — missing or invalid parameters
401Unauthorized — missing or invalid API key
422Unprocessable — no document or face detected in image
500Internal server error — contact support

Rate limits & quotas

Every plan includes a monthly scan allowance (included_scans). Requests never block when you exceed it — the API keeps answering and every scan past the allowance is counted as overage and billed post-hoc at the plan's per-scan rate. That keeps your integration from going down if you have a traffic spike.

WhenWhat happens
You’re under the monthly allowanceScans counted against included_scans. No overage charge.
You cross the allowanceRequests continue to succeed. Every additional scan increments the overage counter and is billed on the next invoice.
Usage thresholds reachedWe send an in-app notification (and email, if you haven’t opted out) at 10 %, 25 %, and 50 % of the allowance, plus when overage begins.
API key or IP restriction triggersRequest is rejected with 401 Unauthorized. Check the key is active and the caller IP is on the allowlist.
Monitor your usage and overage balance in Billing — the dashboard shows scans consumed, projected monthly spend, and every historical invoice.

IDVerifr — Document Recognition

Extracts structured fields from identity documents using a four-stage pipeline: detection → classification → extraction → enrichment. Supports ID cards, passports, and driving licences across 50+ countries. Every response includes all key fields, the portrait photo, and a fraud assessment.

Pipeline overview

1

Detect & Rectify

  • YOLOv8-nano locates and crops the document
  • Perspective correction and orientation normalisation
2

Classify

  • MobileNetV3-Large identifies country and document type
  • Selects the appropriate field extraction template
3

Extract Fields

  • Template-based crop-OCR per country
  • LayoutLMv3 token classification (fine-tuned on 13k documents)
  • EU driving licence numbered-field extractor
  • PaddleOCR 3.x full-image fallback
4

Enrich & Validate

  • ICAO 9303 MRZ parser (highest merge priority)
  • Barcode decode — PDF417 and QR
  • Portrait crop via InsightFace
  • Fraud / specimen watermark checks
  • Field validators and normalisation
Merge priority (highest wins): MRZ › EU DL extractor › LayoutLMv3 › Template OCR › Classifier metadata

Endpoints

MethodPath
POST/idcard_recognition
POST/idcard_recognition_base64
POST/idcard_recognition_base64_multi_page
GET/health

Response fields

FieldDescription
DocumentTypeDRIVING_LICENSE, PASSPORT, NATIONAL_ID, etc.
IssuingCountryISO 3166-1 alpha-3 country code
SurnameFamily name extracted from the document
GivenNamesGiven names extracted from the document
DateOfBirthISO 8601 date (YYYY-MM-DD)
DateOfExpiryISO 8601 date (YYYY-MM-DD)
DocumentNumberDocument identifier number
SexM, F, or X
NationalityISO 3166-1 alpha-3 nationality code
PortraitBase64-encoded face crop from the document
OcrProviderV2_TEMPLATE | PADDLE_OCR | OPEN_AI
FraudCheck.ResultGENUINE or SUSPICIOUS

Sample response

{
  "DocumentType": "DRIVING_LICENSE",
  "IssuingCountry": "ESP",
  "Surname": "GARCIA LOPEZ",
  "GivenNames": "CARLOS",
  "DateOfBirth": "1990-06-15",
  "DateOfExpiry": "2028-06-15",
  "DocumentNumber": "B12345678",
  "Sex": "M",
  "Nationality": "ESP",
  "Portrait": "<base64-encoded-face-crop>",
  "OcrProvider": "V2_TEMPLATE",
  "FraudCheck": {
    "Result": "GENUINE",
    "Checks": ["NO_SPECIMEN_WATERMARK", "NO_SCREEN_CAPTURE"]
  }
}

LiveVerifr — Face Recognition & Liveness

Analyses faces for passive liveness, biometric matching, quality, and pose. The face_detection endpoint returns a liveness verdict, quality scores, 3D head pose, eye/mouth state, occlusion coverage, 68-point landmarks, and a 2048-byte biometric template — all in one call.

Endpoints

MethodPath
POST/face_detection
POST/template_extraction
POST/face_verify
GET/health

Response fields — face_detection

FieldTypeDescription
livenessstringGENUINE or SPOOF — passive anti-spoofing verdict
qualityfloatFace quality score 0–1. Reject below 0.5 in production.
luminancefloatIllumination score 0–1. Low values indicate poor lighting.
eye_distancefloatInter-ocular distance in pixels.
pose.yawfloatHorizontal head rotation in degrees.
pose.rollfloatHead tilt in degrees.
pose.pitchfloatVertical head rotation in degrees.
eye_stateobjectleft_closed / right_closed — float 0–1 where 1 = fully closed.
mouth_openedfloatMouth openness 0–1.
occlusionfloatFraction of face area occluded.
bboxint[4]Bounding box [x1, y1, x2, y2] in pixels.
landmarks_68float[]68 × (x, y) facial landmark coordinates.
templatestring2048-byte biometric vector for 1:1 matching.

face_detection response

{
  "face_count": 1,
  "faces": [
    {
      "liveness": "GENUINE",
      "quality": 0.94,
      "luminance": 0.87,
      "eye_distance": 142.3,
      "pose": {
        "yaw": 1.8,
        "roll": -0.4,
        "pitch": 2.1
      },
      "eye_state": {
        "left_closed": 0.02,
        "right_closed": 0.03
      },
      "mouth_opened": 0.12,
      "occlusion": 0.01,
      "bbox": [120, 80, 380, 420],
      "landmarks_68": [[x1,y1], [x2,y2], "..."],
      "template": "<2048-byte-biometric-vector>"
    }
  ]
}

face_verify response

{
  "similarity": 0.9342,
  "match": true,
  "threshold": 0.82
}

Webhooks

Receive push notifications the moment something interesting happens on your tenant — usage anomalies, invoice lifecycle events, scan-replay retries. Configure endpoints in Webhooks. Each endpoint gets its own signing secret and can be scoped to specific event types.

Event catalog

EventFires when
anomaly.detectedUsage anomaly detector flags a volume or error-rate spike (3σ above the rolling baseline).
invoice.createdStripe issues a new invoice for your subscription.
invoice.finalizedAn invoice becomes payable (line items + taxes are locked).
invoice.paidAn invoice is paid successfully — use this to unlock gated features.
invoice.payment_failedCard was declined or authorization failed. Followed by Stripe retries.
invoice.voidedAn invoice was voided (admin action or credit note).
testSynthetic event fired by “Send test” in the backoffice — useful for verifying signing.
retryA prior delivery was replayed by the retry button in the backoffice.

Payload shape

Every delivery is a JSON POST. The top-level event discriminator tells you which schema applies; all deliveries also include deliveryId and timestamp for idempotency and staleness checks.

Example — anomaly.detected

{
  "event": "anomaly.detected",
  "deliveryId": "c9f4b8d2-2a1a-4a7b-bbd2-f0b7d8e4a311",
  "timestamp": "2026-04-18T14:32:11Z",
  "anomalyId": "f3e6b2a0-7c1d-4e9f-9d3b-11c2d3e4f5a6",
  "scanType": "idRecognition",
  "anomalyType": "VOLUME_SPIKE",
  "severity": "WARN",
  "metricValue": "421",
  "baselineMean": "38.2",
  "baselineStddev": "8.1"
}

Headers

Three custom headers travel with every delivery:

HeaderDescription
X-Hillogy-Delivery-IdUUID for this specific attempt. Use it to deduplicate — retries and at-least-once delivery can repeat an id.
X-Hillogy-TimestampUnix timestamp (seconds) of when the delivery was signed. Reject requests older than ~5 minutes to defeat replays.
X-Hillogy-SignatureSignature envelope: t=<timestamp>,v1=<hmac>. The v1 value is HMAC-SHA256(secret, timestamp + "." + rawBody) in hex.

Sample headers

X-Hillogy-Delivery-Id: c9f4b8d2-2a1a-4a7b-bbd2-f0b7d8e4a311
X-Hillogy-Timestamp: 1745006531
X-Hillogy-Signature: t=1745006531,v1=3b4f9a…e2c8

Verifying signatures

Always verify the signature before trusting a payload. Use the raw body (not a re-serialised JSON string) and a constant-time comparison to avoid timing leaks.

Node.js

// Node.js — verify an incoming webhook with crypto
import crypto from "node:crypto";

export function verifyHillogyWebhook(req, secret) {
  const header = req.headers["x-hillogy-signature"];   // "t=1745006531,v1=3b4f…"
  const timestamp = req.headers["x-hillogy-timestamp"]; // "1745006531"
  if (!header || !timestamp) return false;

  // Reject replays older than 5 minutes.
  const age = Math.floor(Date.now() / 1000) - Number(timestamp);
  if (age > 300) return false;

  // Body MUST be the raw JSON string — parsing loses key order.
  const signed = timestamp + "." + req.rawBody;
  const expected = crypto.createHmac("sha256", secret)
    .update(signed).digest("hex");

  // Pull the v1= value out and compare in constant time.
  const match = /v1=([a-f0-9]+)/.exec(header);
  if (!match) return false;
  return crypto.timingSafeEqual(
    Buffer.from(match[1], "hex"),
    Buffer.from(expected, "hex"),
  );
}

Python

# Python — verify an incoming webhook with hmac
import hmac, hashlib, time, re

def verify_hillogy_webhook(headers, raw_body: bytes, secret: str) -> bool:
    header = headers.get("X-Hillogy-Signature")
    ts     = headers.get("X-Hillogy-Timestamp")
    if not header or not ts:
        return False

    # Reject replays older than 5 minutes.
    if abs(time.time() - int(ts)) > 300:
        return False

    signed = f"{ts}.".encode() + raw_body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()

    match = re.search(r"v1=([a-f0-9]+)", header)
    if not match:
        return False
    return hmac.compare_digest(match.group(1), expected)

Secret rotation

Each endpoint can hold two signing secrets simultaneously — a primary and a staged rollover. Both are used to sign outbound deliveries during the rollover window, so your verifier can accept either while you roll the secret on your side. Promote the staged secret to primary from the backoffice once you're done.

Retry policy

Non-2xx responses (and timeouts after 10 s) are retried with exponential backoff. Deliveries can also be manually replayed from the backoffice execution history — those arrive as event: "retry" with the original delivery id in originalExecutionId.

Integration Examples

Code examples for every major language and framework. Always proxy API calls through your backend — never include your API key in client-side code.

IDVerifr — code examples

curl -X POST https://api.dmsworkspace.com/idcard_recognition \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "file=@id_card.jpg"

LiveVerifr — code examples

# Liveness + face analysis
curl -X POST https://api.dmsworkspace.com/face_detection \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "image=@selfie.jpg"

# 1:1 face verification
curl -X POST https://api.dmsworkspace.com/face_verify \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"template_a":"<vector>","template_b":"<vector>"}'

Benchmarks — IDVerifr Field Accuracy

Evaluated on a 428-image set. 100% JSON coverage — every image returns a structured response. Strongest on EU and Latin-script documents. Non-Latin scripts (CJK, Arabic) use MRZ-only extraction; native-script OCR is on the product roadmap.

FieldExact matchLenient match
Document Name
84.0%
84.7%
Sex
69.5%
69.5%
Date of Birth
65.8%
67.8%
Date of Expiry
59.5%
60.4%
Given Names
54.1%
56.9%
Surname
49.7%
54.3%
Document Number
42.2%
47.2%

Exact: character-for-character equality after normalisation. Lenient: allows common OCR substitutions (0/O, 1/I, etc.).