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

# Apply for KYC

> Submit a user's KYC information to DCS in a single API call and receive a kycTicketId, the ticket you will quote when issuing the card; it accepts a Sumsub Share Token or direct document upload.

* **Option 1: Sumsub Share Token** — the user has already been verified in your own Sumsub account, so you hand the Share Token to DCS and upload no document files;
* **Option 2: document file upload** — you collect the documents yourself and upload them to DCS.

Both options create the same kind of KYC ticket, with an identical status machine, query API and webhook.

## 📄 Guide

Whether you are an exchange, a wallet or a platform, you can submit a user's KYC information to DCS in a single API call. This is a required step before you can issue a card to that user. As a card issuer licensed by the Monetary Authority of Singapore (MAS), DCS receives the data securely, assesses risk against anti-money laundering (AML) rules, and returns the verification result through a webhook.

## Who does what

| Step                                                | Who     | Notes                                                                                                                                                        |
| --------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Collect the user's identity and career information  | Partner | Sumsub route: complete verification in your own Sumsub account and take the Share Token. Upload route: prepare the document files and the career information |
| Upload the files and submit the KYC application     | Partner | On the upload route, first request an S3 pre-upload URL, PUT the file to it, then call `apply-kyc`                                                           |
| Document verification, AML screening, manual review | DCS     | Handled asynchronously after receipt                                                                                                                         |
| Report the status and the result                    | DCS     | Through the query API or the `KYC_TICKET` webhook                                                                                                            |

## Prerequisites

* You hold the Enterprise ApiKey / SecretKey — see the [Authentication guide](../../integration-resources/authentication).
* You have created the user and hold their `customerId` — see [Create a user](../users/create-customer).
* **On the Sumsub route**: you have set up a sharing partner relationship between your own Sumsub account and DCS (see the [KYC vendor notes](../../customer-success/faq-kyc-vendor)) and hold the user's Share Token.
* **On the upload route**: you have the user's identity document to hand (an ID card, passport or driving license, for example), plus a proof of address if one is required.

## Choosing between the two options

| Situation                                                 | Recommended option             | Key fields                               |
| --------------------------------------------------------- | ------------------------------ | ---------------------------------------- |
| You already run your own Sumsub integration               | Option 1: Sumsub Share Token   | `sumsubShareToken`                       |
| No Sumsub integration; you collect the documents yourself | Option 2: document file upload | `identifyProofList` / `addressProofList` |

**How DCS tells them apart**: when the document-file fields (`identifyProofList` / `addressProofList`) are left empty, DCS skips validating them; when they are supplied, they are checked against the enumerations and the length limits. `sumsubShareToken` is passed downstream with the request and used to pull the data the user has already had verified in Sumsub. Both may appear in the same request, in which case the downstream risk service decides which one prevails. Either way you must submit **career information** (`kycCareerInfo`) — that part is required on both routes.

***

## Option 1: Sumsub Share Token

### Minimum request

`POST /open-api/kyc-ticket/v1/apply-kyc`

```json theme={null}
{
  "kycTicketRef": "kyc-20260617-0001",
  "customerId": "CUST_8f3a...",
  "sumsubShareToken": "_act-xxxxxxxxxxxxxxxxxxxxxx",
  "kycCareerInfo": {
    "employmentStatus": "EMPLOYED",
    "employerName": "Acme Pte Ltd",
    "employmentJobIndustry": "INFORMATION",
    "occupation": "IT_CONSULTANT",
    "jobSeniority": "EXECUTIVE",
    "purposeOfAccount": "DAILY_SPENDING",
    "sourceOfFunds": "EMPLOYMENT",
    "sourceOfFundsCountry": "SG",
    "sourceOfWealth": "EMPLOYMENT"
  }
}
```

> On the Sumsub route you can leave `identifyProofList` / `addressProofList` empty. `sumsubShareToken` is passed downstream, where it is used to fetch the documents the user has already had verified in Sumsub.

***

## Option 2: document file upload

### Step 1: upload the document files

Identity and address documents are not embedded in the application request itself. Instead you request an S3 pre-upload link, PUT the file to it, and quote the returned `objectKey` in the application.

Call `POST /open-api/intent-ticket/v1/generate-pre-upload-url` with `businessType` set to `CREATE_CARD_KYC`:

```json theme={null}
{
  "fileNames": ["id_front.jpg", "id_back.jpg"],
  "businessType": "CREATE_CARD_KYC"
}
```

The response carries a temporary upload URL and an `objectKey` for each file. After you PUT the file to the URL, keep the `objectKey`: it is what goes into the `identityProofUrl` / `addressProofUrl` field in the next step, not the full URL.

