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.
Create an account
Sign up at idverifr.dmsworkspace.com. The free tier includes scans for evaluation, no credit card required.
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.
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.
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);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
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.
| When | What happens |
|---|---|
| You’re under the monthly allowance | Scans counted against included_scans. No overage charge. |
| You cross the allowance | Requests continue to succeed. Every additional scan increments the overage counter and is billed on the next invoice. |
| Usage thresholds reached | We 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 triggers | Request is rejected with 401 Unauthorized. Check the key is active and the caller IP is on the allowlist. |
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
Detect & Rectify
- YOLOv8-nano locates and crops the document
- Perspective correction and orientation normalisation
Classify
- MobileNetV3-Large identifies country and document type
- Selects the appropriate field extraction template
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
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
Endpoints
| Method | Path |
|---|---|
| POST | /idcard_recognition |
| POST | /idcard_recognition_base64 |
| POST | /idcard_recognition_base64_multi_page |
| GET | /health |
Response fields
| Field | Description |
|---|---|
| DocumentType | DRIVING_LICENSE, PASSPORT, NATIONAL_ID, etc. |
| IssuingCountry | ISO 3166-1 alpha-3 country code |
| Surname | Family name extracted from the document |
| GivenNames | Given names extracted from the document |
| DateOfBirth | ISO 8601 date (YYYY-MM-DD) |
| DateOfExpiry | ISO 8601 date (YYYY-MM-DD) |
| DocumentNumber | Document identifier number |
| Sex | M, F, or X |
| Nationality | ISO 3166-1 alpha-3 nationality code |
| Portrait | Base64-encoded face crop from the document |
| OcrProvider | V2_TEMPLATE | PADDLE_OCR | OPEN_AI |
| FraudCheck.Result | GENUINE 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
| Method | Path |
|---|---|
| POST | /face_detection |
| POST | /template_extraction |
| POST | /face_verify |
| GET | /health |
Response fields — face_detection
| Field | Type | Description |
|---|---|---|
| liveness | string | GENUINE or SPOOF — passive anti-spoofing verdict |
| quality | float | Face quality score 0–1. Reject below 0.5 in production. |
| luminance | float | Illumination score 0–1. Low values indicate poor lighting. |
| eye_distance | float | Inter-ocular distance in pixels. |
| pose.yaw | float | Horizontal head rotation in degrees. |
| pose.roll | float | Head tilt in degrees. |
| pose.pitch | float | Vertical head rotation in degrees. |
| eye_state | object | left_closed / right_closed — float 0–1 where 1 = fully closed. |
| mouth_opened | float | Mouth openness 0–1. |
| occlusion | float | Fraction of face area occluded. |
| bbox | int[4] | Bounding box [x1, y1, x2, y2] in pixels. |
| landmarks_68 | float[] | 68 × (x, y) facial landmark coordinates. |
| template | string | 2048-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
| Event | Fires when |
|---|---|
| anomaly.detected | Usage anomaly detector flags a volume or error-rate spike (3σ above the rolling baseline). |
| invoice.created | Stripe issues a new invoice for your subscription. |
| invoice.finalized | An invoice becomes payable (line items + taxes are locked). |
| invoice.paid | An invoice is paid successfully — use this to unlock gated features. |
| invoice.payment_failed | Card was declined or authorization failed. Followed by Stripe retries. |
| invoice.voided | An invoice was voided (admin action or credit note). |
| test | Synthetic event fired by “Send test” in the backoffice — useful for verifying signing. |
| retry | A 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:
| Header | Description |
|---|---|
| X-Hillogy-Delivery-Id | UUID for this specific attempt. Use it to deduplicate — retries and at-least-once delivery can repeat an id. |
| X-Hillogy-Timestamp | Unix timestamp (seconds) of when the delivery was signed. Reject requests older than ~5 minutes to defeat replays. |
| X-Hillogy-Signature | Signature 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.
| Field | Exact match | Lenient 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.).