> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tuteliq.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Age & Identity Verification

> Production-grade age and identity verification with document intelligence, biometric matching, liveness detection, and multi-layer fraud prevention — covering 45 countries

Tuteliq Verification is a production-grade identity and age verification pipeline that combines document intelligence, biometric matching, liveness detection, and multi-layer fraud prevention in a single API call.

Most verification providers give you OCR and a face match. Tuteliq cross-references **every data source on the document against every other** — MRZ check digits, barcode data, OCR text, front vs. back, document vs. selfie — and flags any inconsistency as potential tampering. This catches forgeries that pass single-layer checks.

<Card title="Cross-device flow" icon="arrows-rotate" href="/verification/cross-device-flow" horizontal>
  **Recommended integration path for mobile, web, and native apps.** Create a session on your server, open the URL in a webview or QR code, get the result back via Server-Sent Events. Includes code samples for iOS (Swift), Android (Kotlin), React Native, Flutter, and web (iframe).
</Card>

<CardGroup cols={2}>
  <Card title="Age Verification" icon="id-card" href="#age-verification">
    Confirm user age via document analysis, biometric estimation, or both. Direct-submission API for same-device flows.
  </Card>

  <Card title="Identity Verification" icon="fingerprint" href="#identity-verification">
    Full identity confirmation with document authentication, face matching, liveness detection, and fraud prevention.
  </Card>

  <Card title="Document Checks" icon="file-shield" href="/verification/document-checks">
    45 countries with algorithmic validation. ICAO 9303 MRZ. PDF417 barcode decoding.
  </Card>

  <Card title="Fraud Prevention" icon="shield-halved" href="/verification/fraud-prevention">
    7 cross-referencing layers, recapture detection, LLM-powered authenticity analysis, and IP geolocation checks.
  </Card>
</CardGroup>

## What makes Tuteliq different

<CardGroup cols={3}>
  <Card title="45-Country Document Validation" icon="globe">
    Algorithmic check digit validation for CPF, personnummer, Aadhaar, Codice Fiscale, CURP, SSN, and 39 more. Not just format checks — full mathematical verification.
  </Card>

  <Card title="7-Layer Cross-Referencing" icon="layer-group">
    MRZ vs. OCR. Barcode vs. OCR. Front vs. back. Document vs. selfie. Declared type vs. detected type. IP vs. document country. OCR confidence gating. Every inconsistency is flagged.
  </Card>

  <Card title="AI-Powered Authenticity" icon="brain">
    Vision model analyzes document layout, security features, fonts, and color consistency against known templates. Detects screen photos, printout recaptures, and digital manipulation.
  </Card>
</CardGroup>

## Tier availability

| Feature                          | Minimum Tier        | Credits             |
| -------------------------------- | ------------------- | ------------------- |
| Age Verification (liveness only) | Pro (\$99/mo)       | 10 per verification |
| Age Verification (full)          | Pro (\$99/mo)       | 20 per verification |
| Identity Verification            | Business (\$349/mo) | 25 per verification |

***

## Age Verification

`POST /v1/verification/age`

Verify a user's age through document analysis, biometric age estimation, or both. Returns a verified age range, confidence score, and detailed document intelligence.

### Verification methods

| Method        | How it works                                                                     | Best for                                                        |
| ------------- | -------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| **Document**  | Extracts DOB from government ID via OCR, MRZ parsing, and barcode decoding       | High-assurance age gates, parental consent                      |
| **Biometric** | Estimates age from a selfie using vision AI (child/teen/adult classification)    | Frictionless age checks, onboarding flows                       |
| **Combined**  | Document + biometric with cross-reference — flags age inconsistencies > 10 years | Maximum assurance — verifies the document belongs to the person |

### Request

<CodeGroup>
  ```typescript Node.js theme={"dark"}
  const result = await tuteliq.verifyAge({
    document: fs.createReadStream('id-front.jpg'),     // Government-issued ID
    documentBack: fs.createReadStream('id-back.jpg'),  // Optional — enables barcode reading + back cross-ref
    selfie: fs.createReadStream('selfie.jpg'),          // Optional — for biometric estimation
    method: 'combined',                                  // 'document' | 'biometric' | 'combined'
  });

  console.log(result.verified);        // true
  console.log(result.estimated_age);   // 15
  console.log(result.age_range);       // "13-15"
  console.log(result.is_minor);        // true
  console.log(result.confidence);      // 0.97
  console.log(result.document_type);   // "passport"
  ```

  ```python Python theme={"dark"}
  result = client.verify_age(
      document=open("id-front.jpg", "rb"),
      document_back=open("id-back.jpg", "rb"),  # Optional
      selfie=open("selfie.jpg", "rb"),
      method="combined",
  )

  print(result.verified)        # True
  print(result.estimated_age)   # 15
  print(result.age_range)       # "13-15"
  print(result.is_minor)        # True
  ```

  ```bash cURL theme={"dark"}
  curl -X POST https://api.tuteliq.ai/v1/verification/age \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -F "document=@id-front.jpg" \
    -F "document_back=@id-back.jpg" \
    -F "selfie=@selfie.jpg" \
    -F "method=combined"
  ```
