Individual KYC process

Core concepts

  • The account KYC process is built around submissions, which are essentially sessions with requirements and data you put into them.
  • After fulfilling all requirements, a submission may be closed and becomes uneditable.
  • After the submission has been reviewed, the applicant becomes active.
  • If new data is required, a new submission may be created for an applicant.

In this guide, we'll cover the KYC process for an individual account.

Create an account

You want to call the create account endpoint first if you have not done it yet.

curl --request POST \
     --url https://api.due.network/v1/accounts \
     --header 'accept: application/json' \
     --header 'authorization: Bearer <API_KEY>' \
     --header 'content-type: application/json' \
     --data '
{
  "type": "individual",
  "name": "John Snow",
  "email": "[email protected]",
  "country": "GB",
  "category": "employed"
}
'

Grab the id from the response - this is the ACCOUNT_ID.

{
    "id": "acct_DlsiOQ7RxG1hgJVL",
    // ...
}

Initiate the KYC process

curl --request POST \
     --url https://api.due.network/v1/kyc \
     --header 'Due-Account-Id: <ACCOUNT_ID>' \
     --header 'accept: application/json' \
     --header 'authorization: Bearer <API_KEY>'

This will return a submission entity containing all the requirements you need to fulfill.

{
  "id": "ksub_hvwkgs0ATW36rTpw",
  "applicantId": "acct_DlsiOQ7RxG1hgJVL",
  "status": "open",
  "reason": "initial",
  "requirements": [
    {
      "kind": "static",
      "info": [...],
      "documents": [...],
      "questionnaires": [...]
    },
    {
      "kind": "conditional",
      "if": [...],
      "then": [...]
    },
    {
      "kind": "oneOf",
      "oneOf": [...]
    }
  ],
  "documents": [],
  "questionnaires": []
}

The id field would be the SUBMISSION_ID.

Understanding requirements

Want a visual overview? See the Individual KYC requirements diagram for the current verification flow without diving into the schema structure.


Note: The tax residence country used to determine applicable requirements is derived from the country field provided when creating the account.

The requirements field contains an array of requirement objects. The structure is uniform across all requirement types, allowing for flexible and composable verification flows.

Note: Examples in this documentation are illustrative and may not reflect actual requirements. Always refer to the requirements returned by the API for your specific account.

Top-level structure

The requirements array uses AND logic — every item in the array must be satisfied for the submission to be complete.

Requirement kinds

Each requirement object has a kind field indicating its type:

KindDescription
staticAlways required. Contains info, documents, and/or questionnaires fields.
conditionalOnly required when a condition is met. Contains if and then fields.
oneOfExactly one of the options must be satisfied. Contains a oneOf array.

Static requirements

Static requirements are always required regardless of any conditions.

{
  "kind": "static",
  "info": [
    {
      "key": "firstName",
      "type": "string"
    },
    {
      "key": "lastName",
      "type": "string"
    }
  ],
  "documents": [
    {
      "category": "ID",
      "oneOf": [
        { "type": "PASSPORT" },
        { "type": "ID_CARD", "variants": ["FRONT_SIDE", "BACK_SIDE"] }
      ]
    }
  ],
  "questionnaires": [
    {
      "templateId": "pep_sanctions",
      "questions": [...]
    }
  ]
}

Conditional requirements

Conditional requirements are only required when the if condition is satisfied. The if field contains an array of requirements (using AND logic) that act as the condition. The then field contains an array of requirements to fulfill when the condition is met.

{
  "kind": "conditional",
  "if": [
    {
      "kind": "static",
      "documents": [
        {
          "category": "ID",
          "oneOf": [
            { "type": "PASSPORT" }
          ]
        }
      ]
    }
  ],
  "then": [
    {
      "kind": "static",
      "info": [
        {
          "key": "passportExpiryDate",
          "type": "date",
          "required": true
        }
      ]
    }
  ]
}

In this example, the passportExpiryDate field is only required if a PASSPORT is provided.

OneOf requirements

OneOf requirements allow the applicant to satisfy exactly one of the provided options.

