# Verify KYC

Source: https://docs.settlemint.com/docs/developers/compliance/verify-kyc
Issue KYC verifications to registered users via API



Issue a KYC attestation after you have reviewed a user's identity data and your issuing account holds trust for the `knowYourCustomer` topic. The Platform API writes the attestation to the user's identity contract, so any asset that requires that topic recognises the user as eligible.

For the web interface approach, see the [user guide](/docs/operators/compliance/verify-kyc).

## Prerequisites [#prerequisites]

You need a Platform URL (for example `https://your-platform.example.com`) and an API key from a user with the **Claim Issuer** (`claimIssuer`) system role. See [Getting Started](/docs/api-reference/reference/getting-started) for API key setup.

Your account also needs a wallet verification method (pincode or 2FA) and must be registered as a [trusted issuer](/docs/developers/compliance/configure-trusted-issuers) for the KYC topic. The target user must have already [registered](/docs/developers/user-management/register-user) and must have an identity contract. Collect and review the user's KYC data according to your operating policy before proceeding.

## What this API flow changes [#what-this-api-flow-changes]

Auditors and integrators need to understand what the API writes. A KYC attestation is a cryptographically signed record stored on the user's identity contract. This attestation is evaluated when an asset or system policy requires the `knowYourCustomer` topic.

The flow separates three records:

| Record         | Role                                                                             |
| -------------- | -------------------------------------------------------------------------------- |
| User account   | Locates the participant and wallet address for the platform user.                |
| KYC profile    | Stores the reviewed KYC data and produces the content hash used as claim data.   |
| Identity claim | Records the trusted issuer's signed attestation on the user's identity contract. |

DALP does not require KYC for every user by default. The platform enforces KYC when an asset or system policy requires the `knowYourCustomer` claim.

Some assets require a different claim, no KYC topic, or additional topics depending on the asset's compliance settings. For the broader model, see [Compliance Overview](/docs/operators/compliance/overview).

## Issuing KYC verifications [#issuing-kyc-verifications]

