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

# Document policy

> Choose which documents you accept and check the extracted data against what you expect.

`document_policy` controls two separate things: which documents are eligible at all, and
whether the data extracted from an eligible document matches values you already hold.

Applies to `DOCUMENT_AND_BIOMETRIC` and `DOCUMENT_ONLY`. Ignored for `BIOMETRIC_ONLY`.

```json lines theme={null}
{
  "document_policy": {
    "document_eligibility": {
      "filter_mode": "ALLOW",
      "target_documents": [
        { "country": "AR", "type": "NATIONAL_ID", "allow_expired": false }
      ]
    },
    "data_matching": {
      "given_names": { "value": "Ana Maria", "similarity_min_score": 0.85 },
      "surnames":    { "value": "Perez",     "similarity_min_score": 0.85 },
      "gender": "F",
      "document_type": "NATIONAL_ID",
      "document_issuing_country": "AR",
      "document_number": "20123456",
      "date_of_birth": "1996-01-09"
    }
  }
}
```

## Document eligibility

<ParamField body="document_eligibility.filter_mode" type="string">
  How to read `target_documents`.

  * `ALLOW` — **only** the listed documents are accepted
  * `REJECT` — every supported document is accepted **except** the listed ones
</ParamField>

<ParamField body="document_eligibility.target_documents" type="array">
  The list `filter_mode` operates on.

  <Expandable title="target document">
    <ParamField body="country" type="string" required>
      ISO 3166-1 alpha-2 country code — `AR`, `CO`, `MX`.
    </ParamField>

    <ParamField body="type" type="string" required>
      `NATIONAL_ID`, `PASSPORT`, `DRIVER_LICENSE`, `RESIDENT_PERMIT` or `TEMP_PERMIT_ID`.
    </ParamField>

    <ParamField body="allow_expired" type="boolean" required>
      Whether an expired document of this exact country and type is accepted.
    </ParamField>
  </Expandable>
</ParamField>

Omit `document_eligibility` entirely to accept every document chmod supports.

<Warning>
  `filter_mode: "ALLOW"` with an empty `target_documents` accepts nothing — every
  transaction is rejected. chmod flags this with a `CONFIG_TARGET_DOCUMENTS_EMPTY`
  warning, but it will not fix it for you.
</Warning>

### Expiry

`allow_expired` is set per entry, not globally, so you can accept an expired national ID
while still requiring a valid passport:

```json lines theme={null}
{
  "filter_mode": "ALLOW",
  "target_documents": [
    { "country": "AR", "type": "NATIONAL_ID", "allow_expired": true  },
    { "country": "AR", "type": "PASSPORT",    "allow_expired": false }
  ]
}
```

An expired document with `allow_expired: false` emits `EXPIRED_DOCUMENT`. A document whose
issue date is in the future emits `DOCUMENT_NOT_YET_VALID` regardless of this setting.

### Supported documents

| Country | National ID | Passport | Driver licence | Resident permit | Temp permit ID |
| - | :-: | :-: | :-: | :-: | :-: |
| Argentina (`AR`) | Yes | Yes | Yes | — | — |
| Bolivia (`BO`) | Yes | Yes | Yes | — | — |
| Brazil (`BR`) | Yes | Yes | Yes | — | — |
| Chile (`CL`) | Yes | Yes | Yes | — | — |
| Colombia (`CO`) | Yes | Yes | Yes | Yes | Yes |
| Costa Rica (`CR`) | Yes | Yes | Yes | — | — |
| Dominican Republic (`DO`) | Yes | Yes | Yes | — | — |
| Ecuador (`EC`) | Yes | Yes | Yes | — | — |
| El Salvador (`SV`) | Yes | Yes | Yes | — | — |
| Guatemala (`GT`) | Yes | Yes | Yes | — | — |
| Honduras (`HN`) | Yes | Yes | Yes | — | — |
| Mexico (`MX`) | Yes | Yes | Yes | — | — |
| Nicaragua (`NI`) | Yes | Yes | Yes | — | — |
| Panama (`PA`) | Yes | Yes | Yes | — | — |
| Paraguay (`PY`) | Yes | Yes | Yes | — | — |
| Peru (`PE`) | Yes | Yes | Yes | — | — |
| Uruguay (`UY`) | Yes | Yes | Yes | — | — |

