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

# KYC onboarding

> Provision a Due customer, complete KYC, and unlock channels with endorsements.

## Overview

Onboard a client before running payin or payout flows: provision the Due customer, complete KYC (hosted or API-driven), request endorsements for the channels you need, and accept terms of service when required.

<Note>
  Portal proxies these endpoints to Due and returns Due's responses verbatim. Example responses below are illustrative; exact fields come from Due and may evolve.
</Note>

## 1. Provision the Due customer

Call `POST /customers` to create the Due customer for the authenticated client.

```bash theme={null}
curl --request POST \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/due/customers \
  --header 'Authorization: Bearer [token]' \
  --header 'Content-Type: application/json' \
  --data '{
    "type": "individual",
    "name": "Jane Doe",
    "email": "jane@example.com",
    "country": "US",
    "category": "self_employed",
    "kycReturnUrl": "https://example.com/due/return"
  }'
```

* `type` is `individual` or `business`.
* `kycReturnUrl` is optional and controls where the user lands after a hosted KYC session.

This endpoint is idempotent: calling it again for the same client returns the existing Due customer instead of creating a new one. Use `GET /customers` to fetch the customer and its status at any time.

## 2. Check KYC state and requirements

`GET /kyc` returns the customer's KYC state and outstanding requirements. Drive your onboarding UI from this response.

```bash theme={null}
curl --request GET \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/due/kyc \
  --header 'Authorization: Bearer [token]'
```

## 3. Complete KYC

Due supports two paths. The hosted session is the fastest way to get started; the API-driven flow gives you full control of the UI.

### Option A: hosted KYC session (recommended)

Call `POST /kyc/session` to create a hosted session, then redirect the user to the returned URL.

```bash theme={null}
curl --request POST \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/due/kyc/session \
  --header 'Authorization: Bearer [token]'
```

After the user finishes, they are sent to the `kycReturnUrl` you provided at provisioning. Subscribe to [webhooks](/integrations/On-Off-Ramp/due-webhooks) to detect approval. `GET /kyc` returns the current state whenever you need to read it directly.

### Option B: API-driven KYC (build your own UI)

#### 1. Start a submission

```bash theme={null}
curl --request POST \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/due/kyc \
  --header 'Authorization: Bearer [token]'
```

#### 2. Read the submission's required fields

```bash theme={null}
curl --request GET \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/due/kyc/submissions/{submissionId} \
  --header 'Authorization: Bearer [token]'
```

#### 3. Submit collected information

`POST /kyc/submissions/{submissionId}/info` accepts the form values as key-value pairs. Due validates them and returns field-level errors for anything missing or malformed.

```bash theme={null}
curl --request POST \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/due/kyc/submissions/{submissionId}/info \
  --header 'Authorization: Bearer [token]' \
  --header 'Content-Type: application/json' \
  --data '{
    "firstName": "Jane",
    "lastName": "Doe",
    "dateOfBirth": "1990-01-31"
  }'
```

#### 4. Upload documents

First create the document against the submission to receive an upload token:

```bash theme={null}
curl --request POST \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/due/kyc/submissions/{submissionId}/documents \
  --header 'Authorization: Bearer [token]' \
  --header 'Content-Type: application/json' \
  --data '{
    "kind": "passport"
  }'
```

Example response:

```json theme={null}
{
  "data": {
    "token": "doc_upload_token"
  }
}
```

Then upload the file bytes with that token:

```bash theme={null}
curl --request POST \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/due/kyc/submissions/documents/{token} \
  --header 'Authorization: Bearer [token]' \
  --header 'Content-Type: application/json' \
  --data '{
    "dataBase64": "[base64-encoded file]",
    "filename": "passport.jpg"
  }'
```

* `dataBase64` is required. The decoded file can be up to 10MB.
* The MIME type is inferred from the `filename` extension (`jpg`, `jpeg`, `png`, `webp`, `heic`, `pdf`), or pass `contentType` explicitly.

#### 5. Complete the submission

```bash theme={null}
curl --request POST \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/due/kyc/submissions/{submissionId}/complete \
  --header 'Authorization: Bearer [token]'
```

#### 6. Detect the outcome

Subscribe to [webhooks](/integrations/On-Off-Ramp/due-webhooks) to learn when the customer is approved. `GET /kyc` returns the current state whenever you need to read it directly.

## 4. Endorsements

Some channels require channel-specific approvals on top of base KYC. Each channel from `GET /channels` lists its `endorsementsRequired`.

List the customer's endorsements:

```bash theme={null}
curl --request GET \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/due/kyc/endorsements \
  --header 'Authorization: Bearer [token]'
```

Request an endorsement by code:

```bash theme={null}
curl --request POST \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/due/kyc/endorsements/{code} \
  --header 'Authorization: Bearer [token]'
```

<Note>
  Requesting an endorsement that already exists returns a `409`. Treat it as already in progress and fetch its state instead.
</Note>

Check an endorsement's state and any extra requirements it adds:

```bash theme={null}
curl --request GET \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/due/kyc/endorsements/{code} \
  --header 'Authorization: Bearer [token]'
```

If an endorsement adds requirements, they show up in `GET /kyc`; collect them through the same submission flow above.

## 5. Terms of service

Some requirements include a terms of service token. Fetch the document and record the user's acceptance:

```bash theme={null}
curl --request GET \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/due/tos/{token} \
  --header 'Authorization: Bearer [token]'
```

```bash theme={null}
curl --request POST \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/due/tos/{token} \
  --header 'Authorization: Bearer [token]' \
  --header 'Content-Type: application/json' \
  --data '{
    "ipAddress": "203.0.113.10"
  }'
```

<Note>
  `ipAddress` is required and should be the end user's IP address, not your server's.
</Note>

<Warning>
  Transfer, recipient, and virtual account endpoints require a provisioned Due customer with approved KYC. Some channels additionally require endorsements; check `endorsementsRequired` on the channel before initiating a transfer.
</Warning>

## Next steps

* [Payins](/integrations/On-Off-Ramp/due-payins)
* [Payouts](/integrations/On-Off-Ramp/due-payouts)
* [Webhooks](/integrations/On-Off-Ramp/due-webhooks)