<Steps>
  <Step>
    ### Identify user to verify [#identify-user-to-verify]

    You need the user's wallet address and userId to issue the attestation. If you do not have them, search by email or name. The search returns both fields in a single response.

    ```bash
    curl -X GET "https://your-platform.example.com/api/user/search?query=investor@example.com" \
      -H "X-Api-Key: YOUR_API_KEY"
    ```

    Response:

    ```json
    [
      {
        "id": "usr_abc123",
        "name": "John Investor",
        "wallet": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
        "role": "member"
      }
    ]
    ```

    Save both the `id` (userId) and `wallet` address. You need both in later steps.
  </Step>

  <Step>
    ### List claim topics [#list-claim-topics]

    Query the available topics to identify the KYC topic ID. The system returns all registered claim topics; use the `name` field to find `knowYourCustomer`.

    ```bash
    curl -X GET "https://your-platform.example.com/api/system/claim-topics" \
      -H "X-Api-Key: YOUR_API_KEY"
    ```

    Response:

    ```json
    [
      {
        "id": "0x534b8f03c16c92c70d1da1d2fae43b98352bf3d7...",
        "topicId": "26984799302505749158794800959285050858086405868089409909048783980951278841746",
        "name": "knowYourCustomer",
        "signature": "string claim",
        "registry": {
          "id": "0x534b8f03c16c92c70d1da1d2fae43b98352bf3d7"
        }
      },
      {
        "id": "0x534b8f03c16c92c70d1da1d2fae43b98352bf3d7...",
        "topicId": "15733030998618876990024220391915773205162379317494393310546829862321881862123",
        "name": "accreditedInvestor",
        "signature": "string claim",
        "registry": {
          "id": "0x534b8f03c16c92c70d1da1d2fae43b98352bf3d7"
        }
      },
      {
        "id": "0x534b8f03c16c92c70d1da1d2fae43b98352bf3d7...",
        "topicId": "39526553109170329799339511574661256630735485618560740361645615581310848276505",
        "name": "qualifiedInstitutionalInvestor",
        "signature": "string claim",
        "registry": {
          "id": "0x534b8f03c16c92c70d1da1d2fae43b98352bf3d7"
        }
      }
      // ... additional topics available
    ]
    ```

    The KYC topic has `name: "knowYourCustomer"`. Pass this name in the `claim.topic` field when you issue the attestation in step 6.

    ![Verification topic registry for KYC claim types](/docs/screenshots/identity/verification-topics.webp)
  </Step>

  <Step>
    ### Create KYC profile [#create-kyc-profile]

    If the user has no KYC profile yet, create one with their personal details. The platform returns a `contentHash` you will use as the claim value in step 4.

    ```bash
    curl -X POST "https://your-platform.example.com/api/user/usr_abc123/kyc/upsert" \
      -H "X-Api-Key: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "userId": "usr_abc123",
        "firstName": "John",
        "lastName": "Investor",
        "dob": "1985-03-15T00:00:00.000Z",
        "country": "BE",
        "residencyStatus": "resident",
        "nationalId": "123456789"
      }'
    ```

    Response:

    ```json
    {
      "changed": true,
      "currentVersion": {
        "id": "ver_xyz789",
        "number": 1,
        "contentHash": "1e1329fc9216a119e9c596084cd353949f0754ddfa53014760ae6cc7ef8d1d35",
        "createdAt": "2024-01-15T10:00:00.000Z"
      },
      "profile": {
        "id": "kyc_abc123",
        "userId": "usr_abc123",
        "firstName": "John",
        "lastName": "Investor",
        "dob": "1985-03-15T00:00:00.000Z",
        "country": "BE",
        "residencyStatus": "resident",
        "nationalId": "123456789",
        "createdAt": "2024-01-15T10:00:00.000Z",
        "updatedAt": "2024-01-15T10:00:00.000Z"
      }
    }
    ```

    Save the `contentHash` from `currentVersion`. You pass it as the claim value in step 4.

    Required fields: `userId` (from step 1) plus at least one of `firstName`, `lastName`, `dob`, `country`, `residencyStatus`, or `nationalId`.

    Field constraints: `dob` requires the user to be at least 18 years old; `country` must be an ISO 3166-1 alpha-2 code (e.g., "BE", "US", "DE"); `residencyStatus` must be one of `"resident"`, `"non_resident"`, `"dual_resident"`, or `"unknown"`.
  </Step>

  <Step>
    ### Get claim value [#get-claim-value]

    Different topics require different claim values. For the `knowYourCustomer` topic, the value is the KYC profile content hash. If you created the profile in step 3, use that `contentHash`. Otherwise, fetch the hash from the existing profile:

    ```bash
    curl -X GET "https://your-platform.example.com/api/user/usr_abc123/kyc/read" \
      -H "X-Api-Key: YOUR_API_KEY"
    ```

    Response:

    ```json
    {
      "id": "kyc_xyz789",
      "userId": "usr_abc123",
      "firstName": "John",
      "lastName": "Investor",
      "dob": "1985-03-15T00:00:00.000Z",
      "country": "BE",
      "residencyStatus": "resident",
      "nationalId": "123456789",
      "contentHash": "1e1329fc9216a119e9c596084cd353949f0754ddfa53014760ae6cc7ef8d1d35",
      "createdAt": "2024-01-15T10:00:00.000Z",
      "updatedAt": "2024-01-15T10:00:00.000Z"
    }
    ```

    Pass the `contentHash` value as the claim data: a hex string without the `0x` prefix.

    For boolean topics, skip this API call and use `"true"` as the claim value. These topics are boolean:

    * `antiMoneyLaundering`
    * `accreditedInvestor`
    * `accreditedInvestorVerified`
    * `professionalInvestor`
    * `qualifiedInstitutionalInvestor`
    * `regulationS`
  </Step>

  <Step>
    ### Get the identity contract address [#get-the-identity-contract-address]

    Look up the user's identity contract by wallet address. The API returns the contract address in the `id` field, separate from `account.id` (the wallet address).

    ```bash
    curl -X GET "https://your-platform.example.com/api/system/identity/by-wallet/0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb" \
      -H "X-Api-Key: YOUR_API_KEY"
    ```

    Response:

    ```json
    {
      "id": "0x8e5F72f6E5b3B4D1234567890AbCdEf1234567890",
      "account": {
        "id": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
        "contractName": null
      },
      "isContract": false,
      "hasIdentity": true,
      "registered": {
        "isRegistered": true,
        "country": "BE"
      },
      "claims": []
    }
    ```

    Pass the `id` value (the identity contract address) in the next step. Do not use `account.id`, which is the user's wallet address.
  </Step>

  <Step>
    ### Issue the KYC claim [#issue-the-kyc-claim]

    Write the attestation to the user's identity contract. Pass the identity contract address from step 5 as `targetIdentityAddress`.

    ```bash
    curl -X POST "https://your-platform.example.com/api/system/identity/claim/issue" \
      -H "X-Api-Key: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "targetIdentityAddress": "0x8e5F72f6E5b3B4D1234567890AbCdEf1234567890",
        "claim": {
          "topic": "knowYourCustomer",
          "data": {
            "claim": "1e1329fc9216a119e9c596084cd353949f0754ddfa53014760ae6cc7ef8d1d35"
          }
        },
        "walletVerification": {
          "secretVerificationCode": "YOUR_PINCODE"
        }
      }'
    ```

    Response:

    ```json
    {
      "txHash": "0x8d95bfd5381478d90992d3e2e64c73178e46bb18592bfcbffdf899f2407aee9b",
      "success": true,
      "claimTopic": "knowYourCustomer",
      "targetWallet": "0x8e5F72f6E5b3B4D1234567890AbCdEf1234567890"
    }
    ```

    <Callout type="info" title="Response field naming">
      The `targetWallet` field in the response contains the address of the identity contract (matching the `targetIdentityAddress`
      from the request), not the user's wallet address.
    </Callout>
  </Step>

  <Step>
    ### Confirm the claim [#confirm-the-claim]

    Query the identity contract to check that the platform recorded the attestation. Look for a `claims` entry with `name: "knowYourCustomer"` and `isTrusted: true`.

    ```bash
    curl -X GET "https://your-platform.example.com/api/system/identity/by-wallet/0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb" \
      -H "X-Api-Key: YOUR_API_KEY"
    ```

    A successful verification returns a response similar to the following.

    ```json
    {
      "id": "0x8e5F72f6E5b3B4D1234567890AbCdEf1234567890",
      "account": {
        "id": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
        "contractName": null
      },
      "isContract": false,
      "hasIdentity": true,
      "registered": {
        "isRegistered": true,
        "country": "BE"
      },
      "claims": [
        {
          "id": "0xc057fb9c66650cfaf24192baa697f463b0ddf81eb8b764c460cd1841967742a63551c161fda0528a93030bdcc6efb139f05b3a912053c839655f0147e7370e77bff1df5c5b637964",
          "name": "knowYourCustomer",
          "signature": "0xfe5b3c723b68a482c5cfd4fc38c384a217e748b5375340d2b05e8684f5d0558c0eabb331636cb5182d424e478cd6ba3db6f6efca452b570e6d6516d1b8bd24cf1b",
          "revoked": false,
          "issuer": {
            "id": "0xD3c16123446a6fe39635adD185574e7c6DC617Fe"
          },
          "values": [
            {
              "key": "claim",
              "value": "1e1329fc9216a119e9c596084cd353949f0754ddfa53014760ae6cc7ef8d1d35"
            }
          ],
          "isTrusted": true
        }
      ]
    }
    ```

    Verify the response contains a `claims` entry with `name: "knowYourCustomer"`. Confirm `isTrusted` is `true` (your account is a trusted issuer for this topic), `revoked` is `false` (the attestation is active), and `issuer.id` matches your issuing account's identity contract. The `signature` field holds the issuer's cryptographic signature; `values` holds the contentHash. Once these fields appear, the user can receive assets that require the KYC topic.

    ![User identity with verification status](/docs/screenshots/identity/user.webp)
  </Step>
