KYC Integration – Identity Document Capture
1. Quick Start
Copy this code, replace YOUR_API_KEY with your key, and open it in a browser:
<!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="full"
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('Document: ' + e.detail.data.documentNumber);
}
});
</script>
</body>
</html>
That’s it. Read on for more options and details.
2. Prerequisites
To use any of the integration options you need:
- API Key: authentication key provided by Roommatik.
- API URL: KYC service base address (e.g.
https://kyc.roommatik.com). - HTTPS: the web component requires an HTTPS environment (or localhost) to access the camera.
Contact your Roommatik account manager to obtain your credentials.
3. Integration options
There are three ways to integrate document verification:
| Option | Description | Use case |
|---|---|---|
| A. Direct API | Send images directly to the KYC API via HTTP requests | You already have your own capture interface or image pipeline |
| B. Component – Capture only | Use the web component to capture images and process them with your own logic | You need the capture UI but want to control the API submission |
| C. Component – Capture + KYC | The component captures images and automatically sends them to the KYC API | Full integration with minimum effort |
4. Option A: Direct API
Endpoint
POST {API_URL}/api/kyc
Required headers
| Header | Value | Description |
|---|---|---|
Content-Type | application/json | Content type |
X-Api-Key | Your API key | Authentication |
Request body
{
"image": "BASE64_IMAGE_WITHOUT_PREFIX",
"returnType": ["Data", "Face"],
"isMappingApplied": true
}
| Field | Type | Default | Description |
|---|---|---|---|
image | string | — | Base64 encoded image without the data:image/...;base64, prefix. Required unless imageUrl or file is provided. |
imageUrl | string | — | Presigned S3 URL pointing to the document image. Alternative to image. |
returnType | string[] | ["Data"] | Controls what the API returns. Possible values: "Data" (extracted fields), "Face" (cropped facial photo), "Signature" (cropped signature image). Can be combined, e.g. ["Data", "Face", "Signature"]. |
isMappingApplied | boolean | false | When true, normalizes raw OCR field names into standardized output (e.g. MRZ_DocumentNumber → documentNumber). Strongly recommended. |
face_vector | boolean | false | When true, computes a 512-dimensional face embedding. Requires "Face" in returnType. See section 13. |
debug | boolean | false | When true, includes a MappingDebug field in the response with step-by-step mapping details. |
detect_orientation | boolean | false | When true, automatically detects and corrects the orientation of the document image. |
/api/v1/kyc) to send both sides in a single request — see section 15.
Successful response (200 OK)
{
"firstName": "JOHN",
"lastName": "DOE SMITH",
"dateOfBirth": "1990-01-15",
"documentNumber": "12345678Z",
"expiryDate": "2030-12-31",
"gender": "M",
"nationality": "ES"
}
The fields returned depend on the document type and nationality. The most common ones are:
| Field | Description |
|---|---|
firstName | First name |
lastName | Last name |
dateOfBirth | Date of birth |
documentNumber | Document number |
expiryDate | Expiry date |
gender | Gender (M/F) |
nationality | Nationality (ISO code) |
mrz | Machine Readable Zone |
No data extracted (204 No Content)
If the API cannot extract data from the image, it returns a 204 status with no body. This usually indicates the image is low quality or does not contain a recognizable document.
Full cURL example
curl -X POST https://kyc.roommatik.com/api/kyc \
-H "Content-Type: application/json" \
-H "X-Api-Key: YOUR_API_KEY" \
-d '{
"image": "/9j/4AAQSkZJRgABAQ...",
"returnType": ["Data", "Face"],
"isMappingApplied": true
}'
Alternative: Send image via presigned URL
Instead of base64, you can upload the image to S3 and send a presigned URL:
{
"imageUrl": "https://s3.eu-central-1.amazonaws.com/bucket/key?X-Amz-...",
"returnType": ["Data", "Face"],
"isMappingApplied": true
}
Alternative: Send image as file (multipart)
You can also send the image as a file using multipart/form-data:
curl -X POST https://kyc.roommatik.com/api/kyc \ -H "X-Api-Key: YOUR_API_KEY" \ -F "[email protected]" \ -F "returntype=Data,Face" \ -F "IsMappingApplied=true"
JavaScript example
async function verifyDocument(imageBase64, apiKey) {
// Strip the data:image prefix if present
const base64 = imageBase64.includes(',')
? imageBase64.split(',')[1]
: imageBase64;
const response = await fetch('https://kyc.roommatik.com/api/kyc', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': apiKey
},
body: JSON.stringify({
image: base64,
returnType: ['Data'],
isMappingApplied: true
})
});
if (response.status === 204) {
throw new Error('No data could be extracted from the document');
}
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
return await response.json();
}
5. Option B: Web component – Capture only
In this mode the component provides the capture interface with automatic document detection and returns Base64 images for you to process with your own logic.
Installation
Add the following tags to the <head> of your HTML page:
<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">
Usage
<roommatik-kyc
mode="capture-only"
lang="en"
></roommatik-kyc>
<script>
const kyc = document.querySelector('roommatik-kyc');
kyc.addEventListener('kyc-captured', (e) => {
const { frontImage, backImage } = e.detail;
console.log('Front:', frontImage); // data:image/jpeg;base64,...
console.log('Back:', backImage); // data:image/jpeg;base64,... or null
// Send images to your backend or process them here
});
kyc.addEventListener('kyc-error', (e) => {
console.error('Error:', e.detail.message);
});
kyc.addEventListener('kyc-cancel', () => {
console.log('User cancelled the capture');
});
</script>
combine-images is set, the kyc-captured event returns { combinedImage } instead of { frontImage, backImage }. The combined image contains both sides stacked vertically.6. Option C: Web component – Capture + KYC
In this mode the component captures images and automatically sends them to the KYC API. You receive the extracted document data directly.
Installation
Add the following tags to the <head> of your HTML page (if not already included):
<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">
Usage
<roommatik-kyc
mode="full"
api-url="https://kyc.roommatik.com"
api-key="YOUR_API_KEY"
lang="en"
<!-- combine-images: set to combine front+back into one image -->
></roommatik-kyc>
<script>
const kyc = document.querySelector('roommatik-kyc');
kyc.addEventListener('kyc-complete', (e) => {
if (e.detail.success) {
console.log('Extracted data:', e.detail.data);
// { firstName, lastName, dateOfBirth, documentNumber, ... }
} else {
console.error('Error:', e.detail.error);
}
});
kyc.addEventListener('kyc-error', (e) => {
console.error('Error:', e.detail.message);
});
</script>
7. Component reference
Web Component attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
mode | "capture-only" | "full" | "capture-only" | Operation mode |
api-url | string | — | KYC API base URL (required in full mode) |
api-key | string | — | API key (required in full mode) |
lang | string | "en" | Language: en, es, fr, de, it, pt |
primary-color | string (hex) | #3b82f6 | Primary UI color |
two-sided | boolean | true | Scan two sides (front + back). Set to false for single-sided documents (e.g. passport) |
max-image-width | number | 800 | Maximum captured image width (px) |
jpeg-quality | number | 0.70 | JPEG quality (0 to 1) |
auto-start | boolean | true | Auto-start camera on mount |
combine-images | boolean | false | When true, front and back images are combined into a single image and sent as one API call |
labels | JSON string | — | Custom text labels (overrides built-in translations) |
Events
| Event | Mode | Detail (e.detail) |
|---|---|---|
kyc-captured | capture-only | { frontImage: string, backImage: string | null, combinedImage: string | null } |
kyc-complete | full | { success: boolean, data?: object, error?: string, rawResponse?: object } |
kyc-error | both | Error object |
kyc-cancel | both | — |
8. Document sides
Use the two-sided attribute to indicate whether the document has one or two sides:
two-sided | Behaviour | Example |
|---|---|---|
true (default) | Scans front + back | ID card, residence permit |
false | Scans one side only | Passport |
9. Error handling
API response codes
| Code | Meaning |
|---|---|
200 | Data extracted successfully |
204 | No data could be extracted (low quality image or unrecognized document) |
400 | Bad request (invalid parameters) |
401 | Unauthorized (invalid or missing API key) |
403 | Forbidden (account blocked, license expired, IP not whitelisted, or rate limit exceeded) |
429 | Too many requests (rate limit exceeded) |
500 | Internal server error |
Common error messages
| Message | Cause |
|---|---|
License Expired | Your license has expired. Contact your administrator. |
License Blocked | Your license has been blocked. |
Account is Blocked | The account is blocked. |
Access forbidden. Your IP is not whitelisted. | Your IP is not in the whitelist. |
Rate limit exceeded | You have exceeded the per-minute or per-day request limit. |
No data could be extracted from the document | The image does not contain a recognizable document or is low quality. |
10. Limits and restrictions
| Concept | 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 file upload) |
| HTTPS required | The web component requires HTTPS to access the camera |
| Supported browsers | Chrome, Firefox, Safari, Edge (latest versions) |
| Max image size | Recommended under 2MB per image. Larger images may cause timeouts. |
11. Full integration example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document Verification</title>
<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>
<h1>Identity Verification</h1>
<div id="result"></div>
<roommatik-kyc
mode="full"
api-url="https://kyc.roommatik.com"
api-key="YOUR_API_KEY"
lang="en"
primary-color="#1a73e8"
></roommatik-kyc>
<script>
const kyc = document.querySelector('roommatik-kyc');
const resultDiv = document.getElementById('result');
kyc.addEventListener('kyc-complete', (e) => {
if (e.detail.success) {
const d = e.detail.data;
resultDiv.innerHTML = `
<h2>Document Verified</h2>
<p><strong>Name:</strong> ${d.firstName} ${d.lastName}</p>
<p><strong>Document:</strong> ${d.documentNumber}</p>
<p><strong>Expiry:</strong> ${d.expiryDate}</p>
`;
} else {
resultDiv.innerHTML = `<p style="color:red">Error: ${e.detail.error}</p>`;
}
});
kyc.addEventListener('kyc-error', (e) => {
resultDiv.innerHTML = `<p style="color:red">Error: ${e.detail.message}</p>`;
});
</script>
</body>
</html>
12. Choosing the right returnType
| returnType | Use case | Response |
|---|---|---|
["Data"] | OCR only — fastest option. Use when you only need extracted text fields (name, document number, dates, etc.). | Small JSON with text fields only. |
["Data", "Face"] | OCR + facial photo. Required if you plan to use face_vector for biometric matching. | JSON + base64 face image (~30-50 KB extra). |
["Data", "Face", "Signature"] | Full extraction. Returns text fields, facial photo, and signature image. | JSON + two base64 images. |
For most integrations that only need document data extraction (check-in, registration, identity verification without biometrics), use
returnType: ["Data"] with isMappingApplied: true. This is the fastest and lightest configuration: the API skips face and signature extraction entirely, returns a smaller response, and gives you clean, normalized field names (firstName, lastName, documentNumber, etc.) ready to use.13. Face Embedding (Face Vector)
When a document image contains a photograph (ID card, passport), the API can extract a 512-dimensional face embedding vector. This vector is a numerical representation of the face useful for:
- Face matching: compare the document photo against a live selfie.
- Deduplication: detect if two documents belong to the same person.
- Identity verification: store the vector and compare against future submissions.
Request
Add face_vector: true to the request body:
{
"image": "BASE64_IMAGE_WITHOUT_PREFIX",
"returnType": ["Data", "Face"],
"isMappingApplied": true,
"face_vector": true
}
With multipart/form-data:
curl -X POST https://kyc.roommatik.com/api/kyc \ -H "X-Api-Key: YOUR_API_KEY" \ -F "[email protected]" \ -F "returntype=Data,Face" \ -F "IsMappingApplied=true" \ -F "face_vector=true"
Important: returnType must include "Face". Without it, the API will not extract the facial photograph and FaceVector will always be null.
Response
When face_vector is enabled and a face is detected, the response includes a FaceVector field:
{
"firstName": "JOHN",
"lastName": "DOE",
"documentNumber": "12345678Z",
"FaceVector": [0.0234, -0.0891, 0.0412, ... ]
}
| Field | Type | Description |
|---|---|---|
FaceVector | number[] | 512-dimensional float array (ArcFace embedding), L2-normalized. Only present when face_vector=true and a face is detected. Returns null if no face is found. |
Comparing face vectors
Face vectors are compared using cosine similarity. A score above 0.5 typically indicates the same person:
function cosineSimilarity(a, b) {
let dot = 0, normA = 0, normB = 0;
for (let i = 0; i < a.length; i++) {
dot += a[i] * b[i];
normA += a[i] * a[i];
normB += b[i] * b[i];
}
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
}
const similarity = cosineSimilarity(vectorFromDocument, vectorFromSelfie);
if (similarity > 0.5) {
console.log('Face match confirmed');
}
14. Dual-Image Processing (Front & Back)
Many identity documents have relevant information on both sides (e.g. ID cards with MRZ on the back). Instead of making two separate requests to /api/kyc, you can send both sides in a single request using the v1 endpoint.
Endpoint
POST {API_URL}/api/v1/kyc
Request body
{
"image1": "BASE64_FRONT_IMAGE",
"image2": "BASE64_BACK_IMAGE",
"returnType": ["Data", "Face", "Signature"],
"isMappingApplied": true,
"combineBothSides": false,
"detect_orientation": true
}
| Field | Type | Default | Description |
|---|---|---|---|
image1 | string (base64) or File | — | Front side of the document. |
image2 | string (base64) or File | — | Back side of the document. |
image1Url | string | — | Alternative: S3 presigned URL for image 1. |
image2Url | string | — | Alternative: S3 presigned URL for image 2. |
combineBothSides | boolean | false | When true, the server combines both images into one before processing. When false, each side is processed independently. |
returnType | string[] | ["Data"] | Same as standard endpoint. |
isMappingApplied | boolean | false | Normalizes field names and merges fields from both sides, keeping the highest-scoring value. |
face_vector | boolean | false | Compute face embedding. Requires "Face" in returnType. |
detect_orientation | boolean | false | Auto-detect and correct document orientation. |
Response when combineBothSides: false
Each side is processed independently and returned separately:
{
"Image1Response": {
"keyValues": { "VIZ_Surname": "DOE", "VIZ_GivenNames": "JOHN" },
"keyScores": { "VIZ_Surname": 0.95, "VIZ_GivenNames": 0.97 },
"documentInfo": { "nationality": "GBR", "documentType": "UK - Id Card", "documentSide": 1 },
"face": "data:image/jpeg;base64,..."
},
"Image2Response": {
"keyValues": { "MRZ_Surname": "DOE", "MRZ_DateOfBirth": "900115" },
"keyScores": { "MRZ_Surname": 1.0, "MRZ_DateOfBirth": 1.0 },
"documentInfo": { "nationality": "GBR", "documentType": "UK - Id Card", "documentSide": 2 }
}
}
When isMappingApplied: true, the gateway automatically merges fields from both sides, keeping the highest-scoring value for each field, and applies field name normalization on the merged result.
Response when combineBothSides: true
The server combines both images into one and processes it as a single document — the response is identical to the standard /api/kyc endpoint.
When to use each approach
| Scenario | Recommended approach |
|---|---|
| Simple integration, minimal code | Two separate requests to /api/kyc |
| Need to know which side each field came from | /api/v1/kyc with combineBothSides: false |
| Server-side image combination | /api/v1/kyc with combineBothSides: true |
| Single-sided documents (passports) | Standard /api/kyc endpoint |
Multipart example
curl -X POST https://kyc.roommatik.com/api/v1/kyc \
-H "X-Api-Key: YOUR_API_KEY" \
-F "[email protected]" \
-F "[email protected]" \
-F "returntype=Data,Face,Signature" \
-F "IsMappingApplied=true" \
-F "combineBothSides=false"
15. miDNI QR Decoding
The Spanish national ID card (DNI) contains a QR code readable via the miDNI mobile app. This QR encodes personal data with a digital signature for authenticity verification. The API provides a dedicated endpoint to decode and verify this data.
Endpoint
POST {API_URL}/api/qr/midni
Request
Send the raw QR payload (not an image) in Base64 or hexadecimal format:
{
"Base64Data": "BASE64_ENCODED_QR_PAYLOAD"
}
Or:
{
"HexData": "HEX_ENCODED_QR_PAYLOAD"
}
Successful response (200 OK)
{
"data": {
"documentNumber": "12345678Z",
"firstName": "NOMBRE",
"lastName": "APELLIDO1 APELLIDO2",
"firstSurname": "APELLIDO1",
"secondSurname": "APELLIDO2",
"dateOfBirth": "01/01/1990",
"gender": "M",
"nationality": "ESP",
"expiryDate": "31/12/2030",
"address": "CALLE EJEMPLO 1, 28001 MADRID",
"signatureValid": "true",
"dataExpired": "false"
},
"responseTime": 12,
"description": "miDNI QR (VDS) - Verification: Valid"
}
Response fields
| Field | Description |
|---|---|
documentNumber | DNI number |
firstName | Given names |
lastName | Full surnames |
firstSurname / secondSurname | Individual surnames |
dateOfBirth | Date of birth |
gender | Gender (M/F) |
nationality | Nationality (ISO code) |
expiryDate | Document expiry date |
address | Full registered address |
signatureValid | Whether the QR digital signature is valid |
dataExpired | Whether the QR data has expired |
Error responses
| Code | Meaning |
|---|---|
| 400 | Invalid data format, expired QR, or invalid signature |
| 401 | Unauthorized (invalid API key) |
| 402 | Insufficient credits |
| 500 | Internal server error |
cURL example
curl -X POST https://kyc.roommatik.com/api/qr/midni \
-H "Content-Type: application/json" \
-H "X-Api-Key: YOUR_API_KEY" \
-d '{"Base64Data": "YOUR_QR_BASE64_PAYLOAD"}'
JavaScript example
async function decodeMiDniQr(qrBase64Payload, apiKey) {
const response = await fetch('https://kyc.roommatik.com/api/qr/midni', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': apiKey,
},
body: JSON.stringify({ Base64Data: qrBase64Payload }),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'API error: ' + response.status);
}
return await response.json();
}