</CodeGroup>

### Response

```json theme={"dark"}
{
  "verified": true,
  "estimated_age": 15,
  "age_range": "13-15",
  "is_minor": true,
  "confidence": 0.97,
  "method": "combined",
  "document_type": "passport",
  "document_country": "GB",
  "biometric_age": 15,
  "document_age": 15,
  "document": {
    "ocr_confidence": 94,
    "mrz_valid": true,
    "document_number_valid": true,
    "expired": false
  },
  "credits_used": 20
}
```

### Response fields

| Field                            | Type    | Description                                                   |
| -------------------------------- | ------- | ------------------------------------------------------------- |
| `verified`                       | boolean | Whether age verification succeeded                            |
| `estimated_age`                  | integer | Best estimate of user's age                                   |
| `age_range`                      | string  | Tuteliq age bracket: `under-10`, `10-12`, `13-15`, or `14-17` |
| `is_minor`                       | boolean | Whether the user is under 18                                  |
| `confidence`                     | float   | Confidence score (0.0-1.0)                                    |
| `method`                         | string  | Method used: `document`, `biometric`, or `combined`           |
| `document_type`                  | string  | Detected document type                                        |
| `document_country`               | string  | ISO 3166-1 alpha-2 country code                               |
| `biometric_age`                  | integer | Age estimated from selfie (if provided)                       |
| `document_age`                   | integer | Age from document DOB (if provided)                           |
| `document.ocr_confidence`        | integer | OCR confidence percentage (0-100)                             |
| `document.mrz_valid`             | boolean | Whether MRZ check digits passed (if MRZ present)              |
| `document.document_number_valid` | boolean | Whether document number passed algorithmic validation         |
| `document.expired`               | boolean | Whether the document has expired                              |
| `credits_used`                   | integer | Credits consumed                                              |

### Age extraction sources

DOB is extracted from multiple sources and cross-referenced. Priority order:

1. **MRZ** (Machine Readable Zone) — Most reliable. ICAO 9303 check digit validated.
2. **PDF417 barcode** — US/Canadian driver's licenses. AAMVA-encoded structured data.
3. **OCR labels** — Text patterns like "Date of Birth:", "DOB:", date formats.
4. **Selfie estimation** — Vision AI age bracket classification (fallback).

When multiple sources disagree, the verification flags the inconsistency.

### Supported documents

| Document Type    | Coverage                                                                             |
| ---------------- | ------------------------------------------------------------------------------------ |
| Passport         | All ICAO-compliant passports worldwide (MRZ validated)                               |
| National ID card | All countries with MRZ-equipped cards + 45 countries with document number validation |
| Driver's license | All countries via OCR + US/Canada via PDF417 barcode                                 |
| Residence permit | Via OCR text extraction                                                              |

Documents must be a clear, well-lit photo in JPEG or PNG format. Maximum file size: 10MB.

***

## Identity Verification

`POST /api/v1/verify/identity/submit`

Full KYC identity verification combining document authentication, face matching, liveness detection, and country-specific national-ID validation. Business plan and above.

### What it checks

Every identity verification runs **all** of these checks automatically:

| Check                              | What it does                                                                                                                       |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| **Document OCR**                   | Multilingual text extraction (Tesseract sidecar with multi-pass MRZ-tolerance pipeline)                                            |
| **MRZ validation**                 | ICAO 9303 TD1/TD2/TD3 check-digit validation via `mrz` + `mrz-fast` libraries                                                      |
| **Document number validation**     | Algorithmic check-digit verification for 45 countries (personnummer, CPF, Aadhaar, CURP, BSN, NIF, NINO, etc.)                     |
| **Face detection + matching**      | `@vladmandic/face-api` model in dedicated sidecar; vector-distance match between document portrait and selfie                      |
| **Liveness**                       | Multi-signal: landmark motion, blink, texture/moiré, depth cues, cross-frame consistency, single-use cryptographic challenge token |
| **Synthetic / deepfake detection** | CLIP-based classifier (`deep-image-analysis` sidecar) on the selfie                                                                |
| **Document expiry**                | Checks expiry from labels and MRZ                                                                                                  |
| **Country resolution**             | Inferred from MRZ issuing state, OCR text, or explicit `country_code` parameter                                                    |

### Request

