> ## 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.

# Create transaction

> Open a verification with the rules you want enforced, and mint the SDK token.

```http theme={null}
POST /api/account/integration/kyc/transaction
```

Opens a verification for a customer and returns the `sdk_token` your app needs to run the
flow. This is where you declare what should be verified and what counts as acceptable: the whole configuration is documented on this page.

## Request

<ParamField header="Authorization" type="string" required>
  `Bearer {access_token}` — see [Authentication](/api-reference/authentication).
</ParamField>

<ParamField body="customer_id" type="string (UUID)" required>
  The customer this verification belongs to, from
  [Create customer](/api-reference/customers/create-customer).
</ParamField>

<ParamField body="transaction_type" type="string" required>
  Which checks to run.

  * `DOCUMENT_AND_BIOMETRIC` — document capture and liveness
  * `DOCUMENT_ONLY` — document capture only
  * `BIOMETRIC_ONLY` — liveness only
</ParamField>

<ParamField body="config" type="object">
  The rules to enforce. Every field is optional and falls back to the default listed below, so `{}` is valid. Build it on your server from your own records — never from values sent by the app.

  <Expandable title="config">
    <ParamField body="transaction_ttl_minutes" type="integer" default="1440">
      How long the user has to complete the flow, in minutes, counted from creation. Range `1`–`1440`. When it elapses the transaction becomes `EXPIRED` and the `sdk_token` stops working.
    </ParamField>

    <ParamField body="webhook" type="object">
      Where chmod notifies you as the transaction changes status. Omit it and nothing is sent — you would then have to poll.

      <Expandable title="webhook">
        <ParamField body="url" type="string" required>
          Must start with `https://`. See [Webhooks](/results/webhooks) for the request, the signature and how to verify it.
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField body="device_policy" type="object">
      How each device-integrity signal affects the decision. Every field takes `REJECT`, `WARN` or `IGNORE`. Applies to all three transaction types.

      <Expandable title="device_policy">
        <ParamField body="on_compromised_device" default="REJECT" type="string">
          The device is rooted, jailbroken, or running under a debugger. Emits `COMPROMISED_DEVICE`.
        </ParamField>

        <ParamField body="on_developer_mode" default="WARN" type="string">
          Developer mode is enabled. Emits `DEVELOPER_MODE_ENABLED`.
        </ParamField>

        <ParamField body="on_emulator" default="REJECT" type="string">
          The capture was made on an emulator or simulator. Emits `EMULATOR_DETECTED`.
        </ParamField>

        <ParamField body="on_vpn_or_proxy" default="WARN" type="string">
          The connection is routed through a VPN, a proxy or Tor. Emits `VPN_OR_PROXY_DETECTED`.
        </ParamField>

        <ParamField body="on_gps_mock_location" default="REJECT" type="string">
          The reported GPS location is simulated. Emits `MOCK_LOCATION_DETECTED`. Requires the SDK to request location permission.
        </ParamField>

        <ParamField body="on_gps_ip_location_mismatch" default="WARN" type="string">
          The GPS country differs from the IP country. Emits `GEO_IP_MISMATCH`. Requires the SDK to request location permission.
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField body="document_policy" type="object">
      Which documents are accepted, and what the extracted data must match. Applies to `DOCUMENT_AND_BIOMETRIC` and `DOCUMENT_ONLY`; ignored for `BIOMETRIC_ONLY`.

      <Expandable title="document_policy">
        <ParamField body="document_eligibility" type="object">
          Omit it to accept every supported document.

          <Expandable title="document_eligibility">
            <ParamField body="filter_mode" type="string">
              `ALLOW` — only the listed documents are accepted. `REJECT` — every supported document is accepted except the listed ones.
            </ParamField>

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

              * `country` (string, required) — ISO 3166-1 alpha-2, for example `AR`.
              * `type` (string, required) — `NATIONAL_ID`, `PASSPORT`, `DRIVER_LICENSE`, `RESIDENT_PERMIT` or `TEMP_PERMIT_ID`.
              * `allow_expired` (boolean, required) — whether an expired document of this exact country and type is accepted. When `false`, an expired document emits `EXPIRED_DOCUMENT`.

              A document not in the supported list emits `DOCUMENT_TYPE_NOT_SUPPORTED`; one that is supported but excluded here emits `DOCUMENT_NOT_ELIGIBLE`.
            </ParamField>
          </Expandable>
        </ParamField>

        <ParamField body="data_matching" type="object">
          Values the document must agree with. Every field is optional; every mismatch is a `REJECT`.

          <Expandable title="data_matching">
            <ParamField body="given_names" type="object">
              `{ "value": string, "similarity_min_score": number }`. Compared by text similarity; `similarity_min_score` ranges `0.1`–`1.0`, where `1.0` is an exact match. Emits `NAME_MISMATCH` with `{ score, threshold }`.
            </ParamField>

            <ParamField body="surnames" type="object">
              Same shape and semantics as `given_names`. Emits `SURNAME_MISMATCH`.
            </ParamField>

            <ParamField body="gender" type="string">
              `M`, `F` or `X`. Exact match. Emits `GENDER_MISMATCH`.
            </ParamField>

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

            <ParamField body="document_issuing_country" type="string">
              ISO 3166-1 alpha-2. Exact match. Emits `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. Emits `DOCUMENT_NUMBER_MISMATCH`.
            </ParamField>

            <ParamField body="date_of_birth" type="string">
              ISO-8601 date, `YYYY-MM-DD`. Exact match. Emits `DATE_OF_BIRTH_MISMATCH`.
            </ParamField>
          </Expandable>
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField body="biometric_policy" type="object">
      Liveness and face-comparison thresholds, expected age and gender. Applies to `DOCUMENT_AND_BIOMETRIC` and `BIOMETRIC_ONLY`; ignored for `DOCUMENT_ONLY`.

      <Expandable title="biometric_policy">
        <ParamField body="liveness_min_score" default="0.85" type="number">
          Minimum confidence that a real, present person was captured. Range `0.1`–`1.0`. Below it, `LIVENESS_LOW_CONFIDENCE` is emitted.
        </ParamField>

        <ParamField body="face_comparison_min_score" default="0.85" type="number">
          Minimum confidence for two faces to count as the same person. Range `0.1`–`1.0`. Applies to every comparison. A failed comparison emits `DOCUMENT_VS_LIVENESS_FACE_MISMATCH`, `LIVENESS_VS_IDENTITY_REF_FACE_MISMATCH` or `DOCUMENT_VS_IDENTITY_REF_FACE_MISMATCH`.
        </ParamField>

        <ParamField body="require_face_comparison" default="true" type="boolean">
          Whether face comparison runs at all. `false` skips every comparison and emits the informational `FACE_COMPARISON_DISABLED`.
        </ParamField>

        <ParamField body="expected_gender" default="null" type="string | null">
          `MALE`, `FEMALE`, or `null` to accept any. Estimated from the selfie. Emits `LIVENESS_GENDER_MISMATCH_EXPECTED`.
        </ParamField>

        <ParamField body="expected_min_age" default="null" type="integer | null">
          Minimum accepted age, `0`–`200`. Estimated from the selfie. Emits `LIVENESS_AGE_OUT_OF_RANGE`.
        </ParamField>

        <ParamField body="expected_max_age" default="null" type="integer | null">
          Maximum accepted age, `0`–`200`. Must not be lower than `expected_min_age`.
        </ParamField>

        <ParamField body="on_another_customer_repeated_face" default="REJECT" type="string">
          `REJECT`, `WARN` or `IGNORE` when the face already belongs to a different customer in your account. Accepted and validated, **not enforced yet**.
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField body="blacklist_policy" type="object">
      How to react to a face on a blocklist. Both fields take `REJECT`, `WARN` or `IGNORE`. Accepted and validated, **not enforced yet**.

      <Expandable title="blacklist_policy">
        <ParamField body="on_internal_face_blacklist_match" default="REJECT" type="string">
          The face matches your organization's own blocklist. Emits `INTERNAL_FACE_BLACKLIST_MATCH`.
        </ParamField>

        <ParamField body="on_global_face_blacklist_match" default="REJECT" type="string">
          The face matches the platform-wide blocklist. Emits `GLOBAL_FACE_BLACKLIST_MATCH`.
        </ParamField>
      </Expandable>
    </ParamField>
  </Expandable>
</ParamField>

## Response

<ResponseField name="transaction_id" type="string (UUID)">
  The transaction identifier. Use it to
  [fetch the result](/api-reference/transactions/get-transaction); it also arrives on the
  webhook and is returned to your app by the SDK.
</ResponseField>

<ResponseField name="sdk_token" type="string (JWT)">
  Pass this to your mobile app and hand it to the SDK. Scoped to this transaction only.
</ResponseField>

<ResponseField name="token_type" type="string">
  Always `Bearer`.
</ResponseField>

<ResponseField name="expires_in" type="integer">
  SDK token lifetime in seconds, derived from `transaction_ttl_minutes`.
</ResponseField>

## Example

A full onboarding configuration, with every policy set:

<CodeGroup>
  ```bash cURL lines theme={null}
  curl -X POST https://{your-api-host}/api/account/integration/kyc/transaction \
    -H "Authorization: Bearer $ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "customer_id": "4a63b11c-803d-4126-bf91-d5f8290ff0a5",
      "transaction_type": "DOCUMENT_AND_BIOMETRIC",
      "config": {
        "transaction_ttl_minutes": 60,
        "webhook": {
          "url": "https://api.example.com/hooks/chmod"
        },
        "device_policy": {
          "on_compromised_device": "REJECT",
          "on_developer_mode": "WARN",
          "on_emulator": "REJECT",
          "on_vpn_or_proxy": "WARN",
          "on_gps_mock_location": "REJECT",
          "on_gps_ip_location_mismatch": "WARN"
        },
        "document_policy": {
          "document_eligibility": {
            "filter_mode": "ALLOW",
            "target_documents": [
              { "country": "AR", "type": "NATIONAL_ID", "allow_expired": false },
              { "country": "AR", "type": "PASSPORT",    "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"
          }
        },
        "biometric_policy": {
          "liveness_min_score": 0.9,
          "face_comparison_min_score": 0.9,
          "require_face_comparison": true,
          "expected_gender": null,
          "expected_min_age": 18,
          "expected_max_age": 120,
          "on_another_customer_repeated_face": "REJECT"
        },
        "blacklist_policy": {
          "on_internal_face_blacklist_match": "REJECT",
          "on_global_face_blacklist_match": "REJECT"
        }
      }
    }'
  ```

  ```typescript Node.js lines theme={null}
  const res = await fetch(`${CHMOD_API_URL}/api/account/integration/kyc/transaction`, {
    method: "POST",
    headers: {
      Authorization:  `Bearer ${accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      customer_id:      user.chmodCustomerId,
      transaction_type: "DOCUMENT_AND_BIOMETRIC",
      config: {
        transaction_ttl_minutes: 60,
        webhook: { url: `${process.env.PUBLIC_URL}/hooks/chmod` },
        device_policy: {
          on_compromised_device: "REJECT",
          on_emulator:           "REJECT",
          on_vpn_or_proxy:       "WARN",
        },
        document_policy: {
          document_eligibility: {
            filter_mode: "ALLOW",
            target_documents: [
              { country: "AR", type: "NATIONAL_ID", allow_expired: false },
            ],
          },
          // Match what the document says against what you already hold on file.
          data_matching: {
            given_names:   { value: user.firstName, similarity_min_score: 0.85 },
            surnames:      { value: user.lastName,  similarity_min_score: 0.85 },
            date_of_birth: user.dateOfBirth,
          },
        },
        biometric_policy: {
          liveness_min_score:        0.9,
          face_comparison_min_score: 0.9,
          require_face_comparison:   true,
        },
      },
    }),
  });

  const { transaction_id, sdk_token } = await res.json();
  ```

  ```python Python lines theme={null}
  res = requests.post(
      f"{CHMOD_API_URL}/api/account/integration/kyc/transaction",
      headers={"Authorization": f"Bearer {access_token}"},
      json={
          "customer_id":      user.chmod_customer_id,
          "transaction_type": "DOCUMENT_AND_BIOMETRIC",
          "config": {
              "transaction_ttl_minutes": 60,
              "webhook": {"url": f"{PUBLIC_URL}/hooks/chmod"},
              "document_policy": {
                  "document_eligibility": {
                      "filter_mode": "ALLOW",
                      "target_documents": [
                          {"country": "AR", "type": "NATIONAL_ID", "allow_expired": False},
                      ],
                  },
              },
              "biometric_policy": {
                  "liveness_min_score": 0.9,
                  "require_face_comparison": True,
              },
          },
      },
  )
  data = res.json()
  ```
</CodeGroup>

```json Response lines theme={null}
{
  "transaction_id": "34dc4204-2b57-42ae-a3bc-1d114935b98f",
  "sdk_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600
}
```

## Defaults

What you get when you omit a field entirely:

| Field | Default |
| - | - |
| `transaction_ttl_minutes` | `1440` (24 hours) |
| `webhook` | none — no notification is sent |
| `device_policy.on_compromised_device` | `REJECT` |
| `device_policy.on_developer_mode` | `WARN` |
| `device_policy.on_emulator` | `REJECT` |
| `device_policy.on_vpn_or_proxy` | `WARN` |
| `device_policy.on_gps_mock_location` | `REJECT` |
| `device_policy.on_gps_ip_location_mismatch` | `WARN` |
| `document_policy.document_eligibility` | every supported document is accepted |
| `document_policy.data_matching` | nothing is matched |
| `biometric_policy.liveness_min_score` | `0.85` |
| `biometric_policy.face_comparison_min_score` | `0.85` |
| `biometric_policy.require_face_comparison` | `true` |
| `biometric_policy.expected_gender` | `null` (any) |
| `biometric_policy.expected_min_age` / `expected_max_age` | `null` (any) |
| `biometric_policy.on_another_customer_repeated_face` | `REJECT` |
| `blacklist_policy.on_internal_face_blacklist_match` | `REJECT` |
| `blacklist_policy.on_global_face_blacklist_match` | `REJECT` |

## Going deeper

Each policy has a reference page with the supporting detail — the supported document matrix, how the three face comparisons work, what happens when a device signal is missing, and how to pick thresholds:

<CardGroup cols={2}>
  <Card title="Device policy" icon="mobile-screen-button" href="/configuration/device-policy" />

  <Card title="Document policy" icon="id-card" href="/configuration/document-policy" />

  <Card title="Biometric policy" icon="face-viewfinder" href="/configuration/biometric-policy" />

  <Card title="Blacklist policy" icon="ban" href="/configuration/blacklist-policy" />
</CardGroup>

## Next

<CardGroup cols={2}>
  <Card title="Get transaction" icon="magnifying-glass" href="/api-reference/transactions/get-transaction">
    Read the status and the decision.
  </Card>

  <Card title="Webhooks" icon="webhook" href="/results/webhooks">
    Be notified as soon as the decision exists.
  </Card>
</CardGroup>


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