Identity Verification (KYC)
Purpose: this documentation describes how to integrate the Roommatik identity verification system. You can use the web widget in one of its six operation modes, consume the REST API directly, or combine both approaches.
The widget covers everything from document capture to full biometric verification (document + selfie + face comparison). The REST API exposes the same services for server-to-server integrations.
Full technical reference · Download Postman collection
1. Quick start
Copy the snippet below, replace YOUR_API_KEY with your key, and open it in a browser. This example runs the full verification flow: document capture, selfie capture, and face comparison.
<!DOCTYPE html>
<html>
<head>
<script src="https://sdk.roommatik.com/kyc/v1/roommatik-kyc.js"></script>
<link rel="stylesheet" href="https://sdk.roommatik.com/kyc/v1/roommatik-kyc.css">
</head>
<body>
<roommatik-kyc
mode="verify"
api-url="https://kyc.roommatik.com"
api-key="YOUR_API_KEY"
lang="en">
</roommatik-kyc>
<script>
document.querySelector('roommatik-kyc')
.addEventListener('kyc-complete', (e) => {
if (e.detail.success) {
alert('Identity verified: ' + e.detail.document.DocumentNumber);
}
});
</script>
</body>
</html>
2. Prerequisites
- API key: authentication key issued by Roommatik.
- Base URL:
https://kyc.roommatik.com. - HTTPS: the widget requires an HTTPS origin (or
localhost) to access the camera.
Contact your Roommatik account manager to obtain your credentials.
3. Widget modes
Choose the mode that matches your use case via the mode attribute:
| Mode | Description | API key required |
|---|---|---|
capture-only | Captures images (document or face) and hands them back to your code. No API calls are made. | No |
document | Document capture with OCR and field extraction. | Yes |
verify | Full verification: document + selfie + face comparison. | Yes |
face-compare | Capture or upload two face images and return a similarity score. | Yes |
face-embed | Capture a face and extract a 512-dimensional face embedding vector. | Yes |
4. Widget attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
mode | string | capture-only | Operation mode (see previous table). |
api-url | string | — | API base URL. Required for all modes except capture-only. |
api-key | string | — | API key. Required for all modes except capture-only. |
lang | string | en | Language: en, es, fr, de, it, pt. |
primary-color | string (hex) | #3b82f6 | Primary UI color. |
two-sided | boolean | true | Two-sided document (ID card, residence permit). Set two-sided="false" for passports and single-sided documents. Only applies to document and verify modes. |
capture-target | string | document | In capture-only mode: document or face. |
input-mode | string | camera | Input method: camera, upload or both. The upload option accepts JPG, PNG and multi-page PDF files. |
scan-mode | string | ocr | For document/verify modes: ocr (camera capture with OCR) or digitalIdQr (read the QR code from the Spanish DNIe / miDNI app). |
combine-images | boolean | false | Combines front and back into a single image and submits them in one API call. |
return-type | string | Data | Comma-separated values: Data, Face, Signature. For example "Data,Face". |
is-mapping-applied | boolean | true | Returns standardized field names (FullName, DocumentNumber, etc.). |
max-image-width | number | 1600 | Maximum captured image width in pixels. |
jpeg-quality | number (0–1) | 0.92 | JPEG compression quality. |
auto-start | boolean | true | Automatically start the camera when the widget mounts. |
labels | JSON string | — | Overrides built-in translations for specific labels. |
5. Events
| Event | Modes | e.detail |
|---|---|---|
kyc-captured | capture-only | { frontImage, backImage, combinedImage, frontBlob, backBlob, combinedBlob, qrData? } |
kyc-complete | all other modes | Object discriminated by mode (see section 6). |
kyc-error | all | Error object. |
kyc-cancel | all | — |
6. Result by mode
The e.detail object of the kyc-complete event is discriminated by mode:
In widget modes that return document data (document and verify), the document fields are delivered inside a data object (or document in verify mode), with PascalCase names when is-mapping-applied="true".
6.1. document mode
{
"mode": "document",
"success": true,
"data": {
"Name": "JOHN",
"Surname": "DOE SMITH",
"FullName": "JOHN DOE SMITH",
"DocumentNumber": "AB123456",
"DocumentType": "Identity Card",
"DateOfBirth": "15/03/1990",
"DateOfExpiry": "20/01/2030",
"Sex": "M",
"Nationality": "ESP"
}
}
6.2. verify mode
{
"mode": "verify",
"success": true,
"document": {
"FullName": "JOHN DOE SMITH",
"DocumentNumber": "AB123456",
"DateOfBirth": "15/03/1990",
"DateOfExpiry": "20/01/2030"
},
"verification": {
"verified": true,
"similarity": 87.3,
"distance": 0.127
}
}
6.3. face-compare mode
{
"mode": "face-compare",
"success": true,
"verified": true,
"similarity": 87.3,
"distance": 0.127
}
6.4. face-embed mode
{
"mode": "face-embed",
"success": true,
"results": [{
"embedding": [0.0123, -0.0456, "...512 values"],
"boundingBox": { "x": 50, "y": 30, "width": 120, "height": 150 },
"confidence": 0.99
}]
}
7. capture-only mode — uploading images from the browser
If you need the captured images to bypass your own server, the widget can upload them directly to a presigned S3 URL and hand back only the object key:
const kyc = document.querySelector('roommatik-kyc');
kyc.uploadCapturedImage = {
getPresignedUrl: async () => {
const res = await fetch('/my-backend/presign', { method: 'POST' });
return await res.json(); // { uploadUrl, key }
},
onUploaded: (key) => {
console.log('Image uploaded:', key);
}
};
When combined with two-sided="true", combine-images="true" is enforced so there is a single upload per capture.
8. React integration
import { KycWidget } from '@roommatik/id-document-capture';
import '@roommatik/id-document-capture/style.css';
<KycWidget
mode="verify"
apiUrl="https://kyc.roommatik.com"
apiKey="YOUR_API_KEY"
lang="en"
onComplete={(result) => {
if (result.mode === 'verify' && result.success) {
console.log('Verified:', result.verification.verified);
console.log('Document:', result.document);
}
}}
onError={(err) => console.error(err)}
/>
9. SDK (without UI)
If you already have your own capture interface, use the KycClient SDK to talk to the API:
import { KycClient } from '@roommatik/id-document-capture';
const client = new KycClient({
apiUrl: 'https://kyc.roommatik.com',
apiKey: 'YOUR_API_KEY',
returnType: ['Data', 'Face'],
isMappingApplied: true
});
// Document OCR (single or dual side)
const result = await client.verify(frontImage, backImage);
// Face comparison
const compare = await client.compareFaces(imageA, imageB);
// 512-dimensional face embedding
const embed = await client.embedFace(image);
// miDNI QR decoding
const qr = await client.verifyQr(qrBase64);
In addition, KycClient exposes static utilities to encode and decode face vectors in a compact format suitable for QR codes:
const qrPayload = KycClient.encodeEmbeddingForQr(embedding); // ~700 characters
const vector = KycClient.decodeEmbeddingFromQr(qrPayload);
10. REST API (v2)
All gateway endpoints authenticate via the X-Api-Key header. The Content-Type is application/json unless stated otherwise. You can optionally include the X-Account-Name header with your account name to speed up authentication.
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v2/kyc | Document OCR — single side (image) or dual side (image1 + image2). |
| POST | /api/v2/mrz | MRZ-only extraction (passports and ID cards). |
| POST | /api/v2/face/compare | Face comparison. |
| POST | /api/v2/face/embed | 512-dimensional face embedding extraction. |
| POST | /api/v2/qr/midni | Spanish DNIe (miDNI) QR decoding. |
| GET | /health | Public liveness check. |
10.1. POST /api/v2/kyc
Request body (single side):
{
"image": "BASE64_IMAGE_WITHOUT_PREFIX",
"returnType": ["Data", "Face"],
"IsMappingApplied": true
}
Request body (dual side):
{
"image1": "BASE64_FRONT",
"image2": "BASE64_BACK",
"returnType": ["Data", "Face"],
"IsMappingApplied": true
}
| Field | Type | Description |
|---|---|---|
image / image1 / image2 | string | Base64-encoded image without the data:image/...;base64, prefix. |
imageUrl | string | Alternative to image: an S3 presigned URL the service will fetch. |
returnType | string[] | What to return: Data, Face, Signature. |
IsMappingApplied | boolean | Returns fields with standardized names. |
face_vector | boolean | Compute a 512-dimensional face embedding. Requires Face in returnType. |
faceImage | string | Base64 selfie to compare against the document face. The response will include FaceMatchAccuracy. |
Response (200 OK):
{
"Data": {
"Name": "JOHN",
"Surname": "DOE SMITH",
"FullName": "JOHN DOE SMITH",
"DocumentNumber": "AB123456",
"DocumentType": "Identity Card",
"DateOfBirth": "15/03/1990",
"DateOfExpiry": "20/01/2030",
"Sex": "M",
"Nationality": "ESP"
},
"GeoData": {
"Region": "Community of Madrid",
"Province": "Madrid",
"PostalCode": "28001"
},
"ResponseTime": 1250,
"Description": "Spain - Identity Card",
"Face": "BASE64_CROPPED_FACE"
}
The Face, Signature, FaceVector, FaceMatchAccuracy and GeoData fields are omitted when they do not apply. If the image does not yield extractable data, the API responds with 204 No Content.
10.2. POST /api/v2/face/compare
Supports four input modes:
// 1) Two photos
{ "image1": "BASE64_A", "image2": "BASE64_B" }
// 2) Document + selfie (the face is auto-cropped from the document)
{ "document": "BASE64_DOC", "selfie": "BASE64_SELFIE" }
// 3) Precomputed vector + photo
{ "vector": [0.0123, ...], "image": "BASE64" }
// 4) Two vectors (fastest)
{ "vector1": [0.0123, ...], "vector2": [0.0456, ...] }
Response:
{
"verified": true,
"similarity": 87.3,
"distance": 0.127,
"face1": { "detected": true, "confidence": 0.99, "boundingBox": {...} },
"face2": { "detected": true, "confidence": 0.98, "boundingBox": {...} },
"processingTimeMs": 320
}
10.3. POST /api/v2/face/embed
// Single image
{ "image": "BASE64" }
// Batch
{ "images": ["BASE64_1", "BASE64_2"] }
Response:
{
"results": [{
"faceDetected": true,
"confidence": 0.99,
"boundingBox": {...},
"embedding": [0.0123, -0.0456, "...512 values"]
}],
"processingTimeMs": 280
}
10.4. POST /api/v2/qr/midni
Decodes the Spanish DNIe (miDNI) QR. The endpoint expects the raw binary payload extracted from the QR, not an image of it:
{ "Base64Data": "PAYLOAD_BASE64" }
// or
{ "HexData": "PAYLOAD_HEX" }
Response:
{
"Data": {
"DocumentNumber": "12345678Z",
"FirstName": "JOHN",
"Surname": "DOE SMITH",
"DateOfBirth": "15-03-1990",
"Gender": "M",
"ExpiryDate": "20-01-2030",
"Nationality": "ES",
"SignatureValid": "true"
},
"GeoData": { "Region": "...", "Province": "...", "PostalCode": "..." },
"ResponseTime": 45,
"Description": "miDNI QR (Complete) - Verification: Valid"
}
10.5. Full cURL example
curl -X POST https://kyc.roommatik.com/api/v2/kyc \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "BASE64_IMAGE",
"returnType": ["Data", "Face"],
"IsMappingApplied": true
}'
11. HTTP response codes
| Code | Meaning |
|---|---|
200 | Operation successful. |
204 | No data could be extracted — low image quality or unrecognized document. |
400 | Bad request: malformed JSON, missing required fields, or invalid parameters. |
401 | Unauthorized — API key missing or invalid. |
402 | Insufficient credits for this operation. |
403 | Account blocked, license expired, or IP not allowed. |
422 | Face endpoints: no face was detected in the submitted image. |
429 | Rate limit exceeded (per minute or per day). |
500 | Internal service error. |
503 | Service temporarily unavailable. |
12. Limits and restrictions
| Item | Details |
|---|---|
| Rate limiting | Configurable per account (per minute and per day). |
| Timeout | 30 seconds by default. |
| Image format | JPEG or PNG (Base64, presigned URL, or multipart upload). Multi-page PDF from the widget. |
| HTTPS | The widget requires HTTPS to access the camera. |
| Supported browsers | Chrome, Firefox, Safari, and Edge (recent versions). |
| Max image size | Recommended under 2 MB per image. |