### Step 2: submit the KYC application

`POST /open-api/kyc-ticket/v1/apply-kyc`

```json theme={null}
{
  "kycTicketRef": "kyc-20260617-0001",
  "customerId": "CUST_8f3a...",
  "identifyProofList": [
    {
      "identityProofUrl": "<the objectKey returned in step 1>",
      "identityProofType": "PASSPORT",
      "identityProofSubType": "EMPTY",
      "identityProofIssuedCountry": "SG"
    }
  ],
  "kycCareerInfo": {
    "employmentStatus": "EMPLOYED",
    "employerName": "Acme Pte Ltd",
    "employmentJobIndustry": "INFORMATION",
    "occupation": "IT_CONSULTANT",
    "jobSeniority": "EXECUTIVE",
    "purposeOfAccount": "DAILY_SPENDING",
    "sourceOfFunds": "EMPLOYMENT",
    "sourceOfFundsCountry": "SG",
    "sourceOfWealth": "EMPLOYMENT"
  }
}
```

***

## Request fields at a glance

### Common fields (both options)

| Field                     | Type   |   Required  | Description                                                                                                                     |
| ------------------------- | ------ | :---------: | ------------------------------------------------------------------------------------------------------------------------------- |
| `kycTicketRef`            | string |     Yes     | KYC idempotency reference; max 50. Resubmitting the same reference does not create a second ticket                              |
| `customerId`              | string |     Yes     | User ID; max 50                                                                                                                 |
| `kycCareerInfo`           | object |  See notes  | Career information (below). **At least one of** this field and `kycCareerInfoEncryption` must be present                        |
| `kycCareerInfoEncryption` | string |  See notes  | The `kycCareerInfo` JSON as AES-GCM ciphertext. At least one of the two must be present; when both are, the **ciphertext wins** |
| `encryptionIV`            | string | Conditional | The AES-GCM IV used to decrypt `kycCareerInfoEncryption`; required whenever you send the ciphertext                             |

### Field specific to option 1

| Field              | Type   |        Required       | Description                                                                                                                            |
| ------------------ | ------ | :-------------------: | -------------------------------------------------------------------------------------------------------------------------------------- |
| `sumsubShareToken` | string | Required for option 1 | Sumsub Share Token; max 500. Passed downstream to fetch the already-verified data, which lets you leave the document-file fields empty |

### Fields used by option 2

| Field               | Type      |         Required         | Description                                        |
| ------------------- | --------- | :----------------------: | -------------------------------------------------- |
| `identifyProofList` | object\[] | Recommended for option 2 | List of identity documents (below)                 |
| `addressProofList`  | object\[] |   Optional for option 2  | List of address documents (below); up to 5 entries |

<Warning>
  Spelling note: the outer list field is spelled `identifyProofList` (identify), while its nested fields start with `identityProof*` (identity). The prefixes differ, so pass each field exactly as spelled in this table.
</Warning>

<Warning>
  `identifyProofList` and its nested `identityProofType` / `identityProofIssuedCountry` are all **optional at the endpoint level**, which is what allows the Sumsub route to send nothing but `sumsubShareToken`. On the upload route, however, all three are **required in practice**: verification cannot complete without them.
</Warning>

**`identifyProofList[]` identity document**

| Field                        | Type   | Required | Description                                                                                                                     |
| ---------------------------- | ------ | :------: | ------------------------------------------------------------------------------------------------------------------------------- |
| `identityProofUrl`           | string |    No    | The file's `objectKey` from step 1; max 512. Required when you upload the document yourself                                     |
| `identityProofType`          | string |    No    | One of `ID_CARD` / `DRIVERS` / `PASSPORT`; max 32. Required when you upload the document yourself                               |
| `identityProofSubType`       | string |    No    | One of `FRONT_SIDE` / `BACK_SIDE` / `EMPTY`; use `EMPTY` for single-sided documents or documents with no front and back; max 32 |
| `identityProofIssuedCountry` | string |    No    | Issuing country as a 2-letter ISO country code. Required when you upload the document yourself                                  |

**`addressProofList[]` address document**

| Field                       | Type   |             Required            | Description                                                                                                 |
| --------------------------- | ------ | :-----------------------------: | ----------------------------------------------------------------------------------------------------------- |
| `addressProofUrl`           | string |                No               | The file's `objectKey`; max 512. Required when you upload the address document yourself                     |
| `addressProofType`          | string | Yes, when the list is not empty | Value: `UTILITY_BILL`; max 32. Leaving it empty while the list has entries returns `DAPI_SYS_ILLEGAL_PARAM` |
| `addressProofIssuedCountry` | string |                No               | Issuing country as a 2-letter ISO country code                                                              |