{
  "kind": "oneOf",
  "oneOf": [
    {
      "kind": "static",
      "info": [
        {
          "key": "address",
          "type": "object",
          "fields": [
            { "key": "country", "type": "iso3166_1_alpha2" },
            { "key": "postCode", "type": "string" },
            { "key": "town", "type": "string" },
            { "key": "street", "type": "string" }
          ]
        }
      ]
    },
    {
      "kind": "static",
      "documents": [
        {
          "category": "PROOF_OF_ADDRESS",
          "oneOf": [
            { "type": "UTILITY_BILL" },
            { "type": "OTHER" }
          ]
        }
      ]
    }
  ]
}

In this example, the applicant can either provide a structured address or upload a proof of address document.

Nesting

Requirements can be nested. For example, a conditional requirement's then clause can contain oneOf requirements, and vice versa. This allows for complex verification flows while maintaining a uniform structure.

Document structure

The documents array describes required documents grouped by category. Each category contains a oneOf array listing acceptable document types.

{
  "category": "ID",
  "oneOf": [
    { "type": "PASSPORT" },
    { "type": "ID_CARD", "variants": ["FRONT_SIDE", "BACK_SIDE"] },
    { "type": "RESIDENCE_PERMIT", "variants": ["FRONT_SIDE", "BACK_SIDE"] }
  ]
}

The variants field indicates which parts of a document need to be uploaded. If no variants are specified, a single upload is expected.

Provide applicant information

From the requirements, identify all info fields across static and applicable conditional requirements:

curl --request POST \
     --url https://api.due.network/v1/kyc/submissions/<SUBMISSION_ID>/info \
     --header 'Due-Account-Id: <ACCOUNT_ID>' \
     --header 'accept: application/json' \
     --header 'authorization: Bearer <API_KEY>' \
     --header 'content-type: application/json' \
     --data '
{
  "firstName": "John",
  "lastName": "Snow",
  "dob": "1980-01-01",
  "phone": "+440000000000",
  "taxResidenceCountry": "GB"
}
'

Add verification documents

Step 1: Request document upload token

To upload a document, request a token specifying the document type, issuing country, and filename.

curl --request POST \
     --url https://api.due.network/v1/kyc/submissions/<SUBMISSION_ID>/documents \
     --header 'Due-Account-Id: <ACCOUNT_ID>' \
     --header 'accept: application/json' \
     --header 'authorization: Bearer <API_KEY>' \
     --header 'content-type: application/json' \
     --data '
{
  "type": "PASSPORT",
  "issuingCountry": "GB",
  "filename": "passport.jpg"
}
'

For documents with variants, include the variant field:

{
  "type": "ID_CARD",
  "variant": "FRONT_SIDE",
  "issuingCountry": "GB",
  "filename": "id_card_front.jpg"
}

This returns a token for uploading:

{
    "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9..."
}

Step 2: Upload the document

curl --request POST \
     --url https://api.due.network/v1/kyc/submissions/documents/<TOKEN> \
     --header 'Due-Account-Id: <ACCOUNT_ID>' \
     --header 'accept: application/json' \
     --header 'authorization: Bearer <API_KEY>' \
     --header 'content-type: image/jpeg' \
     --data-binary '@path/to/passport.jpg'

File settings

Upload one file per document as a raw binary body using its upload token. Set Content-Type to the file's actual media type (for example, image/jpeg for a JPEG). When requesting a token, set type to a document type listed in the submission's requirements.

  • Maximum size: 20 MB per file.
  • Identity documents (passport, ID card, driving licence, residence permit): Use JPG, JPEG, PNG, or HEIC. The file must be at least 100 KB or 300 DPI; avoid heavily compressed photos. Use PDF only for an official digital version of an ID, not a scan or photo.
  • Proof of address and company documents (bank statements, utility bills, incorporation certificates, registers): Use JPG, JPEG, PNG, or PDF.
  • Two-sided IDs: Request a separate upload token and upload one file for each side. Set variant to FRONT_SIDE for one token and BACK_SIDE for the other.

Repeat for all required documents based on the applicable requirements.