A document outside this matrix emits `DOCUMENT_TYPE_NOT_SUPPORTED`. One that is supported
but not permitted by your `document_eligibility` emits `DOCUMENT_NOT_ELIGIBLE` — two
different problems worth distinguishing when you handle them.

## Data matching

`data_matching` compares what the document says against values you supply. Every field is
optional — include only what you actually hold and actually want enforced.

All mismatches are `REJECT`. There is no policy action to soften them: if you supply a
value, you are asserting it must match.

### Fuzzy fields

Names are compared by text similarity, because transliteration, accents, middle names and
compound surnames all make exact matching useless in practice.

<ParamField body="given_names" type="object">
  `{ "value": "Ana Maria", "similarity_min_score": 0.85 }`

  Rejects with `NAME_MISMATCH` when similarity falls below the threshold. The issue
  carries `{ score, threshold }` so you can see how close it was.
</ParamField>

<ParamField body="surnames" type="object">
  `{ "value": "Perez", "similarity_min_score": 0.85 }`

  Rejects with `SURNAME_MISMATCH`, also carrying `{ score, threshold }`.
</ParamField>

`similarity_min_score` ranges from `0.1` to `1.0`, where `1.0` demands a character-for-character
match.

| Threshold | Behaviour |
| - | - |
| `1.0` | Exact match. Rejects `JOSE` against `JOSÉ`. |
| `0.85` | Tolerates accents, casing and minor OCR slips. A reasonable starting point. |
| `0.7` | Tolerates a missing middle name or a compound surname reordered. |
| `< 0.6` | Loose enough that unrelated names start passing. |

<Info>
  Names on documents are usually printed uppercase and unaccented, while your database
  probably holds them as the user typed them. `0.85` absorbs that difference; `1.0` will
  reject a large share of legitimate users.
</Info>

### Exact fields

These must match exactly. None of them carry `details` — you already know the value you
sent, and the extracted value is in `result_data.document.data`.

<ParamField body="gender" type="string">
  `M`, `F` or `X`. Rejects with `GENDER_MISMATCH`.
</ParamField>

<ParamField body="document_type" type="string">
  `NATIONAL_ID`, `PASSPORT`, `DRIVER_LICENSE`, `RESIDENT_PERMIT` or `TEMP_PERMIT_ID`.
  Rejects with `EXPECTED_DOCUMENT_TYPE_MISMATCH`.
</ParamField>

<ParamField body="document_issuing_country" type="string">
  ISO 3166-1 alpha-2. Rejects with `DOCUMENT_ISSUING_COUNTRY_MISMATCH`.
</ParamField>

<ParamField body="document_number" type="string">
  Alphanumeric only — no dots, hyphens or spaces. chmod normalises the extracted number
  the same way before comparing, so send `20123456` rather than `20.123.456`.
  Rejects with `DOCUMENT_NUMBER_MISMATCH`.
</ParamField>

<ParamField body="date_of_birth" type="string">
  ISO-8601 date, `YYYY-MM-DD`. Rejects with `DATE_OF_BIRTH_MISMATCH`.
</ParamField>

## Consistency with the customer's history

These checks need no configuration — they run whenever the customer has earlier
transactions, and they are a large part of why reusing `customer_id` matters:

| Issue | Type | Fires when |
| - | - | - |
| `DATE_OF_BIRTH_HISTORY_MISMATCH` | `REJECT` | The date of birth differs from the one on record |
| `CONFLICTING_DOCUMENT_NUMBER_HISTORY` | `REJECT` | Same document type and country, different number |
| `NAME_HISTORY_MISMATCH` | `WARN` | The name differs from the one on record |

All three carry `reference_transaction_id`, pointing at the earlier transaction they
disagree with, plus the recorded value — information you cannot reconstruct from the
result alone.

## What comes back

Everything extracted from the document lands in `result_data.document.data`: the printed
fields, the parsed MRZ with its check digits, the decoded barcode, and the address when
the document carries one. See [Reading a result](/results/reading-a-result).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.