### `kycCareerInfo` career information (required on both options)

| Field                   | Type   |   Required  | Description                                                      |
| ----------------------- | ------ | :---------: | ---------------------------------------------------------------- |
| `employmentStatus`      | string |     Yes     | Employment status; max 32                                        |
| `employerName`          | string | Conditional | Employer name; required when `employmentStatus=EMPLOYED`; max 50 |
| `employmentJobIndustry` | string |     Yes     | Industry of employment; max 32                                   |
| `occupation`            | string |     Yes     | Occupation; max 32                                               |
| `jobSeniority`          | string |     Yes     | Job seniority; max 32                                            |
| `purposeOfAccount`      | string |     Yes     | Purpose of the account; max 32                                   |
| `sourceOfFunds`         | string |     Yes     | Source of funds; max 32                                          |
| `sourceOfFundsCountry`  | string |     Yes     | Source-of-funds country as a 2-letter ISO country code           |
| `sourceOfWealth`        | string |     Yes     | Source of wealth; max 32                                         |

> Career information is sensitive data. If you want it encrypted end to end, serialise `kycCareerInfo` to JSON, encrypt it with AES-GCM, put the ciphertext in `kycCareerInfoEncryption` and the IV in `encryptionIV`; the plaintext `kycCareerInfo` can then be omitted.
> The fields above are validated against server-side enumerations rather than accepting arbitrary strings. For the full set of values, see the [KYC application parameter dictionary](./kyc-application-fields).

## Response

Every `/open-api/` endpoint shares the same response envelope, `{ code, message, messageDetail, data }`. On success, `data` carries the newly created KYC ticket:

```json theme={null}
{
  "code": "SYS_SUCCESS",
  "message": "success",
  "messageDetail": null,
  "data": {
    "kycTicketId": "KYC_1a2b...",
    "kycTicketRef": "kyc-20260617-0001",
    "customerId": "CUST_8f3a...",
    "status": "INIT",
    "createTime": "2026-06-17T08:00:00+08:00"
  }
}
```

> About the envelope: judge success or failure by `code` (`SYS_SUCCESS`, for example); `message` is a short caption. `messageDetail` is an optional display object (with `message` / `title` / `type` / `icon` / `action` / `linkTitle` / `linkUrl`) meant for front-end prompts, and should never be used to decide whether a call succeeded.

<Warning>
  Note that HTTP 200 with `code=SYS_SUCCESS` only means **the application has been accepted**, not that KYC has passed. After a successful submission `status` is normally `INIT`, and you get the final outcome from the query API or the webhook.
</Warning>

| Response field | Description                                                                          |
| -------------- | ------------------------------------------------------------------------------------ |
| `kycTicketId`  | KYC application ID; quote this in later queries and references                       |
| `kycTicketRef` | The idempotency reference, echoed back                                               |
| `customerId`   | User ID                                                                              |
| `status`       | KYC ticket status: `INIT` / `NEED_VERIFY` / `PENDING` / `PASSED` / `REJECTED`        |
| `createTime`   | Creation time, formatted `yyyy-MM-dd'T'HH:mm:ss+08:00`, as elsewhere on the platform |

## Following up on the result

Once submitted, the KYC ticket moves through the statuses below. Both options share the same status machine.

| Status        | Meaning                   | Terminal | What you do next                                                                            |
| ------------- | ------------------------- | :------: | ------------------------------------------------------------------------------------------- |
| `INIT`        | Information submitted     |    No    | Wait for the system to process it                                                           |
| `NEED_VERIFY` | Waiting for the face scan |    No    | [Get the face verification link](./h5-kyc-guidance) and walk the user through the face scan |
| `PENDING`     | Under review              |    No    | Wait for the review to finish                                                               |
| `PASSED`      | Approved                  |    Yes   | Carry on with the next step, such as applying for a virtual card                            |
| `REJECTED`    | Rejected                  |    Yes   | Resubmit under a new `kycTicketRef`                                                         |

When `status=REJECTED`, both the [Query KYC](./query-kyc) endpoint and the `KYC_TICKET` webhook return an `errorCode` and an `errorMessage` giving the reason. For how to handle each one, see [KYC reject codes](./kyc-reject-codes).

Prefer webhook subscriptions over polling for status updates; see [Webhook events and schema](../webhooks/events-and-schema).

## Next steps

Once KYC is `PASSED`, go to the [card issuing flow](../cards/card-issuing) to issue the user a card. If the status is stuck at `NEED_VERIFY`, start with the [face verification page](./h5-kyc-guidance) and walk the user through the face scan.
