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

# Workflow Feature Configs

> Reference for all config fields accepted by POST and PATCH /v3/workflows/ when building simple linear workflows with the features array.

Use these `config` fields inside each item of the `features` array for [`POST /v3/workflows/`](/management-api/workflows/create) and [`PATCH /v3/workflows/{settings_uuid}/`](/management-api/workflows/update).

```json theme={null}
{
  "workflow_label": "Standard KYC",
  "features": [
    {
      "feature": "OCR",
      "config": {
        "documents_allowed": {
          "USA": {
            "DL": {
              "enabled": 1,
              "sides": 2
            }
          }
        }
      }
    }
  ]
}
```

The v3 workflow API builds a linear node-based workflow from the ordered `features` array. Send features in the exact execution order.

<Note>
  Which engine runs a feature — Didit's own, or a third-party provider — is not one of the config fields below. It's managed through the console's [Marketplace](/console/marketplace), which sets `provider_key` and `fallback_to_native` on the feature's `config` object for you.
</Note>

## Validation rules

* `OCR` defaults to all supported countries and document types when `config.documents_allowed` is omitted, `null`, or `{}`. If you send a custom `documents_allowed` object, it must contain at least one valid country and one enabled document type. Each document config can set `sides` to `1` for front-only capture or `2` for front-and-back capture.
* `QUESTIONNAIRE` requires `config.questionnaire_uuid`.
* `KYB_DOCUMENTS` requires `config.kyb_required_document_groups`.
* `FACE_MATCH` must come after `OCR` in simple workflow requests.
* `NFC` and `DATABASE_VALIDATION` require `OCR` when they appear in the same workflow. `AML` needs one of `OCR`, `KYB_REGISTRY`, or `DOCUMENT_AI` before it; with only `DOCUMENT_AI` it additionally needs `config.aml_field_sources` to fill the screened name (see [AML](#aml)).
* KYB sub-features such as `KYB_DOCUMENTS` and `KYB_KEY_PEOPLE` require `KYB_REGISTRY` earlier in the workflow.
* A workflow is either a person (KYC) workflow or a business (KYB) workflow, never both. A workflow that uses any KYB feature (`KYB_REGISTRY`, `KYB_DOCUMENTS`, `KYB_KEY_PEOPLE`) may only combine it with `AML`, `DOCUMENT_AI`, `QUESTIONNAIRE`, `PHONE_VERIFICATION`, `EMAIL_VERIFICATION`, and `IP_ANALYSIS`. It cannot include person-only features such as `OCR`, `LIVENESS`, `FACE_MATCH`, `NFC`, `PROOF_OF_ADDRESS`, `DATABASE_VALIDATION`, or `AGE_ESTIMATION`, and a person workflow cannot include KYB features. On a business workflow `DOCUMENT_AI` compares any field marked `is_full_name` against the verified company instead of a person. To verify the people behind a company, build a separate KYC workflow and reference it from the `KYB_KEY_PEOPLE` node using `kyb_ubo_verification_workflow`, `kyb_officer_verification_workflow`, or `kyb_shareholder_verification_workflow`.
* Action fields use `DECLINE`, `REVIEW`, or `NO_ACTION` unless the field below says it is restricted.
* Score and threshold fields are numbers from `0` to `100` unless a narrower range is listed.

## Common status rules

Every feature supports an optional `status_rules` array. Use it to override the feature status when a field condition matches.

```json theme={null}
{
  "status_rules": [
    {
      "field": "kyc.issuing_state",
      "operator": "in",
      "value": ["VEN", "IRN"],
      "status": "In Review",
      "value_type": "literal"
    },
    {
      "field": "kyc.extra_fields.profession",
      "operator": "fuzzy_match",
      "value": "Soft. Engineer",
      "score": 80,
      "status": "Declined",
      "value_type": "literal"
    },
    {
      "field": "kyc.extra_fields.dl_class_code_b_to",
      "operator": "less_than",
      "value": "2030-01-01",
      "status": "In Review",
      "value_type": "literal"
    },
    {
      "field": "kyc.extra_fields.dl_class_code_b_from",
      "operator": "less_than",
      "value": { "direction": "past", "amount": 3, "unit": "years" },
      "status": "Declined",
      "value_type": "relative_date"
    }
  ]
}
```

| Field | Type | Description |
| - | - | - |
| `field` | string | Field path to check, such as `kyc.issuing_state` or `kyc.document_type`. |
| `operator` | string | One of `equals`, `not_equals`, `greater_than`, `greater_than_or_equals`, `less_than`, `less_than_or_equals`, `contains`, `not_contains`, `in`, `not_in`, `is_empty`, `is_not_empty`, `regex`, or `fuzzy_match`. |
| `value` | any | Comparison value. Omit or send `null` for operators that do not need a value. |
| `status` | string | One of `Approved`, `Declined`, `In Review`, or `No Action`. |
| `value_type` | string | Optional. `literal` by default. Use `field` to compare against another field, `list` to resolve a Didit list UUID at runtime, or `relative_date` to compare a date field against today plus/minus a duration. |
| `score` | number | Required for `fuzzy_match`. Minimum similarity score from `0` to `100`. The rule matches when the computed similarity is greater than or equal to this value (`similarity >= score`). For example, `Software Engineer` can match `Soft. Engineer` when the similarity is at least `80`. |

Read the example above as: `kyc.extra_fields.profession` fuzzy matches `Soft. Engineer` with a minimum score of `80` → set the feature status to `Declined`.

For ID Verification, target extracted `extra_fields` values such as `kyc.extra_fields.profession`, driver license class validity dates like `kyc.extra_fields.dl_class_code_b_from` and `kyc.extra_fields.dl_class_code_b_to`, or per-class restriction/information codes like `kyc.extra_fields.dl_class_code_b_notes` (column 12 of UK licences). These values come from the OCR result's `extra_fields` object; they are not part of `expected_details`.

<Note>
  `dl_class_code_*_from` and `dl_class_code_*_to` are always either a valid ISO date or absent from `extra_fields` — Didit never returns unreadable OCR text in a date field. If the class-table cell can't be read, the field is omitted rather than sent as a raw string, so use the `is_empty` operator to route unreadable class dates instead of assuming the field is always present.
</Note>

For date fields, use `value_type: "relative_date"` with a value object shaped as `{ "direction": "past" | "future", "amount": number, "unit": "days" | "months" | "years" }`. For example, `operator: "less_than"` with `{ "direction": "past", "amount": 3, "unit": "years" }` matches dates older than three years from today.

### Compare against a specific step

A graph workflow can run the same feature more than once, for example three ID Verification steps.
With `value_type: "field"`, append `@<node_id>` to the value to read it from one specific step.
The referenced node must be a feature step that always runs before the rule's own step, on every path that reaches it, or the rule's own step itself.
A value without the suffix is read from the rule's own step.

```json theme={null}
{
  "status_rules": [
    {
      "field": "kyc.full_name",
      "operator": "fuzzy_match",
      "value": "kyc.full_name@ocr_1",
      "value_type": "field",
      "score": 80,
      "status": "Declined"
    }
  ]
}
```

Read this rule, set on the third ID Verification step, as: this step's `kyc.full_name` fuzzy matches the full name read by step `ocr_1` with a minimum score of `80` → set the feature status to `Declined`.
In the console, the variable picker lists each eligible step by its number on the canvas and stores the suffix for you.

## OCR / ID Verification

Feature value: `OCR`.

| Config field | Type | Description |
| - | - | - |
| `documents_allowed` | object | Optional. Omit it, send `null`, or send `{}` to allow every supported country and document type. Send an ISO3 country code to document type config only when you want to restrict allowed documents. At least one custom document must have `enabled: 1`. Document configs can include `sides: 1` for front-only capture or `sides: 2` for front-and-back capture. Document types include `P`, `ID`, `DL`, `RP`, `HIC`, `TC`, `SSC`, and other country-supported values. |
| `methods` | object | Which of the three [ID Verification methods](/core-technology/id-verification/verification-methods) each country may use: document capture, non-doc lookup against a government or other authoritative source, and digital ID wallets. Omit the key, a country, or a method inside a country and that country is document capture only. Every method and wallet must be marked available for the country in the capability catalog, or the save is rejected. See [Methods per country](#methods-per-country) below. |
| `document_blur_fields_by_country` | object | ISO3 country code to blur field names to redact on document images. |
| `image_capture_methods_allowed` | string\[] | Allowed capture methods. Values include `CAMERA_SCAN` and `UPLOAD`. |
| `duplicated_user_action` | action | Action for a duplicated user. |
| `is_age_restrictions_enabled` | boolean | Enables minimum and maximum age checks. |
| `age_restrictions_by_country` | object | ISO3 country code to an age config with `minimum_age`, optional `maximum_age`, and optional `states`. |
| `minimum_age` | integer | Global minimum age, `1` to `120`. |
| `minimum_age_action` | action | Action when the user is below `minimum_age`. |
| `maximum_age` | integer | Global maximum age, `1` to `120`. |
| `maximum_age_action` | action | Action when the user is above `maximum_age`. |
| `expiration_date_not_detected_action` | action | Action when the document expiration date is missing. |
| `invalid_code_action` | action | Action when a barcode or machine-readable code is invalid. |
| `invalid_mrz_action` | action | Action when the MRZ is invalid. |
| `inconsistent_data_action` | action | Action when extracted document fields conflict. |
| `invalid_validation_action` | action | Action when document validation fails. |
| `expected_details_mismatch_action` | action | Action when expected user details do not match the document. |
| `image_too_blurry_action` | action | Action for blurry document images. |
| `image_too_dark_action` | action | Action for dark document images. |
| `image_too_bright_action` | action | Action for overexposed document images. |
| `unparsed_address_action` | action | Action when the document address cannot be parsed. |
| `document_or_personal_number_format_mismatch_action` | action | Action when document or personal number format does not match the expected pattern. |
| `critical_field_occlusion_action` | action | Action when the occlusion check reports that a critical printed field - an identity number, a name, the date of birth or the expiry date - was physically covered on the captured document. Covering an identity number is a tampering signal rather than a capture-quality one, so this defaults to `REVIEW`; the value read for a covered field is dropped from the extraction either way. Raise it to `DECLINE` where a redacted document must never reach a reviewer. |
| `document_without_portrait_action` | action | Action when the recognized document type never carries a portrait photo on any side (some residence permits, health cards, registration certificates, and other catalog-known portrait-less document families). Defaults to `REVIEW`. Consider `DECLINE` when Face Match anchors the workflow's identity check. |
| `is_document_selfie_portrait_match_enabled` | boolean | Enables document portrait to selfie comparison. |
| `document_selfie_portrait_match_decline_threshold` | integer | Decline threshold for document portrait to selfie match. |
| `document_selfie_portrait_match_review_threshold` | integer | Review threshold for document portrait to selfie match. |
| `document_selfie_face_search_enabled` | boolean | Run the [Face Search 1:N](/core-technology/face-search/overview#the-selfie-taken-during-document-capture) on the selfie taken during document capture (needs `is_document_selfie_portrait_match_enabled`): match it against the face blocklist and previously verified users. The selfie is only compared, its template is never stored. On by default. `false` skips the search and leaves the document-selfie duplicate-face actions with nothing to act on; `face_search_enabled: false` on the Liveness step skips it too, since that opt-out covers every selfie of the session. |
| `document_selfie_duplicated_face_action` | action | Verdict when the document-capture selfie matches, or possibly matches, the face of a user already verified in this application. Defaults to `NO_ACTION`. |
| `document_selfie_duplicated_face_name_mismatch_action` | action | Verdict when the document-capture selfie matches a user already verified under a different name. Can only escalate `document_selfie_duplicated_face_action`. Defaults to `REVIEW`. |
| `document_selfie_duplicated_face_document_mismatch_action` | action | Verdict when the document-capture selfie matches a user already verified with a different date of birth on their identity document. The same person on a different or renewed document is not flagged. Can only escalate `document_selfie_duplicated_face_action`. Defaults to `REVIEW`. |
| `id_verification_name_match_score_threshold` | integer | Name match threshold across multiple ID verifications. |
| `id_verification_max_retry_attempts` | integer | Retry limit for ID verification, `2` to `5`. |
| `id_document_quality_threshold` | integer | Minimum capture-quality score (`0` to `100`) a document image must reach to be accepted. The quality check always runs; only this threshold is configurable. Defaults to `20`, tuned so degraded and borderline captures (blur, motion, unreadable text) are rejected while legible documents pass; raise it for stricter capture requirements. The console presents this value on a perceptual slider where the default of `20` displays as a balanced 50%. |
| `cross_org_fraud_document_action` | action | Verdict when this document was flagged as fraudulent by another organization in the network. |
| `document_audio_recording_enabled` | boolean | Record the microphone during document capture, so a reviewer can hear the session. Off by default. |
| `document_liveness_portrait_replace_decline_threshold` | integer | Portrait replacement score (`0` to `100`, higher = more likely genuine) below which the session is declined. Defaults to `35`. See [document liveness thresholds](/core-technology/id-verification/warnings-id-verification#configuring-document-liveness-thresholds). |
| `document_liveness_portrait_replace_review_threshold` | integer | Portrait replacement score below which the session goes to In Review, when it is not already declined. At `100`, almost every session is held. Defaults to `70`. See [document liveness thresholds](/core-technology/id-verification/warnings-id-verification#configuring-document-liveness-thresholds). |
| `document_liveness_printed_copy_decline_threshold` | integer | Printed copy score (`0` to `100`, higher = more likely genuine) below which the session is declined. Defaults to `35`. See [document liveness thresholds](/core-technology/id-verification/warnings-id-verification#configuring-document-liveness-thresholds). |
| `document_liveness_printed_copy_review_threshold` | integer | Printed copy score below which the session goes to In Review, when it is not already declined. At `100`, almost every session is held. Defaults to `77`. See [document liveness thresholds](/core-technology/id-verification/warnings-id-verification#configuring-document-liveness-thresholds). |
| `document_liveness_screen_replay_decline_threshold` | integer | Screen capture score (`0` to `100`, higher = more likely genuine) below which the session is declined. Defaults to `30`. See [document liveness thresholds](/core-technology/id-verification/warnings-id-verification#configuring-document-liveness-thresholds). |
| `document_liveness_screen_replay_review_threshold` | integer | Screen capture score below which the session goes to In Review, when it is not already declined. At `100`, almost every session is held. Defaults to `70`. See [document liveness thresholds](/core-technology/id-verification/warnings-id-verification#configuring-document-liveness-thresholds). |
| `image_quality_too_low_action` | action | Verdict when overall document image quality is below the bar. |
| `is_image_capture_review_screen_enabled` | boolean | Show the user a review screen after each document capture, letting them retake the photo before it is submitted. Off by default. |
| `is_ocr_id_verification_data_review_enabled` | boolean | Let the user review and correct the extracted identity data before the step completes. Off by default. A correction is scored against `ocr_id_verification_data_review_critical_fields`, and a disagreement takes the critical or the minor action accordingly. The review still runs when the workflow includes NFC - a later chip read simply supersedes it. |
| `ocr_id_verification_data_review_critical_fields` | string\[] | Which extracted identity fields count as critical when the user edits the OCR result, so a mismatch takes the critical action rather than the minor one. Shape: `["first_name" \| "last_name" \| "date_of_birth" \| ...]`. |
| `ocr_id_verification_data_review_critical_mismatch_action` | restricted action | Verdict when the user's edit disagrees with OCR on a critical field. |
| `ocr_id_verification_data_review_minor_mismatch_action` | action | Verdict when the user's edit disagrees with OCR on a non-critical field. |
| `status_rules` | array | Optional custom status rules. |

<Note>
  Document liveness is configured with the six `document_liveness_*` thresholds above, not with
  per-signal action fields. `portrait_replacement_action`, `printed_copy_action` and
  `screen_capture_action` are account-level settings and are **not** accepted in a workflow
  feature config: the API strips unknown config keys on save, so sending them there has no
  effect. Each signal has a decline threshold and a review threshold on a `0`-`100` scale where
  a higher score means the document is more likely to be genuine.
</Note>

Example:

```json theme={null}
{
  "feature": "OCR",
  "config": {
    "documents_allowed": {
      "USA": {
        "DL": {
          "enabled": 1,
          "sides": 2
        }
      }
    },
    "duplicated_user_action": "REVIEW",
    "image_capture_methods_allowed": ["CAMERA_SCAN", "UPLOAD"]
  }
}
```

### Methods per country

`methods` is keyed by [ISO 3166-1 alpha-3](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3) country code. Each country carries up to three method objects; anything you leave out takes its default.

```json theme={null}
{
  "feature": "OCR",
  "config": {
    "methods": {
      "ZAF": {
        "document": { "enabled": true },
        "id_lookup": {
          "enabled": true,
          "max_attempts": 2,
          "skip_liveness_and_face_match": true,
          "on_partial_match": "fallback_to_document",
          "on_no_match": "fallback_to_document",
          "on_provider_error": "fallback_to_document"
        },
        "wallet": { "enabled": false, "providers": [], "on_failure": "fallback_to_document" }
      },
      "DNK": {
        "document": { "enabled": true },
        "id_lookup": { "enabled": true, "on_no_match": "decline" }
      }
    }
  }
}
```

Both lookups above are `available` in today's catalog, so that config saves as written. The wallet block is off on purpose: **no wallet is live in production yet**, so `wallet.enabled: true` is rejected and takes the whole save with it, lookup configuration included. When the catalog marks a wallet `available` in a country, add it to that country:

```json theme={null}
{
  "DNK": {
    "document": { "enabled": true },
    "wallet": { "enabled": true, "providers": ["mitid"], "on_failure": "fallback_to_document" }
  }
}
```

| Key | Type | Default | Description |
| - | - | - | - |
| `<ISO3>.document.enabled` | boolean | `true` | Whether a photographed document is accepted in this country. Which documents are accepted keeps living in `documents_allowed`. |
| `<ISO3>.id_lookup.enabled` | boolean | `false` | Offer the non-document lookup against this country's government or other authoritative source. Rejected when the catalog does not mark the country's source `available`. |
| `<ISO3>.id_lookup.max_attempts` | integer | `1` | `1` to `5`. How many **answered** lookups the user gets before the fallback switch fires. A number that fails the client-side format check never reaches the register, so it neither counts nor bills. |
| `<ISO3>.id_lookup.skip_liveness_and_face_match` | boolean | `true` | After a match against a register that returns a portrait, skip the workflow's own Liveness and Face Match steps — the lookup already ran passive liveness on the selfie and matched it to that portrait. Normalised to `false` on save for a source that returns no portrait, where the switch would mean nothing. |
| `<ISO3>.id_lookup.on_partial_match` | `fallback_to_document` \| `decline` | `fallback_to_document` | What happens when the register answered but some fields did not match. |
| `<ISO3>.id_lookup.on_no_match` | `fallback_to_document` \| `decline` | `fallback_to_document` | What happens when the register answered and holds no matching record. |
| `<ISO3>.id_lookup.on_provider_error` | `fallback_to_document` \| `decline` | `fallback_to_document` | What happens when the register could not be reached. Never billed. |
| `<ISO3>.id_lookup.response_fields` | string\[] \| null | `null` | Optional subset of the source's response field keys. Unknown keys are rejected and the source's required fields are always added back, so the saved value is always a valid selection. The current staging implementation applies this selection to comparison output, optional gender and retained registry portraits. Required identity fields remain. See the rollout and retention note below. |
| `<ISO3>.wallet.enabled` | boolean | `false` | Offer digital ID wallet sign-in in this country. |
| `<ISO3>.wallet.providers` | string\[] | `[]` | Catalog wallet ids this country accepts — an accept-list, never a ranking. The order you send is normalised into catalog order. At least one wallet is required when `enabled` is `true`. A wallet that is not offered in the country, or that is still coming soon, is rejected. |
| `<ISO3>.wallet.on_failure` | `fallback_to_document` \| `decline` | `fallback_to_document` | What happens when the sign-in is cancelled, times out or fails. |

Validation the API applies on save:

* An unknown country code, an unknown key inside a country, or an unknown key inside a method is rejected — nothing is silently dropped.
* A country where no method is enabled is rejected: nobody there could verify.
* `fallback_to_document` in a country where `document.enabled` is `false` declines at runtime, because there is nowhere to fall back to.

<Warning>
  The current staging implementation applies `id_lookup.response_fields` to comparison output, optional gender and retained registry portraits. Required identity fields remain part of the verification record. This is not a guarantee that every intermediate provider payload is excluded from processing. Production rollout has not been verified for this change; confirm the behavior in your target environment before relying on field selection for retention requirements.
</Warning>

Read the capability catalog before you write `methods` — through the console's **Countries** tab, or with the [`didit_workflow_get_id_verification_methods_catalog`](/integration/mcp/tools) MCP tool. Behaviour, coverage and billing are on [ID Verification methods](/core-technology/id-verification/verification-methods).

## NFC / ePassport

Feature value: `NFC`.

| Config field | Type | Description |
| - | - | - |
| `allow_nfc_skip` | boolean | Allows users to skip NFC when their document supports chip reading. |
| `skip_nfc_action` | action | Action when the user skips NFC. |
| `unverified_chip_action` | action | Action when chip verification fails. |
| `trust_anchor_missing_action` | action | Verdict when the chip's data-group hashes are intact but the certificate chain cannot be verified, because the issuing country's CSCA trust anchor is not loaded. |
| `status_rules` | array | Optional custom status rules. |

## Liveness

Feature value: `LIVENESS`.

| Config field | Type | Description |
| - | - | - |
| `face_liveness_method` | string | One of `ACTIVE_3D`, `FLASHING`, or `PASSIVE`. |
| `face_liveness_mode` | string | Liveness mode override. |
| `face_liveness_flash_mode` | string | Flash mode for the `ACTIVE_3D` and `FLASHING` methods. `"0"` (not strict, default) verifies that the colors flashed on screen reflect on the user's face; `"1"` (strict) additionally requires the reflections to match the exact flashed color sequence — stronger against replay and video-injection attacks, but with more false rejections. |
| `face_liveness_score_decline_threshold` | integer | Decline threshold. |
| `face_liveness_score_review_threshold` | integer | Review threshold. |
| `face_liveness_possible_duplicated_face_action` | action | Action when the face may be duplicated. |
| `face_liveness_multiple_faces_action` | action | Action when multiple faces are detected. |
| `face_liveness_video_anomaly_action` | action | Action when the stored recording contains a repeated moving sequence. Default `NO_ACTION`; `REVIEW` and `DECLINE` can escalate a decided verification asynchronously. Sparse or repeated still frames alone do not trigger it. |
| `race_map_similarity_thresholds` | object | Per-demographic similarity thresholds. |
| `face_privacy_mode_enabled` | boolean | Enables privacy mode for face assets. |
| `face_quality_review_threshold` | integer | Review threshold for face image quality. |
| `face_quality_decline_threshold` | integer | Decline threshold for face image quality. Must be less than or equal to the review threshold. |
| `face_luminance_min_threshold` | integer | Minimum luminance threshold. Must be lower than `face_luminance_max_threshold`. |
| `face_luminance_min_action` | action | Action when luminance is too low. |
| `face_luminance_max_threshold` | integer | Maximum luminance threshold. |
| `face_luminance_max_action` | action | Action when luminance is too high. |
| `face_liveness_max_attempts` | integer | Total liveness submissions per session, `1` to `3`. Default `3` means one initial attempt plus two retries. Setting `1` does not disable retries for capture-quality failures: when every warning on the attempt is environmental (`LOW_FACE_LUMINANCE`, `HIGH_FACE_LUMINANCE`, `LOW_FACE_QUALITY`, `HIGH_FACE_OCCLUSION`, `NO_FACE_DETECTED`) the user always gets a second attempt with actionable feedback. |
| `cross_org_fraud_face_action` | action | **Deprecated and no longer applied.** It treated a face as if it carried a fraud verdict, which a stable biometric identity cannot. Still accepted so existing workflows keep validating. Use `cross_org_identity_claim_pattern_action`. |
| `cross_org_identity_claim_pattern_action` | action | Verdict when other organizations in the network recorded identity-claim events for this same person in which the claimed identity did not match. Review or No action only — a Decline set here is downgraded to Review. See [Cross-organization identity claims](/core-technology/liveness/cross-organization-identity-claims). |
| `external_capture_device_action` | action | Verdict when an external capture device is detected. |
| `face_audio_recording_enabled` | boolean | Record the microphone during the selfie capture, so a reviewer can hear the session. Off by default. |
| `face_search_enabled` | boolean | Run the automatic [Face Search 1:N](/core-technology/face-search/overview#turning-automatic-face-search-off) on the selfie: match it against previously verified users, the face blocklist and allowlist, and store its biometric template for later matching. On by default. `false` skips the search, keeps no face template for this step, and leaves the duplicated-face actions with nothing to act on. |
| `face_liveness_uncertain_retry_enabled` | boolean | Native SDK active liveness only. On, a capture whose liveness score falls strictly between `face_liveness_score_decline_threshold` and `face_liveness_score_review_threshold` is never decided, whichever verdict the engine attached to it: the person is told the result was unclear and asked to capture again, every time, outside the `face_liveness_max_attempts` budget (which still applies to captures the engine could not score). Off by default; the web flow ignores it. |
| `face_liveness_duplicated_face_name_mismatch_action` | action | Verdict when a duplicate face is found under a different name. |
| `face_liveness_duplicated_face_document_mismatch_action` | action | Verdict when a duplicate face is found and the matched user's identity document shows a different date of birth. The same person on a different or renewed document is not flagged. Defaults to `REVIEW`. |
| `frame_injection_action` | action | Verdict when injected video frames are detected. |
| `screen_capture_action` | action | Verdict when the selfie is a photo of a screen. |
| `virtual_camera_action` | action | Verdict when a virtual camera feed is detected. |
| `status_rules` | array | Optional custom status rules. |

## Face Match

Feature value: `FACE_MATCH`.

| Config field | Type | Description |
| - | - | - |
| `face_match_score_decline_threshold` | integer | Decline threshold. |
| `face_match_score_review_threshold` | integer | Review threshold. |
| `face_match_max_attempts` | integer | Total face-match submissions per session, `1` to `3`. Default `3` means one initial attempt plus two retries. |
| `face_match_not_computed_action` | restricted action | Verdict when no face-match score could be produced (missing portrait or selfie). |
| `face_match_eyes_covered_action` | action | Verdict when the eyes region is covered in the selfie. Defaults to `NO_ACTION`, which records the evidence without changing the Face Match result. This signal is still being calibrated, so it stays shadow-only until the region reports as calibrated: set `REVIEW` or `DECLINE` now and it takes effect from that point on. |
| `face_match_mouth_covered_action` | action | Verdict when the mouth region is covered in the selfie. Same `NO_ACTION` default and same calibration caveat as `face_match_eyes_covered_action`. |
| `face_match_face_covered_action` | action | Verdict when the face as a whole is covered in the selfie. Same `NO_ACTION` default and same calibration caveat as `face_match_eyes_covered_action`. |
| `status_rules` | array | Optional custom status rules. |

## Age Estimation

Feature value: `AGE_ESTIMATION`.

| Config field | Type | Description |
| - | - | - |
| `enable_id_verification_fallback` | boolean | Runs ID Verification when age estimation lands in the borderline range. |
| `minimum_age_threshold` | integer | Minimum accepted age, `1` to `100`. |
| `borderline_minimum_age_threshold` | integer | Lower bound for the borderline range, `1` to `100`. |
| `borderline_maximum_age_threshold` | integer | Upper bound for the borderline range, `1` to `100`. |
| `face_liveness_method` | string | One of `ACTIVE_3D`, `FLASHING`, or `PASSIVE`. |
| `face_liveness_flash_mode` | string | Flash mode for the `ACTIVE_3D` and `FLASHING` methods. `"0"` (not strict, default) verifies that the colors flashed on screen reflect on the user's face; `"1"` (strict) additionally requires the reflections to match the exact flashed color sequence. |
| `face_liveness_score_decline_threshold` | number | Decline threshold for the liveness check used by age estimation. |
| `face_liveness_score_review_threshold` | number | Review threshold for the liveness check used by age estimation. |
| `face_liveness_possible_duplicated_face_action` | action | Action when the face may be duplicated. |
| `face_liveness_multiple_faces_action` | action | Action when multiple faces are detected. |
| `face_liveness_video_anomaly_action` | action | Action when the stored recording contains a repeated moving sequence. Default `NO_ACTION`; `REVIEW` and `DECLINE` can escalate a decided verification asynchronously. Sparse or repeated still frames alone do not trigger it. |
| `face_privacy_mode_enabled` | boolean | Enables privacy mode for face assets. |
| `face_quality_review_threshold` | integer | Review threshold for face quality. |
| `face_quality_decline_threshold` | integer | Decline threshold for face quality. |
| `face_luminance_min_threshold` | integer | Minimum luminance threshold. |
| `face_luminance_min_action` | action | Action when luminance is too low. |
| `face_luminance_max_threshold` | integer | Maximum luminance threshold. |
| `face_luminance_max_action` | action | Action when luminance is too high. |
| `face_liveness_max_attempts` | integer | Total liveness submissions per session, `1` to `3`. Default `3` means one initial attempt plus two retries. Setting `1` does not disable retries for capture-quality failures: when every warning on the attempt is environmental (`LOW_FACE_LUMINANCE`, `HIGH_FACE_LUMINANCE`, `LOW_FACE_QUALITY`, `HIGH_FACE_OCCLUSION`, `NO_FACE_DETECTED`) the user always gets a second attempt with actionable feedback. |
| `external_capture_device_action` | action | Verdict when an external capture device is detected. |
| `face_audio_recording_enabled` | boolean | Record the microphone during the selfie capture, so a reviewer can hear the session. Off by default. |
| `face_search_enabled` | boolean | Run the automatic [Face Search 1:N](/core-technology/face-search/overview#turning-automatic-face-search-off) on the selfie: match it against previously verified users, the face blocklist and allowlist, and store its biometric template for later matching. On by default. `false` skips the search, keeps no face template for this step, and leaves the duplicated-face actions with nothing to act on. |
| `face_liveness_uncertain_retry_enabled` | boolean | Native SDK active liveness only. On, a capture whose liveness score falls strictly between `face_liveness_score_decline_threshold` and `face_liveness_score_review_threshold` is never decided, whichever verdict the engine attached to it: the person is told the result was unclear and asked to capture again, every time, outside the `face_liveness_max_attempts` budget (which still applies to captures the engine could not score). Off by default; the web flow ignores it. |
| `face_liveness_duplicated_face_name_mismatch_action` | action | Verdict when a duplicate face is found under a different name. |
| `face_liveness_duplicated_face_document_mismatch_action` | action | Verdict when a duplicate face is found and the matched user's identity document shows a different date of birth. The same person on a different or renewed document is not flagged. Defaults to `REVIEW`. |
| `frame_injection_action` | action | Verdict when injected video frames are detected. |
| `screen_capture_action` | action | Verdict when the selfie is a photo of a screen. |
| `virtual_camera_action` | action | Verdict when a virtual camera feed is detected. |
| `status_rules` | array | Optional custom status rules. |

## Phone Verification

Feature value: `PHONE_VERIFICATION`.

| Config field | Type | Description |
| - | - | - |
| `code_size` | integer | OTP size, `4` to `8`. |
| `phone_max_retries` | integer | Maximum send retries, `1` to `5`. |
| `phone_max_check_attempts` | integer | Maximum OTP check attempts, `1` to `5`. |
| `phone_verification_countries` | object | ISO2 country code to channel config. Example: `{ "US": { "sms": { "enabled": true } } }`. |
| `disposable_number_action` | action | Action for disposable numbers. |
| `voip_number_action` | action | Action for VoIP numbers. |
| `duplicated_phone_number_action` | action | Action for duplicated phone numbers. |
| `high_risk_phone_action` | action | Action when the delivery provider blocks the OTP send. Only `DECLINE` or `REVIEW`; default `DECLINE`. Applied only when every block on the step carries `risk_factors` of exactly `["device_attribute"]`; any other risk factor, any mix including it, or no risk factors at all declines regardless of this setting. |
| `phone_shared_device_mode` | boolean | Allows shared-device phone verification mode. |
| `phone_enrichment_enabled` | boolean | Enables phone intelligence enrichment (line-type risk, trust index, carrier, porting / SIM-swap history, fraud-risk score). **Charged \$0.05 per verification** on top of the phone verification price. |
| `phone_intelligence_score_action` | action | Action when the phone fraud-risk score is at or above `phone_intelligence_score_threshold`. |
| `phone_intelligence_score_threshold` | integer | Fraud-risk score (`0`–`100`, higher is riskier) that triggers `phone_intelligence_score_action`. Default `80`. |
| `phone_type_risk_action` | action | Action when the number is flagged as a high-risk phone type. |
| `recent_port_action` | action | Action when the number was recently ported (SIM-swap / porting signal). |
| `low_phone_trust_action` | action | Action when the phone trust index is at or below `phone_trust_index_threshold`. |
| `phone_trust_index_threshold` | integer | Trust index (`0`–`100`, lower is riskier) at or below which `low_phone_trust_action` is applied. Default `30`. |
| `phone_social_enabled` | boolean | Enables the social footprint check: which messaging, social, e-commerce and professional platforms the verified number is registered on. **Charged \$0.05 per verification** on top of the phone verification price, independent of `phone_enrichment_enabled`. |
| `phone_no_social_presence_action` | action | Action when the social footprint check finds the number registered on none of the platforms it covers - a common synthetic-identity signal. Default `NO_ACTION`. |
| `cross_org_fraud_phone_action` | action | Verdict when this number was flagged as fraudulent by another organization in the network. |
| `status_rules` | array | Optional custom status rules. |

Phone Verification is disabled until the organization completes its first top-up.

<Note>
  Phone intelligence enrichment is an optional add-on. When `phone_enrichment_enabled` is `true`, each completed phone verification is enriched with phone intelligence and **billed \$0.05 per request** in addition to the standard phone verification cost. The score-based actions only take effect once the enrichment score is available; the trust-index, phone-type and porting actions apply as soon as enrichment runs.

  The social footprint check is a second, separate add-on. When `phone_social_enabled` is `true`, each completed phone verification also reports which online platforms the number is registered on - messaging, social media, e-commerce and professional services - and is **billed \$0.05 per request**. It can be enabled with or without `phone_enrichment_enabled`: the two add-ons are priced and billed independently. A lookup that finds the number on none of the platforms it covers is a valid, billable answer and triggers `phone_no_social_presence_action`. Use `phone.social_profiles_registered` (the number of platforms the number is registered on) to branch on the result in a workflow graph.
</Note>

## Email Verification

Feature value: `EMAIL_VERIFICATION`.

| Config field | Type | Description |
| - | - | - |
| `email_code_size` | integer | OTP size, `4` to `8`. |
| `email_max_retries` | integer | Maximum send retries, `1` to `5`. |
| `email_max_check_attempts` | integer | Maximum OTP check attempts, `1` to `5`. |
| `email_alphanumeric_code` | boolean | Uses alphanumeric email codes. |
| `breached_email_action` | action | Action for breached emails. |
| `disposable_email_action` | action | Action for disposable emails. |
| `duplicated_email_action` | action | Action for duplicated emails. |
| `only_corporate_emails_allowed` | boolean | Rejects non-corporate email addresses. |
| `email_enrichment_enabled` | boolean | Enables email intelligence enrichment (deliverability, domain reputation, data-breach exposure, fraud-risk score). **Charged \$0.05 per verification** on top of the email verification price. |
| `email_intelligence_score_action` | action | Action when the email fraud-risk score is at or above `email_intelligence_score_threshold`. |
| `email_intelligence_score_threshold` | integer | Fraud-risk score (`0`–`100`, higher is riskier) that triggers `email_intelligence_score_action`. Default `80`. |
| `recent_email_breach_action` | action | Action when the email appears in a recent known data breach. |
| `frequent_email_breach_action` | action | Action when the email appears across many known data breaches. |
| `email_social_enabled` | boolean | Enables the social footprint check: which email, social, professional, e-commerce and entertainment platforms the verified address is registered on. **Charged \$0.05 per verification** on top of the email verification price, independent of `email_enrichment_enabled`. |
| `email_no_social_presence_action` | action | Action when the social footprint check finds the address registered on none of the platforms it covers - a common synthetic-identity signal. Default `NO_ACTION`. |
| `cross_org_fraud_email_action` | action | Verdict when this address was flagged as fraudulent by another organization in the network. |
| `status_rules` | array | Optional custom status rules. |

<Note>
  Email intelligence enrichment is an optional add-on. When `email_enrichment_enabled` is `true`, each completed email verification is enriched with email intelligence and **billed \$0.05 per request** in addition to the standard email verification cost. The score-based action takes effect once the enrichment score is available; the breach actions apply as soon as enrichment runs.

  The social footprint check is a second, separate add-on. When `email_social_enabled` is `true`, each completed email verification also reports which online platforms the address is registered on - email providers, social media, professional, e-commerce and entertainment services - and is **billed \$0.05 per request**. It can be enabled with or without `email_enrichment_enabled`: the two add-ons are priced and billed independently. A lookup that finds the address on none of the platforms it covers is a valid, billable answer and triggers `email_no_social_presence_action`. Use `email.social_profiles_registered` (the number of platforms the address is registered on) to branch on the result in a workflow graph.
</Note>

## Database Validation

Feature value: `DATABASE_VALIDATION`.

| Config field | Type | Description |
| - | - | - |
| `database_validation_countries` | object | ISO3 country code to `{ "services": ["service_id"] }`. Use service IDs from the Database Validation catalog. |
| `database_validation_partial_match_action` | action | Action when database validation returns a partial match. |
| `database_validation_no_match_action` | action | Action when database validation returns no match. |
| `database_validation_not_applicable_action` | action | Action when database validation cannot run because the document does not carry the identifier the configured services require (e.g. a passport without a national ID number). Defaults to `NO_ACTION`. |
| `database_validation_field_sources` | object | Where to read a database input that no earlier step in the graph can fill. `source` names the producer and `key` names the value inside it: a Document AI field key for `document_ai`, a questionnaire node id for `questionnaire`, and `expected_details.<field>` or `metadata.<key>` for `expected_data`. Exact-key matches against an upstream step resolve automatically and are deliberately not persisted, so renaming a Document AI field never freezes a stale mapping into the config. A malformed entry is dropped on save rather than rejected, which un-satisfies its service and drops it from the selection. Shape: `{"<db_validation_input_field>": {"source": "document_ai"\|"questionnaire"\|"expected_data", "key": "<docai field key>\|<questionnaire node id>\|expected_details.<field>\|metadata.<key>"}}`. |
| `status_rules` | array | Optional custom status rules. |

Example:

```json theme={null}
{
  "feature": "DATABASE_VALIDATION",
  "config": {
    "database_validation_countries": {
      "BRA": {
        "services": ["SERVICE_ID_FROM_CATALOG"]
      }
    },
    "database_validation_field_sources": {
      "tax_id": {
        "source": "expected_data",
        "key": "expected_details.document_number"
      }
    },
    "database_validation_no_match_action": "REVIEW",
    "database_validation_partial_match_action": "REVIEW"
  }
}
```

<Warning>
  `database_validation_countries` is what makes this step do anything. A `DATABASE_VALIDATION`
  feature saved without it is accepted and then runs no check at all, so always send at least one
  country with at least one service ID. Service IDs that do not belong to the country you list
  are dropped on save.
</Warning>

## AML

Feature value: `AML`.

For KYC person screening, use these fields:

| Config field | Type | Description |
| - | - | - |
| `is_aml_ongoing_monitoring_enabled` | boolean | Enables ongoing AML monitoring. |
| `aml_score_approve_threshold` | integer | Approve threshold. |
| `aml_score_review_threshold` | integer | Review threshold. |
| `aml_match_score_threshold` | integer | Match score threshold for possible matches. |
| `aml_name_weight` | integer | Name weight. If any AML weight is provided, name, DOB, and country weights must sum to `100`. |
| `aml_dob_weight` | integer | Date of birth weight. |
| `aml_country_weight` | integer | Country weight. |
| `create_cases_on_hit` | boolean | Open a case automatically when the screening returns a hit, instead of leaving the hit to be triaged from the session. Off by default. |
| `case_blueprint` | uuid | Which case blueprint the automatically-created case is built from. Only meaningful with `create_cases_on_hit`; the account default is used when unset. |
| `status_rules` | array | Optional custom status rules. |

For KYB company screening, use these fields:

| Config field | Type | Description |
| - | - | - |
| `kyb_score_approve_threshold` | integer | Company AML approve threshold. |
| `kyb_score_review_threshold` | integer | Company AML review threshold. |
| `kyb_enable_ongoing_monitoring` | boolean | Enables KYB ongoing AML monitoring. |
| `kyb_company_aml_match_score_threshold` | integer | Company AML match score threshold. |
| `kyb_company_aml_name_weight` | integer | Company name weight. Company AML weights must sum to `100` when provided. |
| `kyb_company_aml_dob_weight` | integer | Incorporation date or DOB weight. |
| `kyb_company_aml_country_weight` | integer | Country weight. |

### Screening an entity a Document AI step extracted

An AML step normally reads who it screens from the `OCR` step (a person) or the `KYB_REGISTRY` step (a company). A workflow whose only extraction step is `DOCUMENT_AI` - non-standardised documents such as a tax certificate or an incorporation deed - has neither, so it has to say where each screening input comes from.

| Config field | Type | Description |
| - | - | - |
| `aml_field_sources` | object | Where to read the entity this step screens. `source` names the producer and `key` names the value inside it: a Document AI field key for `document_ai`, a questionnaire node id for `questionnaire`, and `expected_details.<field>` or `metadata.<key>` for `expected_data`. The map's KEY is one of the four screening inputs - `full_name`, `date_of_birth`, `nationality`, `document_number` - read for a company as legal name, incorporation date, country and registration number. A Document AI field already keyed as one of those four maps automatically and is deliberately not persisted, so renaming it never freezes a stale mapping. An explicit mapping wins over both the auto-map and the ID document, and one that resolves to nothing at run time leaves the input empty rather than falling back. On a business (KYB) workflow the company a `KYB_REGISTRY` step returned is authoritative: only an explicit mapping overrides one of its inputs, the auto-map is not applied on top of a registry record, and with no registry step the mapping is the whole identity. Shape: `{"full_name"\|"date_of_birth"\|"nationality"\|"document_number": {"source": "document_ai"\|"questionnaire"\|"expected_data", "key": "<docai field key>\|<questionnaire node id>\|expected_details.<field>\|metadata.<key>"}}`. |
| `aml_entity_type` | string | `person` or `company`. Defaults to `person` on a person (KYC) workflow and to `company` on a business (KYB) workflow. Set `company` to screen the business named in an uploaded document without a `KYB_REGISTRY` step: the provider searches company records and the match score is scored as a company. |
| `aml_incomplete_data_action` | action | Verdict when the screening ran on less than a full identity (name, date of birth and country). `REVIEW` is the default. `NO_ACTION` proceeds on reduced data for a workflow that knowingly holds only a name. The screening runs either way, and the partial-data log is recorded either way - this only decides what it does to the status. |

Example - screening the company named in a Mexican *acta constitutiva*, with no ID document anywhere in the workflow:

```json theme={null}
{
  "feature": "AML",
  "config": {
    "aml_entity_type": "company",
    "aml_field_sources": {
      "full_name": { "source": "document_ai", "key": "razon_social" },
      "document_number": { "source": "document_ai", "key": "rfc" },
      "date_of_birth": { "source": "document_ai", "key": "fecha_constitucion" },
      "nationality": { "source": "document_ai", "key": "pais" }
    },
    "aml_incomplete_data_action": "REVIEW"
  }
}
```

<Warning>
  With no `OCR` and no `KYB_REGISTRY` step before it, an `AML` step must be able to fill
  `full_name` - there is no provider search without a name. A workflow that maps nothing, or maps
  a Document AI key no upstream step extracts, is rejected on save rather than saved and left
  screening nothing. AML precision follows directly from how many identifiers you supply: a
  name-only screening is what produces high false-positive volume.
</Warning>

## Device & IP Analysis

Feature value: `IP_ANALYSIS`.

| Config field | Type | Description |
| - | - | - |
| `ip_mismatch_action` | action | Action when IP geolocation does not match expected data. |
| `ip_location_not_determined_action` | action | Verdict when no IP address identifying the end user was recorded, so the session has no IP location at all: country, city, ISP, coordinates and time zone are unavailable, and `ip_geofencing_by_country`, the document-country comparison and the VPN/data-centre checks cannot run. Defaults to `NO_ACTION`, which keeps the `IP_LOCATION_NOT_DETERMINED` warning visible on the session without changing its verdict; set `REVIEW` or `DECLINE` if an unlocated session must not pass. |
| `vpn_detection_action` | action | Action when VPN or proxy usage is detected. |
| `expected_ip_mismatch_action` | action | Action when the IP does not match the expected IP. |
| `duplicated_ip_action` | action | Action when the IP is reused suspiciously. |
| `duplicated_device_action` | action | Action when the same persistent device fingerprint is reused across sessions from different `vendor_data` values. |
| `recovered_device_action` | action | Action when v2 fingerprint recovery links the session to a previously seen device with high confidence after the persistent device ID changed. |
| `is_ip_geofencing_enabled` | boolean | Enables IP geofencing restrictions. When `false`, `ip_geofencing_by_country` is preserved but not enforced. |
| `ip_geofencing_by_country` | object | Per-country IP location rules keyed by ISO-3 country code: `{"USA": {"allowed": true, "states": {"California": {"allowed": false}}}}`. State/region entries override the country flag; countries not listed are allowed. |
| `ip_geofencing_action` | action | Action when the session IP is located in a country or state/region that is not allowed. Defaults to `DECLINE`. |
| `automation_detected_action` | action | Verdict when the session looks driven by automation rather than a person. |
| `cross_org_fraud_device_action` | action | Verdict when this device was flagged as fraudulent by another organization in the network. |
| `cross_org_fraud_ip_action` | action | Verdict when this IP was flagged as fraudulent by another organization in the network. |
| `device_app_tampered_action` | action | Verdict when the host application binary looks tampered with. |
| `device_blocklist_action` | action | Verdict when the device is on the organization's block list. |
| `device_debugger_action` | action | Verdict when a debugger is attached to the host application. |
| `device_emulator_action` | action | Verdict when the device is an emulator rather than real hardware. |
| `device_hooking_action` | action | Verdict when runtime hooking or instrumentation is detected. |
| `device_integrity_missing_action` | action | Verdict when no platform device-integrity attestation was returned. |
| `device_rooted_action` | action | Verdict when the device is rooted or jailbroken. |
| `multiple_devices_action` | action | Verdict when the session was driven from more than one device. |
| `status_rules` | array | Optional custom status rules. |

Example:

```json theme={null}
{
  "feature": "IP_ANALYSIS",
  "config": {
    "vpn_detection_action": "REVIEW",
    "duplicated_device_action": "REVIEW",
    "recovered_device_action": "REVIEW",
    "is_ip_geofencing_enabled": true,
    "ip_geofencing_by_country": {
      "USA": { "allowed": true, "states": { "California": { "allowed": false } } },
      "RUS": { "allowed": false, "states": null }
    },
    "ip_geofencing_action": "DECLINE"
  }
}
```

Use `recovered_device_action` separately from `duplicated_device_action` when you want to monitor high-confidence recovered devices before automatically declining them. This is useful during rollout because recovered-device matches are designed for fraud-ring detection but should still be calibrated against your own traffic.

## Proof of Address

Feature value: `PROOF_OF_ADDRESS`.

| Config field | Type | Description |
| - | - | - |
| `poa_documents_allowed` | object | ISO3 country code to POA document type config. At least one valid document type should be enabled. |
| `poa_languages_allowed` | object | ISO 639-1 language code to `0` or `1`. At least one language should be enabled. |
| `poa_name_or_address_mismatch_action` | action | Action when the name or address does not match expected data. |
| `poa_document_issues_action` | action | Action when the document has quality or extraction issues. |
| `poa_max_attempts_exceeded_action` | restricted action | Action when the user exceeds retry attempts. Use `DECLINE` or `REVIEW`. |
| `poa_document_authenticity_action` | restricted action | Action when authenticity checks fail. Use `DECLINE` or `REVIEW`. |
| `poa_unsupported_document_type_action` | action | Action when the document type is unsupported. |
| `poa_unsupported_language_action` | action | Action when the document language is unsupported. |
| `poa_issuer_not_identified_action` | action | Action when the document issuer is not identified. |
| `poa_issue_date_not_detected_action` | action | Action when the issue date is missing. |
| `poa_unparsable_or_invalid_address_action` | action | Action when the address is invalid or cannot be parsed. |
| `poa_name_match_score_threshold` | integer | Name match threshold. |
| `poa_max_retry_attempts` | integer | Retry limit, `2` to `5`. |
| `status_rules` | array | Optional custom status rules. |

## Questionnaire

Feature value: `QUESTIONNAIRE`.

| Config field | Type | Description |
| - | - | - |
| `questionnaire_uuid` | UUID | Required. Use the `questionnaire_id` returned by `POST /v3/questionnaires/`. |
| `review_questionnaire_manually` | boolean | Sends questionnaire responses to manual review. |
| `status_rules` | array | Optional custom status rules. You can target `questionnaire.answers.<question_id>` fields. |

## KYB Registry

Feature value: `KYB_REGISTRY`.

| Config field | Type | Description |
| - | - | - |
| `kyb_accepted_countries` | object | Legacy ISO3 country allow-list for business registry lookup. Superseded by `kyb_registry_countries_config`; still readable, still derived from it for existing workflows, but no longer the way to configure new ones. |
| `kyb_manual_company_action` | action | Action when the company requires manual handling. |
| `kyb_registry_fields_config` | object | Per-field visibility and requiredness for the registry form in the hosted flow. See below. |
| `kyb_registry_countries_config` | object | Per-country registry routing, as `{"<ISO2>": {"enabled": bool, "tier": "basic" \| "shareholders" \| "ubo"}}`. Each enabled country picks the data tier charged when a company is selected (`basic` is the Lite tier), and only tiers the [pricing endpoint](/getting-started/kyb-registry-pricing) marks available for that country can be enabled. At least one country must be enabled: a registry check that searches nowhere is rejected. Manually entered companies are billed separately and do not use this configuration. |
| `kyb_registry_monitoring_enabled` | boolean | Keep the company's registry record under continuous monitoring after a Shareholders or UBO result: a change of status, officers, ownership, or address moves the session back to review and fires a webhook. Available only where the registry provider offers monitoring. USD 2.00 per company per year, cancellable at any time. Off by default. |
| `kyb_vat_invalid_action` | action | Action when the company's VAT number fails EU VIES validation. Defaults to `REVIEW`. |
| `kyb_vat_unverified_action` | action | Action when the VAT number could not be validated because VIES was unavailable. Defaults to `NO_ACTION`. |
| `status_rules` | array | Optional custom status rules. |

`kyb_registry_fields_config` maps each configurable field key to `{"enabled": bool, "required": bool}`. `enabled: false` hides the field, `enabled: true, required: false` shows it as optional, and `enabled: true, required: true` makes it mandatory at submit. `company_name`, `country_code`, and `region` are always shown and required and cannot be configured. When the object is omitted, `null`, or `{}`, the default applies: only `incorporation_date` is required and every other field is optional.

```json theme={null}
{
  "kyb_registry_fields_config": {
    "registration_number": { "enabled": true, "required": true },
    "incorporation_date": { "enabled": true, "required": true },
    "vat_number": { "enabled": true, "required": false },
    "control_scheme": { "enabled": false, "required": false }
  }
}
```

Configurable field keys: `registration_number`, `incorporation_date`, `legal_address`, `vat_number`, `alternative_names`, `tax_number`, `company_type`, `legal_entity_identifier`, `location_of_registration`, `nature_of_business`, `registered_capital_amount`, `registered_capital_currency`, `website`, `email`, `phone`, `control_scheme`. See [configurable registry form fields](/business-verification/company-data#configurable-registry-form-fields) for the end-user behavior and [VAT validation](/business-verification/company-data#vat-validation-vies) for how `vat_number` is checked against VIES.

## KYB Documents

Feature value: `KYB_DOCUMENTS`.

| Config field | Type | Description |
| - | - | - |
| `kyb_required_document_groups` | object or array | Required. Required KYB document groups. |
| `kyb_document_subtype_config` | object | Per-document subtype enablement and age configuration. |
| `kyb_document_tampering_action` | restricted action | Action when tampering is detected. Use `DECLINE` or `REVIEW`. |
| `kyb_document_critical_mismatch_action` | restricted action | Action for critical document mismatches. Use `DECLINE` or `REVIEW`. |
| `kyb_document_non_critical_mismatch_action` | action | Action for non-critical document mismatches. |
| `kyb_document_age_action` | action | Action when document age rules fail. |
| `kyb_document_max_retry_attempts` | integer | Retry limit for KYB documents. |
| `kyb_document_max_attempts_exceeded_action` | restricted action | Action when max attempts are exceeded. Use `DECLINE` or `REVIEW`. |
| `status_rules` | array | Optional custom status rules. |

## KYB Key People

Feature value: `KYB_KEY_PEOPLE`.

| Config field | Type | Description |
| - | - | - |
| `kyb_ubo_ownership_threshold` | integer | UBO ownership threshold, `0` to `100`. |
| `kyb_shareholder_ownership_threshold` | integer | Shareholder ownership threshold, `0` to `100`. It cannot be higher than `kyb_ubo_ownership_threshold`. |
| `kyb_key_people_ownership_required` | boolean | Requires an ownership percentage on every submitted key person. Enforced at submission and surfaced to the hosted UI, which marks the field as required. Default `false`. |
| `kyb_require_ubo_kyc` | boolean | Requires UBOs to complete a person verification workflow. |
| `kyb_ubo_verification_workflow` | UUID | Required when `kyb_require_ubo_kyc` is `true`. Must reference a non-KYB workflow in the same application. |
| `kyb_require_officer_kyc` | boolean | Requires officers to complete person verification. |
| `kyb_shareholder_verification_workflow` | UUID | Person verification workflow for shareholders. |
| `kyb_officer_verification_workflow` | UUID | Person verification workflow for officers. |
| `kyb_require_corporate_ubo_kyb` | boolean | Requires corporate UBOs to complete KYB. |
| `kyb_corporate_ubo_verification_workflow` | UUID | Required when `kyb_require_corporate_ubo_kyb` is `true`. Must reference a KYB workflow in the same application. |
| `kyb_reject_if_ubo_rejected` | boolean | Rejects the business when a required UBO verification is rejected. |
| `kyb_wait_for_all_ubos` | boolean | Waits for all required UBO verifications before deciding the KYB session. |
| `kyb_role_config` | object or array | Per-role configuration. Each required KYC role must include a `verification_workflow`. |
| `kyb_reuse_verified_individuals` | boolean | Reuses already verified individuals when possible. |
| `kyb_notify_parties_by_email` | boolean | Sends email notifications to invited key people. |
| `kyb_key_people_company_fields` | object | Which company details are collected for a CORPORATE key person. Same list-of-descriptors shape and same rules as `kyb_key_people_person_fields`. Shape: `[{"key": "<field key>", "label": "<label>", "type": "text"\|"number"\|"date"\|"phone"\|"email", "required": true\|false, "custom": true\|false}]`. |
| `kyb_key_people_document_mismatch_action` | action | Verdict when a key person's document disagrees with the details declared for them. |
| `kyb_key_people_person_fields` | object | Which personal details are collected for an individual key person, as an ORDERED LIST of field descriptors (not a map). `key` is required and non-empty; `type` must be one of text, number, date, phone or email; `custom` marks a field the customer added rather than one of the built-ins. Shape: `[{"key": "<field key>", "label": "<label>", "type": "text"\|"number"\|"date"\|"phone"\|"email", "required": true\|false, "custom": true\|false}]`. |
| `kyb_key_people_prefill_from_registry` | boolean | Pre-fill the key-people list from the company registry result, so the customer confirms and corrects rather than typing every officer and shareholder in. |
| `status_rules` | array | Optional custom status rules. |

## Document AI

Feature value: `DOCUMENT_AI`.

Requests one or more documents from the user and extracts the named fields you define with a vision model. Each configured field becomes a branchable value `document_ai.<key>`, and the step's outcome is `document_ai.status`.

| Config field | Type | Description |
| - | - | - |
| `document_ai_documents` | array | Required. The documents to request (at most `3`). Each entry is a document object (see below). |
| `document_ai_unreadable_document_action` | action | Action when the document cannot be read or no data can be extracted. |
| `document_ai_missing_required_fields_action` | action | Action when a required field cannot be extracted. |
| `document_ai_document_tampering_action` | restricted action | Action when the document shows signs of manipulation. Use `DECLINE` or `REVIEW`. |
| `document_ai_name_mismatch_action` | action | Action when the extracted full name does not match the verified identity. Requires a field marked with `is_full_name`. |
| `document_ai_unsupported_file_action` | action | Action when the uploaded file type is not supported. |
| `document_ai_max_attempts_exceeded_action` | restricted action | Action when the user exceeds the retry attempts. Use `DECLINE` or `REVIEW`. |
| `document_ai_name_match_score_threshold` | integer | Minimum score (`0`–`100`) for the extracted full name to match the verified identity. Below it, `document_ai_name_mismatch_action` applies. |
| `document_ai_max_retry_attempts` | integer | Retry limit, `2` to `5`. |
| `status_rules` | array | Optional custom status rules. You can target `document_ai.<key>` fields. |

Each entry in `document_ai_documents` is a document:

| Document field | Type | Description |
| - | - | - |
| `document_key` | string | Required. Stable identifier for the document, unique within the step. |
| `title` | string | Required. Title shown to the user. |
| `description` | string | Required. Instructions shown to the user about what to upload. |
| `title_i18n` | object | Optional. Locale code to translated title (e.g. `{ "es": "Comprobante de fondos" }`). |
| `description_i18n` | object | Optional. Locale code to translated description. |
| `fields` | array | Required. The fields to extract (at least one). Field keys must be unique within the document. |

Each entry in a document's `fields` is a field:

| Field | Type | Description |
| - | - | - |
| `key` | string | Required. Stable JSON key for the extracted value, unique within the document. Branch on it as `document_ai.<key>`. |
| `name` | string | Required. Human-readable field label. |
| `name_i18n` | object | Optional. Locale code to translated field name. |
| `instruction` | string | Optional. Instruction telling the model exactly what to extract for this field. |
| `type` | string | Field type: `text`, `number`, or `date`. Defaults to `text`. |
| `required` | boolean | Whether the field must be present. Defaults to `false`. When `true` and the value is missing, `document_ai_missing_required_fields_action` applies. |
| `is_full_name` | boolean | Optional, defaults to `false`. Marks the field whose value is the document holder's full name, compared against the verified identity (the ID-verification name, or the session's expected details). **At most one field per document** may set it `true`, and it is **not mandatory** — a document may mark none, in which case no name comparison runs for that document. Only `text` fields can be the full name. |

Example:

```json theme={null}
{
  "feature": "DOCUMENT_AI",
  "config": {
    "document_ai_documents": [
      {
        "document_key": "proof_of_funds",
        "title": "Proof of Funds",
        "description": "Upload a recent bank statement.",
        "title_i18n": { "es": "Comprobante de fondos" },
        "fields": [
          {
            "key": "account_holder",
            "name": "Account holder",
            "instruction": "The full name of the account holder as printed on the statement.",
            "type": "text",
            "required": true,
            "is_full_name": true
          },
          {
            "key": "closing_balance",
            "name": "Closing balance",
            "instruction": "The closing balance amount.",
            "type": "number",
            "required": false
          }
        ]
      }
    ],
    "document_ai_missing_required_fields_action": "REVIEW",
    "document_ai_name_mismatch_action": "REVIEW",
    "document_ai_name_match_score_threshold": 80
  }
}
```


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