</Steps>

## Request parameters [#request-parameters]

The tables below list the parameters for each API call in this flow. Each subsection corresponds to a step in the procedure.

### User search [#user-search]

Use this endpoint when you need to look up a user by email, name, or wallet address before issuing their attestation.

| Parameter | Type   | Required | Description                         |
| --------- | ------ | -------- | ----------------------------------- |
| `query`   | string | Yes      | Email, name, or wallet to search by |

### Claim issue [#claim-issue]

Pass these parameters in the request body to write the attestation on-chain to the user's identity contract.

| Parameter               | Type   | Required | Description                                              |
| ----------------------- | ------ | -------- | -------------------------------------------------------- |
| `targetIdentityAddress` | string | Yes      | Identity contract address (0x...) from identity lookup   |
| `claim.topic`           | string | Yes      | Claim topic name (e.g., `"knowYourCustomer"`)            |
| `claim.data.claim`      | string | Yes      | Claim value (contentHash for KYC, `"true"` for booleans) |
| `walletVerification`    | object | Yes      | Your wallet verification (pincode or totp)               |

<Callout type="info" title="Identity address required">
  The `targetIdentityAddress` must be the identity contract address (for example `identity.id` from the identity
  lookup). Do not send the user's wallet address.
</Callout>

### Wallet verification object [#wallet-verification-object]