Complete questionnaires

Submit answers for each questionnaire in the applicable submission requirements before completing the submission. The example below uses kycQuestionnaireV1; always use the template, question IDs, and options returned by the API for your account.

Step 1: Read the questionnaire requirements

Find the questionnaires array in each applicable requirement. Each questionnaire contains a templateId and a questions array describing the questions, answer types, available options, and any conditions.

For kycQuestionnaireV1, collect these answers:

Question IDAnswer typeValues and constraints
purposemultiple_choiceSelect at least one option: trade, payroll, invest, remit, spend, or business.
volumeschoiceSelect one monthly transaction volume: min (less than $1,000), 10k ($1,000–10,000), 50k ($10,000–50,000), or max ($50,000 or more).
occupationchoiceSelect one option ID from the returned occupation options, such as it_software_technology.
sowchoiceSelect one primary source of wealth: employment, investments, savings, inheritance, loans, family, or other.
sow_othertextProvide a description when sow is other. Its requiredWhen condition references question sow and value other.

Step 2: Submit the answers

Send the templateId and an answers array to the questionnaire endpoint. For each answer, set id to the question ID and value to an array of strings—even for a single selection or a text answer. Use option IDs, not their display labels.

This example describes an applicant who plans to receive payroll and make everyday purchases, expects monthly transactions of $1,000–10,000, works in IT, and has employment as their primary source of wealth. Replace the example selections with the applicant's answers.

curl --fail-with-body --request POST \
     --url https://api.due.network/v1/kyc/submissions/<SUBMISSION_ID>/questionnaires \
     --header 'Due-Account-Id: <ACCOUNT_ID>' \
     --header 'accept: application/json' \
     --header 'authorization: Bearer <API_KEY>' \
     --header 'content-type: application/json' \
     --data '
{
  "templateId": "kycQuestionnaireV1",
  "answers": [
    { "id": "purpose", "value": ["payroll", "spend"] },
    { "id": "volumes", "value": ["10k"] },
    { "id": "occupation", "value": ["it_software_technology"] },
    { "id": "sow", "value": ["employment"] }
  ]
}
'

If the applicant selects other for sow, replace the sow answer and include sow_other in the same answers array:

[
  { "id": "sow", "value": ["other"] },
  { "id": "sow_other", "value": ["Proceeds from the sale of my personal car"] }
]

These two entries are an excerpt, not a complete request: keep the answers for purpose, volumes, and occupation as well. When sow is not other, omit sow_other, as in the full request above.

Step 3: Verify the submitted answers

A successful request returns HTTP 200 with the submission. Check its top-level questionnaires array for the submitted template and answers. For the full example above, the relevant response excerpt is:

{
  "questionnaires": [
    {
      "templateId": "kycQuestionnaireV1",
      "answers": [
        { "id": "purpose", "value": ["payroll", "spend"] },
        { "id": "volumes", "value": ["10k"] },
        { "id": "occupation", "value": ["it_software_technology"] },
        { "id": "sow", "value": ["employment"] }
      ]
    }
  ]
}

If the request fails, inspect the response body before continuing. Check that the template and question IDs match the returned requirements, choices use option IDs, every value is an array of strings, and conditional answers such as sow_other are included when applicable. The --fail-with-body option makes curl report HTTP errors while preserving the response body for inspection.

Repeat for any other required questionnaires. If the applicable requirements contain no questionnaires, continue to complete the submission after fulfilling the remaining requirements.

Complete the submission

After all requirements, including applicable questionnaires, are fulfilled, complete the submission:

curl --request POST \
     --url https://api.due.network/v1/kyc/submissions/<SUBMISSION_ID>/complete \
     --header 'Due-Account-Id: <ACCOUNT_ID>' \
     --header 'accept: application/json' \
     --header 'authorization: Bearer <API_KEY>'

After review and approval, a webhook is sent:

{
  "type": "bp.kyc.status_changed",
  "data": {
    "id": "<ACCOUNT_ID>",
    "kyc": {
      "status": "passed"
    }
  }
}

Did this page help you?