> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.vlenseg.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.vlenseg.com/_mcp/server.

# Consent-based Digital Identity (CDI)

Consent-based Digital Identity (CDI) lets users who already have a validated identity in the **VLens Network** register with your tenant without repeating national ID capture or email OTP. The user explicitly consents via OTP before their identity is linked. CSO and NTRA re-checks run automatically against the identity already on file.

This is a **separate product flow** from [Digital Identity](/digital-identity) KYC (ID scan + liveness after registration) and from [E-Contracting](/e-contracting). Pick one registration path per user.

![Standard registration vs CDI — 8 user steps vs 2 user steps (phone and liveness)](https://fdr-prod-docs-files-public.s3.us-east-1.amazonaws.com/vlenseg-578354.docs.buildwithfern.com/8d7eb8ab58879285771b5a0be4a8f534d13068adab516ede3ae85073698ea1da/docs/assets/cdi-vs-standard.svg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIA6KXJSKKNFOCF7G4B%2F20260804%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20260804T204709Z&X-Amz-Expires=604800&X-Amz-Signature=21abf64ca24b3cbcdb0ad42ff5979a1e67cd4579685065b192848d4ac0b8c5bd&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject)

All CDI endpoints require only the `ApiKey` header — no bearer token. See [Authentication](/authentication) for the full list of API-key-only endpoints.

If `CheckExistenceOfEmailOrPhone` returns `hasCDI: false`, use the [standard registration flow](/user-profile#standard-registration-flow) instead. Do not call `StepVerifyCDI` or pass `useCdi: true` for those users.

---

## VLens Network architecture

CDI works because the user's digital identity already exists in the **VLens Network**. When a user verified with another tenant joins your app, Vlens matches their selfies against the ID already on file instead of asking them to scan their national ID again.

![VLens Network consent-based identity](https://fdr-prod-docs-files-public.s3.us-east-1.amazonaws.com/vlenseg-578354.docs.buildwithfern.com/9300c9a6358c8b445e5dc081c4d3515ddf0e98ea5f1f3c5f57c428c06ec5f366/docs/assets/cdi-vlens-network.svg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIA6KXJSKKNFOCF7G4B%2F20260804%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20260804T204709Z&X-Amz-Expires=604800&X-Amz-Signature=670235012b3079dd79d7ed0c8dd43955814daa7487bf273b3b20078477544ffc&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject)

| Concept             | Meaning                                                                           |
| ------------------- | --------------------------------------------------------------------------------- |
| **VLens Network**   | Shared pool of users who completed full KYC with any Vlens tenant                 |
| **`hasCDI: true`**  | User exists in the network — eligible for CDI at your tenant                      |
| **`useCdi: true`**  | OTP step records user consent to link their network identity to your organization |
| **`StepVerifyCDI`** | Liveness + face match against the on-file ID (no new ID photos)                   |

---

## How CDI works

```mermaid
sequenceDiagram
    participant App
    participant User
    participant Vlens
    participant CSO as CSO
    participant NTRA

    App->>Vlens: POST /CheckExistenceOfEmailOrPhone
    Vlens-->>App: hasCDI: true

    App->>User: Prompt for consent to link identity
    User-->>App: User approves

    App->>Vlens: POST /Register/StepVerifyPhone (useCdi: true)
    Vlens-->>User: CDI OTP SMS
    User-->>App: OTP entered

    App->>Vlens: POST /Register/StepVerifyPhone (validate OTP)
    Vlens-->>App: phoneNumberOtpRequestId confirmed

    App->>Vlens: POST /Register/StepVerifyCDI (3 selfies)
    Vlens->>CSO: Re-check ID on file
    CSO-->>Vlens: Match result
    Vlens->>NTRA: Re-check phone ownership
    NTRA-->>Vlens: Ownership result
    Vlens-->>App: CDI verification result

    App->>Vlens: POST /Register/StepCreate (SkipEmail: true)
    Vlens-->>App: accessToken, user verified
```

#### Only 2 user-facing steps

Phone OTP (consent) and liveness — no email OTP or ID capture.

#### Up to 70% faster onboarding

Skip repeated document capture for users already verified in the VLens Network.

#### Same security

CSO and NTRA re-checks run automatically against the ID already on file.

#### Higher conversions

Fewer steps means more users complete registration.

---

## Step overview

From the **user's perspective**, CDI is only two steps: phone OTP (consent) and liveness. Your app handles `CheckExistenceOfEmailOrPhone`, OTP validation, and `StepCreate` in the background.

```mermaid
flowchart TD
    subgraph User["What the user does"]
        U1[Phone OTP] --> U2[Liveness - 3 selfies]
    end
    U2 --> Done[Verified]
```

---

## Step 1 — Check existence

Call `CheckExistenceOfEmailOrPhone` before registration. When the response includes `hasCDI: true`, the user has a network identity eligible for CDI and can onboard via the CDI flow.

#### cURL

```bash
curl -X POST https://api.vlenseg.com/api/DigitalIdentity/CheckExistenceOfEmailOrPhone \
  -H "ApiKey: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumber": "+201234567890",
    "email": "user@example.com"
  }'
```

#### JavaScript

```javascript
const res = await fetch(
  "https://api.vlenseg.com/api/DigitalIdentity/CheckExistenceOfEmailOrPhone",
  {
    method: "POST",
    headers: { "ApiKey": API_KEY, "Content-Type": "application/json" },
    body: JSON.stringify({
      phoneNumber: "+201234567890",
      email: "user@example.com"
    })
  }
);
const { data } = await res.json();
if (data.hasCDI) {
  // user eligible for Consent-based Digital Identity (CDI) flow
}
```

#### Python

```python
res = requests.post(
    "https://api.vlenseg.com/api/DigitalIdentity/CheckExistenceOfEmailOrPhone",
    headers={"ApiKey": API_KEY},
    json={"phoneNumber": "+201234567890", "email": "user@example.com"}
)
data = res.json()["data"]
```

**Response (CDI-eligible user):**

```json
{
  "data": {
    "hasCDI": true
  },
  "error_code": null
}
```

---

## Step 2 — Send CDI phone OTP

For CDI onboarding, send the phone OTP with `useCdi: true`. VLens notifies the user they are consenting to link their network identity to your organization. The OTP confirms phone possession and CDI approval.

#### cURL

```bash
curl -X POST https://api.vlenseg.com/api/DigitalIdentity/Register/StepVerifyPhone \
  -H "ApiKey: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumber": "+201234567890",
    "useCdi": true
  }'
```

#### JavaScript

```javascript
const res = await fetch(
  "https://api.vlenseg.com/api/DigitalIdentity/Register/StepVerifyPhone",
  {
    method: "POST",
    headers: { "ApiKey": API_KEY, "Content-Type": "application/json" },
    body: JSON.stringify({
      phoneNumber: "+201234567890",
      useCdi: true
    })
  }
);
const otpRequestId = (await res.json()).data.phoneNumberOtpRequestId;
```

#### Python

```python
res = requests.post(
    "https://api.vlenseg.com/api/DigitalIdentity/Register/StepVerifyPhone",
    headers={"ApiKey": API_KEY},
    json={"phoneNumber": "+201234567890", "useCdi": True}
)
otp_request_id = res.json()["data"]["phoneNumberOtpRequestId"]
```

---

## Step 3 — Validate CDI phone OTP

Validate the OTP using the same endpoint:

#### cURL

```bash
curl -X POST https://api.vlenseg.com/api/DigitalIdentity/Register/StepVerifyPhone \
  -H "ApiKey: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumber": "+201234567890",
    "phoneNumberOtpRequestId": "OTP_REQUEST_ID",
    "phoneNumberOtp": "123456"
  }'
```

#### JavaScript

```javascript
await fetch(
  "https://api.vlenseg.com/api/DigitalIdentity/Register/StepVerifyPhone",
  {
    method: "POST",
    headers: { "ApiKey": API_KEY, "Content-Type": "application/json" },
    body: JSON.stringify({
      phoneNumber: "+201234567890",
      phoneNumberOtpRequestId: otpRequestId,
      phoneNumberOtp: "123456"
    })
  }
);
```

#### Python

```python
requests.post(
    "https://api.vlenseg.com/api/DigitalIdentity/Register/StepVerifyPhone",
    headers={"ApiKey": API_KEY},
    json={
        "phoneNumber": "+201234567890",
        "phoneNumberOtpRequestId": otp_request_id,
        "phoneNumberOtp": "123456"
    }
)
```

---

## Step 4 — CDI liveness and face match

Submit three selfie frames. Vlens checks liveness, matches the selfies against the validated ID already on file, and performs CSO and NTRA re-checks in the background.

![Three selfie frames for StepVerifyCDI](https://fdr-prod-docs-files-public.s3.us-east-1.amazonaws.com/vlenseg-578354.docs.buildwithfern.com/97257eb8ca3f8934aaf83b3ffaa06be3a4abe5fe5f8198dc46b2745aeb2a4652/docs/assets/cdi-selfie-capture.svg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIA6KXJSKKNFOCF7G4B%2F20260804%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20260804T204709Z&X-Amz-Expires=604800&X-Amz-Signature=a17dfe0c952a2b2c7a22d652a3147ef7175015813000f2b091de5be2eec8fc60&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject)

**Capture rules:** All three images must be of the same person and captured within **0.5 seconds** of each other. Identical frames or frames taken too far apart will fail liveness.

### Image compression

Encode each selfie as base64 JPEG or PNG before sending in `scanTransaction`. Compress for speed and reliability:

| Guideline   | Recommendation                                |
| ----------- | --------------------------------------------- |
| Target size | **Under 500 KB** per image (best performance) |
| Hard limit  | 3 MB per image                                |
| Resolution  | Max 25 megapixels                             |
| Format      | JPEG recommended for selfies                  |
| Encoding    | Base64 in JSON body                           |

```javascript
// Example: resize before upload (conceptual)
// Use your platform's image library — e.g. react-native-image-resizer, browser canvas, or server-side sharp
const compressed = await resizeImage(selfieUri, { maxWidth: 1024, quality: 0.85 });
const base64 = await toBase64(compressed);
```

See [Errors](/error-codes) for limit-related error codes (`5002`, `5003`, `5006`).

#### cURL

```bash
curl -X POST https://api.vlenseg.com/api/DigitalIdentity/Register/StepVerifyCDI \
  -H "ApiKey: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumberOtpRequestId": "OTP_REQUEST_ID",
    "scanTransaction": {
      "face_1": "BASE64_SELFIE_1",
      "face_2": "BASE64_SELFIE_2",
      "face_3": "BASE64_SELFIE_3"
    }
  }'
```

#### JavaScript

```javascript
await fetch(
  "https://api.vlenseg.com/api/DigitalIdentity/Register/StepVerifyCDI",
  {
    method: "POST",
    headers: { "ApiKey": API_KEY, "Content-Type": "application/json" },
    body: JSON.stringify({
      phoneNumberOtpRequestId: otpRequestId,
      scanTransaction: {
        face_1: base64Selfie1,
        face_2: base64Selfie2,
        face_3: base64Selfie3
      }
    })
  }
);
```

#### Python

```python
requests.post(
    "https://api.vlenseg.com/api/DigitalIdentity/Register/StepVerifyCDI",
    headers={"ApiKey": API_KEY},
    json={
        "phoneNumberOtpRequestId": otp_request_id,
        "scanTransaction": {
            "face_1": base64_selfie_1,
            "face_2": base64_selfie_2,
            "face_3": base64_selfie_3
        }
    }
)
```

---

## Step 5 — Create the user

If CDI verification succeeds, complete registration with `SkipEmail: true`:

#### cURL

```bash
curl -X POST https://api.vlenseg.com/api/DigitalIdentity/Register/StepCreate \
  -H "ApiKey: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "+201234567890",
    "password": "USER_PASSWORD",
    "phoneNumberOtpRequestId": "OTP_REQUEST_ID",
    "geoLocation": { "latitude": 30.0444, "longitude": 31.2357 },
    "imei": "DEVICE_IMEI",
    "SkipEmail": true
  }'
```

#### JavaScript

```javascript
const res = await fetch(
  "https://api.vlenseg.com/api/DigitalIdentity/Register/StepCreate",
  {
    method: "POST",
    headers: { "ApiKey": API_KEY, "Content-Type": "application/json" },
    body: JSON.stringify({
      username: "+201234567890",
      password: userPassword,
      phoneNumberOtpRequestId: otpRequestId,
      geoLocation: { latitude: 30.0444, longitude: 31.2357 },
      imei: deviceImei,
      SkipEmail: true
    })
  }
);
const userToken = (await res.json()).data.accessToken;
```

#### Python

```python
res = requests.post(
    "https://api.vlenseg.com/api/DigitalIdentity/Register/StepCreate",
    headers={"ApiKey": API_KEY},
    json={
        "username": "+201234567890",
        "password": user_password,
        "phoneNumberOtpRequestId": otp_request_id,
        "geoLocation": {"latitude": 30.0444, "longitude": 31.2357},
        "imei": device_imei,
        "SkipEmail": True
    }
)
user_token = res.json()["data"]["accessToken"]
```