Include this object in every mutation request to authorize the on-chain write. The `secretVerificationCode` depends on your configured method: a 6-digit code for PINCODE, a backup code for SECRET\_CODES, or a TOTP for OTP.

| Field                    | Type   | Description                                    |
| ------------------------ | ------ | ---------------------------------------------- |
| `secretVerificationCode` | string | 6-digit pincode or TOTP code                   |
| `verificationType`       | string | "PINCODE" (default), "SECRET\_CODES", or "OTP" |

Omit `verificationType` to default to `"PINCODE"`. A code must pass validation before the transaction is submitted.

### Claim issue response fields [#claim-issue-response-fields]

The Platform API returns these fields when the attestation write succeeds.

| Field          | Type   | Description                                       |
| -------------- | ------ | ------------------------------------------------- |
| `txHash`       | string | Transaction hash for the claim write              |
| `success`      | bool   | `true` when the platform wrote the claim on-chain |
| `claimTopic`   | string | Topic name the platform issued                    |
| `targetWallet` | string | Identity contract address that received the claim |

## Common claim topics [#common-claim-topics]

The table below shows the topics most commonly used with this flow. Use the `Name` column as the `claim.topic` value in your request.

| Topic ID | Name                             | Claim Value     | Description                       |
| -------- | -------------------------------- | --------------- | --------------------------------- |
| 1        | `knowYourCustomer`               | KYC contentHash | Basic identity verification       |
| 2        | `accreditedInvestor`             | `"true"`        | US qualified investor status      |
| 3        | `qualifiedInstitutionalInvestor` | `"true"`        | EU institutional investor rules   |
| 4        | `antiMoneyLaundering`            | `"true"`        | Source of funds verification      |
| 5        | `professionalInvestor`           | `"true"`        | MiFID professional classification |
| 6        | `accreditedInvestorVerified`     | `"true"`        | Verified accredited investor      |
| 7        | `regulationS`                    | `"true"`        | Regulation S compliance           |

## Best practices [#best-practices]

### KYC standards [#kyc-standards]

Follow your written KYC policy consistently and record evidence for every decision. Log the rationale for each approval or rejection for audit purposes, and use consistent data formats across all attestations.

### Data privacy [#data-privacy]

Store minimal personal data on-chain; use hashes to reference sensitive data held off-chain. Keep off-chain records in secure storage and follow applicable data-protection regulations.

### Claim quality [#claim-quality]

Confirm that documents are authentic before approving any attestation. Cross-check data against multiple sources, watch for red flags, and apply your KYC standards consistently across every applicant.

## Troubleshooting [#troubleshooting]

Use the table below to identify the cause and corrective step for each error.

| Issue                     | Solution                                                                                          |
| ------------------------- | ------------------------------------------------------------------------------------------------- |
| `401 Unauthorized`        | API key is invalid, expired, or disabled                                                          |
| `403 USER_NOT_AUTHORIZED` | Ensure your account has the `claimIssuer` system role                                             |
| `Not a trusted issuer`    | Add your account as a trusted issuer for the KYC topic first                                      |
| `Identity not found`      | The user must register first; see [Register User](/docs/developers/user-management/register-user) |
| `Claim already exists`    | The user already holds this claim                                                                 |
| `No KYC data`             | Create a KYC profile first; see the [Create KYC profile](#create-kyc-profile) step above          |

## Related guides [#related-guides]

* [Register User](/docs/developers/user-management/register-user) - Register users before issuing claims
* [Create Users](/docs/developers/user-management/create-users) - Create user accounts with wallets and on-chain identities
* [Configure Trusted Issuers](/docs/operators/compliance/configure-trusted-issuers) - Set up issuer permissions
* [Compliance Overview](/docs/operators/compliance/overview) - Full compliance model reference
* [Verify KYC (User Guide)](/docs/operators/compliance/verify-kyc) - Web interface approach
