KYC Integration – Identity Document Capture

Purpose: This documentation describes how to integrate the Roommatik identity document verification system. You can use the API directly, the web component with automatic KYC verification, or the web component in capture-only mode to process images with your own logic.

View Postman Collection

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:

OptionDescriptionUse 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

HeaderValueDescription
Content-Typeapplication/jsonContent type
X-Api-KeyYour API keyAuthentication

Request body

{
    "image": "BASE64_IMAGE_WITHOUT_PREFIX",
    "returnType": ["Data", "Face"],
    "isMappingApplied": true
}
FieldTypeDefaultDescription
imagestringBase64 encoded image without the data:image/...;base64, prefix. Required unless imageUrl or file is provided.
imageUrlstringPresigned S3 URL pointing to the document image. Alternative to image.
returnTypestring[]["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"].
isMappingAppliedbooleanfalseWhen true, normalizes raw OCR field names into standardized output (e.g. MRZ_DocumentNumberdocumentNumber). Strongly recommended.
face_vectorbooleanfalseWhen true, computes a 512-dimensional face embedding. Requires "Face" in returnType. See section 13.
debugbooleanfalseWhen true, includes a MappingDebug field in the response with step-by-step mapping details.
detect_orientationbooleanfalseWhen true, automatically detects and corrects the orientation of the document image.
Important: For two-sided documents (identity card, residence permit), you can either send two separate requests (one for front, one for back) and merge the data in your application, or use the Dual-Image endpoint (/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:

FieldDescription
firstNameFirst name
lastNameLast name
dateOfBirthDate of birth
documentNumberDocument number
expiryDateExpiry date
genderGender (M/F)
nationalityNationality (ISO code)
mrzMachine 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>
Note: When 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

AttributeTypeDefaultDescription
mode"capture-only" | "full""capture-only"Operation mode
api-urlstringKYC API base URL (required in full mode)
api-keystringAPI key (required in full mode)
langstring"en"Language: en, es, fr, de, it, pt
primary-colorstring (hex)#3b82f6Primary UI color
two-sidedbooleantrueScan two sides (front + back). Set to false for single-sided documents (e.g. passport)
max-image-widthnumber800Maximum captured image width (px)
jpeg-qualitynumber0.70JPEG quality (0 to 1)
auto-startbooleantrueAuto-start camera on mount
combine-imagesbooleanfalseWhen true, front and back images are combined into a single image and sent as one API call
labelsJSON stringCustom text labels (overrides built-in translations)

Events

EventModeDetail (e.detail)
kyc-capturedcapture-only{ frontImage: string, backImage: string | null, combinedImage: string | null }
kyc-completefull{ success: boolean, data?: object, error?: string, rawResponse?: object }
kyc-errorbothError object
kyc-cancelboth

8. Document sides

Use the two-sided attribute to indicate whether the document has one or two sides:

two-sidedBehaviourExample
true (default)Scans front + backID card, residence permit
falseScans one side onlyPassport

9. Error handling

API response codes

CodeMeaning
200Data extracted successfully
204No data could be extracted (low quality image or unrecognized document)
400Bad request (invalid parameters)
401Unauthorized (invalid or missing API key)
403Forbidden (account blocked, license expired, IP not whitelisted, or rate limit exceeded)
429Too many requests (rate limit exceeded)
500Internal server error

Common error messages

MessageCause
License ExpiredYour license has expired. Contact your administrator.
License BlockedYour license has been blocked.
Account is BlockedThe account is blocked.
Access forbidden. Your IP is not whitelisted.Your IP is not in the whitelist.
Rate limit exceededYou have exceeded the per-minute or per-day request limit.
No data could be extracted from the documentThe image does not contain a recognizable document or is low quality.

10. Limits and restrictions

ConceptDetails
Rate limitingConfigurable per account (per minute and per day)
Timeout30 seconds by default
Image formatJPEG or PNG (Base64, presigned URL, or multipart file upload)
HTTPS requiredThe web component requires HTTPS to access the camera
Supported browsersChrome, Firefox, Safari, Edge (latest versions)
Max image sizeRecommended 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

returnTypeUse caseResponse
["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.
Fastest integration: OCR only
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, ... ]
}
FieldTypeDescription
FaceVectornumber[]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
}
FieldTypeDefaultDescription
image1string (base64) or FileFront side of the document.
image2string (base64) or FileBack side of the document.
image1UrlstringAlternative: S3 presigned URL for image 1.
image2UrlstringAlternative: S3 presigned URL for image 2.
combineBothSidesbooleanfalseWhen true, the server combines both images into one before processing. When false, each side is processed independently.
returnTypestring[]["Data"]Same as standard endpoint.
isMappingAppliedbooleanfalseNormalizes field names and merges fields from both sides, keeping the highest-scoring value.
face_vectorbooleanfalseCompute face embedding. Requires "Face" in returnType.
detect_orientationbooleanfalseAuto-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

ScenarioRecommended approach
Simple integration, minimal codeTwo 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"
}
Note: This endpoint expects the raw binary data extracted from the QR code, not an image of the QR. Use a QR scanner library to extract the payload first.

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

FieldDescription
documentNumberDNI number
firstNameGiven names
lastNameFull surnames
firstSurname / secondSurnameIndividual surnames
dateOfBirthDate of birth
genderGender (M/F)
nationalityNationality (ISO code)
expiryDateDocument expiry date
addressFull registered address
signatureValidWhether the QR digital signature is valid
dataExpiredWhether the QR data has expired

Error responses

CodeMeaning
400Invalid data format, expired QR, or invalid signature
401Unauthorized (invalid API key)
402Insufficient credits
500Internal 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();
}