```bash cURL theme={"dark"}
curl -X POST https://api.tuteliq.ai/api/v1/verify/identity/submit \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "id_front=@id-front.jpg" \
  -F "id_back=@id-back.jpg" \
  -F "selfie=@selfie.jpg" \
  -F "liveness_token=YOUR_LIVENESS_TOKEN" \
  -F "document_type=passport"
```

Pass `passport=@passport.jpg` instead of `id_front`/`id_back` when verifying a passport. The `liveness_token` is issued by the liveness challenge step (see `Liveness detection` doc).

### Response

```json theme={"dark"}
{
  "verification_id": "string",
  "status": "verified",
  "full_name": "string",
  "first_name": "string",
  "last_name": "string",
  "date_of_birth": "string (ISO 8601)",
  "document_type": "passport",
  "country_code": "SWE",
  "country_name": "Sweden",
  "document_number": "string",
  "document_number_valid": true,
  "sex": "M",
  "nationality": "string",
  "place_of_birth": "string",
  "height_cm": 0,
  "issuing_authority": "string",
  "issue_date": "string (ISO 8601)",
  "expiration_date": "string (ISO 8601)",
  "national_id": {
    "value": "string",
    "valid": true,
    "type": "personnummer",
    "country_code": "SWE"
  },
  "mrz_valid": true,
  "liveness": { "valid": true, "reason": "string" },
  "face_match": { "matched": true, "distance": 0.0, "confidence": 0.0 },
  "credits_used": 0
}
```

All identity fields are `null` when not extractable (e.g. `height_cm` for national ID cards that don't print height, or `national_id` for countries without a per-country extractor in this release).

### Response fields

| Field                   | Type            | Description                                                        |
| ----------------------- | --------------- | ------------------------------------------------------------------ |
| `verification_id`       | string \| null  | Persistent ID for the record                                       |
| `status`                | string          | `verified` \| `failed` \| `needs_review`                           |
| `full_name`             | string \| null  | Combined name as extracted from the document                       |
| `first_name`            | string \| null  | Given names — prefers MRZ-parsed, falls back to heuristic split    |
| `last_name`             | string \| null  | Surname — same source preference                                   |
| `date_of_birth`         | string \| null  | ISO 8601                                                           |
| `document_type`         | string \| null  | `passport` \| `national_id` \| `drivers_license`                   |
| `country_code`          | string \| null  | ISO 3166-1 alpha-3                                                 |
| `country_name`          | string \| null  | English country name                                               |
| `document_number`       | string \| null  | Document number as printed / MRZ-parsed                            |
| `document_number_valid` | boolean \| null | Country-specific algorithmic validation passed                     |
| `sex`                   | string \| null  | `M` \| `F`                                                         |
| `nationality`           | string \| null  | Text-extracted; MRZ fallback when available                        |
| `place_of_birth`        | string \| null  | As printed on the document                                         |
| `height_cm`             | integer \| null | Height in cm (range 50–250) when printed on the document           |
| `issuing_authority`     | string \| null  | Issuing body (e.g. `POLISMYNDIGHETEN` for Swedish passports)       |
| `issue_date`            | string \| null  | ISO 8601, when printed                                             |
| `expiration_date`       | string \| null  | ISO 8601                                                           |
| `national_id`           | object \| null  | Country-specific personal/national ID (see below)                  |
| `mrz_valid`             | boolean \| null | ICAO 9303 check digits all passed (null if no MRZ on the document) |
| `liveness`              | object          | `{ valid: boolean, reason?: string }`                              |
| `face_match`            | object \| null  | `{ matched: boolean, distance: number, confidence: number }`       |
| `credits_used`          | integer         | Credits consumed                                                   |

### `national_id` object

Returned only when the document's country has a per-country extractor in this release.

| Field          | Type    | Description                                                                                                       |
| -------------- | ------- | ----------------------------------------------------------------------------------------------------------------- |
| `value`        | string  | The ID number as printed (with separator preserved when present)                                                  |
| `valid`        | boolean | Country-specific algorithmic validation passed                                                                    |
| `type`         | string  | Canonical name — `personnummer` (SWE), `fødselsnummer` (NOR), `henkilötunnus` (FIN), `DNI/NIE` (ESP), `CPF` (BRA) |
| `country_code` | string  | ISO 3166-1 alpha-3                                                                                                |

Countries with validators but no extractor yet (returns `null` for `national_id` until extractors land): DEU, FRA, ITA, NLD, GBR, IRL, CHE, AUT, CZE, ROU, HRV, BGR, GRC, HUN, EST, LVA, LTU, ARG, MEX, CHL, COL, PER, ECU, URY, CAN, USA, JPN, KOR, ZAF, THA, TUR.

### Verification outcomes

| Status         | Meaning                                 | Examples                                                                |
| -------------- | --------------------------------------- | ----------------------------------------------------------------------- |
| `verified`     | All checks passed                       | Valid document, face match, liveness passed                             |
| `failed`       | Hard failure — definitive fraud signal  | Liveness failed, face mismatch, document expired                        |
| `needs_review` | Soft failure — human review recommended | Low OCR confidence, low face-match confidence, partial field extraction |

***

## Integration patterns

### Age gate on sign-up

```typescript theme={"dark"}
// 1. Verify age during sign-up
const verification = await tuteliq.verifyAge({
  selfie: selfieStream,
  method: 'biometric',
});

// 2. Store the verified age group
const ageGroup = verification.age_range;  // "13-15"

// 3. Use verified age group in all safety calls
const safety = await tuteliq.detectUnsafe({
  text: messageContent,
  ageGroup: ageGroup,  // Properly calibrated risk scoring
});
```

### Parental consent flow (COPPA)

```typescript theme={"dark"}
// 1. Verify child's age
const childAge = await tuteliq.verifyAge({
  selfie: childSelfieStream,
  method: 'biometric',
});

if (childAge.estimated_age < 13) {
  // 2. Verify parent's identity
  const parent = await tuteliq.verifyIdentity({
    document: parentIdStream,
    selfie: parentSelfieStream,
  });

  if (parent.verified && !parent.is_minor) {
    await grantParentalConsent(childUserId, parent);
  }
}
```

### Moderator/admin verification

```typescript theme={"dark"}
const identity = await tuteliq.verifyIdentity({
  document: documentStream,
  selfie: selfieStream,
});

if (identity.verified && !identity.is_minor) {
  await grantModeratorRole(userId);
}
```

## Error handling

Verification errors use the `VERIFY_11xxx` code range:

| Code                                          | HTTP | Description                                                                              |
| --------------------------------------------- | ---- | ---------------------------------------------------------------------------------------- |
| `VERIFY_11001` `VERIFY_OCR_FAILED`            | 500  | OCR could not extract text from the document — retake under better lighting / less glare |
| `VERIFY_11002` `VERIFY_NO_DOB_FOUND`          | 400  | Date of birth could not be extracted — try a clearer page / different document           |
| `VERIFY_11003` `VERIFY_FACE_NOT_DETECTED`     | 400  | No face detected in the selfie or document portrait                                      |
| `VERIFY_11004` `VERIFY_FACE_MISMATCH`         | 400  | Selfie does not match the document portrait                                              |
| `VERIFY_11005` `VERIFY_LIVENESS_FAILED`       | 400  | Liveness check failed — possible spoofing or stale capture                               |
| `VERIFY_11006` `VERIFY_SESSION_NOT_FOUND`     | 404  | Session ID unknown — recreate the session                                                |
| `VERIFY_11007` `VERIFY_SESSION_EXPIRED`       | 410  | Session past TTL — recreate the session                                                  |
| `VERIFY_11008` `VERIFY_SESSION_INVALID_TOKEN` | 401  | Session token invalid — recreate the session, do not replay                              |
| `VERIFY_11009` `VERIFY_SESSION_ALREADY_USED`  | 409  | Session token already consumed                                                           |
| `VERIFY_11010` `VERIFY_SESSION_WRONG_STATE`   | 409  | Session not in the right state for this call                                             |
| `VERIFY_11011` `VERIFY_NOT_ENABLED`           | 403  | Verification not provisioned for this account                                            |
| `VERIFY_11012` `VERIFY_IDENTITY_NOT_ENABLED`  | 403  | Identity mode requires Business plan or above                                            |
| `VERIFY_11013` `VERIFY_NO_COUNTRY_FOUND`      | 400  | Country could not be inferred — pass `country_code` explicitly                           |

```bash cURL example error response theme={"dark"}
{
  "error": {
    "code": "VERIFY_11005",
    "message": "Liveness check failed",
    "request_id": "req_...",
    "retryable": false,
    "status_url": "https://tuteliq.ai/status",
    "documentation_url": "https://tuteliq.ai/docs/errors#VERIFY_11005"
  }
}
```

## Data handling

<Warning>
  Verification involves sensitive personal data. Tuteliq processes documents and selfies in real time and does **not** store images or extracted PII after the verification is complete. Only the verification result (age range, pass/fail) is retained. See the [GDPR](/gdpr) page for data handling details.
</Warning>

## Next steps

<CardGroup cols={3}>
  <Card title="Document Checks" icon="file-shield" href="/verification/document-checks">
    Deep dive into 45-country document validation, MRZ parsing, and barcode reading.
  </Card>

  <Card title="Liveness Detection" icon="video" href="/verification/liveness-detection">
    How visual liveness analysis prevents spoofing attacks.
  </Card>

  <Card title="Fraud Prevention" icon="shield-halved" href="/verification/fraud-prevention">
    Multi-layer cross-referencing, recapture detection, and authenticity analysis.
  </Card>
</CardGroup>
