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

# KYC information migration

> How-to task page: let a user who already completed KYC under the DeCard-Managed model reuse that data when applying for a card under Partner-Managed (kycApplyMode=H5-MIGRATION / guide page type=9). Organized as a two-step flow with request fields and a minimal request; the three ticket statuses and all error codes are listed inline rather than linked out.

## 📄 Guide

If your user already completed KYC under the **DeCard-Managed model** and you now want to issue them a card under the **Partner-Managed model**, they do not have to submit their documents, selfie and address all over again — you can start a **KYC information migration** and let the user simply confirm the reuse of their existing data on a DCS-hosted H5 page. As a licensed card issuer, DCS verifies whether the source user's KYC is eligible for reuse and, once it passes, attaches usable KYC information to the target user.

Migration reuses the same two-step H5 KYC flow; the only differences are that the application mode is `H5-MIGRATION`, the guide page type is `type=9`, and the source user must be identified.

## When to use it

| Your situation                                                                             | Use                                       |
| ------------------------------------------------------------------------------------------ | ----------------------------------------- |
| Brand-new user, you collect the documents and submit them via API                          | [Apply for KYC (API mode)](./apply-kyc)   |
| Brand-new user, let them fill everything in on the DCS-hosted page                         | [H5 KYC guide page](./h5-kyc-guidance)    |
| The user's documents / KYC data have expired and need re-verification                      | [Updating KYC information](./kyc-renewal) |
| **The user already completed KYC under the DeCard-Managed model and you want to reuse it** | **This page**                             |

## Who does what

| Step                                                                                                     | Who | Notes                                              |
| -------------------------------------------------------------------------------------------------------- | --- | -------------------------------------------------- |
| Determine that the user already has KYC under the DeCard-Managed model and obtain their `externalUserId` | You | The input to a migration                           |
| Apply for the migration ticket, exchange it for an H5 link, send the link to the user                    | You | Two API calls                                      |
| Verify whether the source user's KYC is eligible for reuse                                               | DCS | Not eligible means the ticket is rejected up front |
| Provide the H5 page on which the user confirms the reuse                                                 | DCS | No UI to build on your side                        |
| Advance the ticket status asynchronously and send webhooks                                               | DCS | Event type `KYC_TICKET`                            |

## Interaction flow

A migration can be rejected. Once rejected the ticket is in a terminal state, so you must **apply for a brand-new migration ticket** to let the user retry — a terminal ticket cannot be reused.

<Frame>
  <img className="block dark:hidden" src="https://mintcdn.com/dcs-0bf7a937/oUoeUKtwWWK2o-Li/imgs/en/diagrams/pa-kyc-migration-light.svg?fit=max&auto=format&n=oUoeUKtwWWK2o-Li&q=85&s=ff80bcd94747da037d5dcdcaaa040c45" alt="KYC migration interaction flow" width="638" height="722" data-path="imgs/en/diagrams/pa-kyc-migration-light.svg" />

  <img className="hidden dark:block" src="https://mintcdn.com/dcs-0bf7a937/oUoeUKtwWWK2o-Li/imgs/en/diagrams/pa-kyc-migration-dark.svg?fit=max&auto=format&n=oUoeUKtwWWK2o-Li&q=85&s=15382c020e9f43d94a061c9a90ef1af2" alt="KYC migration interaction flow" width="638" height="722" data-path="imgs/en/diagrams/pa-kyc-migration-dark.svg" />
</Frame>

## Step 1: Apply for the migration ticket (you)

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

| Field              | Type   | Required | Constraints | Description                                                                                                                                                                               |
| ------------------ | ------ | -------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `kycTicketRef`     | string | Yes      | ≤50         | Your own business idempotency key; you can query by it later                                                                                                                              |
| `customerId`       | string | Yes      | ≤50         | The **target user** — the `customerId` in this model you want to issue a card to                                                                                                          |
| `kycApplyMode`     | string | Yes      | ≤20         | Fixed `H5-MIGRATION`                                                                                                                                                                      |
| `sourceCustomerId` | string | Yes      | ≤50         | The **source user identifier** — pass that user's `externalUserId` from the DeCard-Managed model. Note that despite the `...CustomerId` name this is **not** a `customerId` of this model |

```json theme={null}
{
  "kycTicketRef": "kyc-mig-20260728-0001",
  "customerId": "1000000123",
  "kycApplyMode": "H5-MIGRATION",
  "sourceCustomerId": "usr_8f3a..."
}
```

The response returns the new ticket inside the common envelope's `data`, with `status` set to `INIT`:

```json theme={null}
{
  "code": "SYS_SUCCESS",
  "message": "success",
  "messageDetail": null,
  "data": {
    "kycTicketId": "9000000888",
    "kycTicketRef": "kyc-mig-20260728-0001",
    "customerId": "1000000123",
    "status": "INIT",
    "createTime": "2026-07-28T10:00:00+08:00"
  }
}
```

<Warning>
  This step **synchronously checks whether the source user is eligible for reuse**. If not, no ticket is created and `DAPI_KYC_MIGRATION_NOT_ELIGIBLE` is returned (see the error codes below). In other words, once you have a `kycTicketId` the source user has already passed the pre-check and all that remains is the user's confirmation on H5.
</Warning>

## Step 2: Exchange for the migration guide link (you)

**`POST /open-api/card-redirect/v1/guidance-link`**

| Field                   | Type   | Required | Constraints | Description                                                                                                                 |
| ----------------------- | ------ | -------- | ----------- | --------------------------------------------------------------------------------------------------------------------------- |
| `type`                  | string | Yes      | ≤1          | Fixed `9` (KYC information migration)                                                                                       |
| `customerId`            | string | Yes      | ≤50         | The same target user as in step 1                                                                                           |
| `kycTicketId`           | string | Yes      | ≤50         | The `kycTicketId` returned in step 1                                                                                        |
| `errorRedirectUrl`      | string | **Yes**  | ≤200        | Error page to redirect to when the migration fails. **Required** for `type=9`; omitting it returns `DAPI_SYS_ILLEGAL_PARAM` |
| `language`              | string | Yes      | Length 2    | `zh` / `en`; non-zh countries are uniformly mapped to `en`                                                                  |
| `theme`                 | string | Yes      | ≤10         | Such as `default`                                                                                                           |
| `mode`                  | string | Yes      | ≤10         | `light` / `dark`                                                                                                            |
| `userAgent`             | string | Yes      | ≤300        | User browser UA                                                                                                             |
| `successfulRedirectUrl` | string | No       | ≤200        | Redirect address on success                                                                                                 |
| `submitAutoClosed`      | string | No       | ≤1          | `Y` closes the page automatically after a successful submission / `N` does not                                              |
| `primaryColor`          | string | No       | ≤7          | Main colour, such as `#FFFFFF`                                                                                              |

> `type=9` does **not** need `profileId` (that is required only for `type=7`, apply for KYC-H5).

```json theme={null}
{
  "type": "9",
  "customerId": "1000000123",
  "kycTicketId": "9000000888",
  "errorRedirectUrl": "https://your.app/kyc/failed",
  "language": "en",
  "theme": "default",
  "mode": "light",
  "userAgent": "Mozilla/5.0 ..."
}
```

The response `data` is a string — the H5 URL you hand to the user:

```json theme={null}
{
  "code": "SYS_SUCCESS",
  "message": "success",
  "messageDetail": null,
  "data": "https://h5.thedecard-sandbox.com/kyc?token=..."
}
```

> The H5 link has a limited lifetime. If it expires while the ticket is still `INIT`, call this endpoint again for a fresh link — you do **not** need to apply for a new ticket.

## Step 3: Receive the result and retry if needed

Learn the outcome from the [`KYC_TICKET` webhook](../webhooks/events-and-schema) or by calling `GET /open-api/kyc-ticket/v1/detail` yourself (with `kycTicketId` or `kycTicketRef`):

* **`PASSED`**: the migration is complete, the target user now has usable KYC, and you can go on to [issue a card](../cards/card-issuing) (`cardApplyMode=NORMAL`, submitting that `kycTicketId` and `customerId`).
* **`REJECTED`**: this migration was rejected; `errorCode` / `errorMessage` explain why. Go back to step 1 and apply for a new ticket with a **new `kycTicketRef`**.

### Ticket status (migration mode)

In migration mode a ticket only ever takes these three statuses — `NEED_VERIFY` and `PENDING` do **not** occur:

| Status     | Meaning                                                                                | Terminal | Your next step                                                               |
| ---------- | -------------------------------------------------------------------------------------- | :------: | ---------------------------------------------------------------------------- |
| `INIT`     | Ticket created, waiting for the user to confirm on H5; processing also stays at `INIT` |    No    | Send the H5 link to the user; call `guidance-link` again if the link expires |
| `PASSED`   | Migration passed, the target user now has usable KYC                                   |    Yes   | Nothing to do; proceed to card issuance                                      |
| `REJECTED` | Migration rejected                                                                     |    Yes   | Read `errorCode` / `errorMessage` and re-apply with a new `kycTicketRef`     |

## Error codes

### apply-kyc-h5 (`kycApplyMode=H5-MIGRATION`)

In addition to the common error codes listed on the [H5 KYC guide page](./h5-kyc-guidance), migration mode can also return:

| Error code                           | Meaning                                                         | Trigger                                                                                                                                              |                 Retryable                |
| ------------------------------------ | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------: |
| `DAPI_KYC_SOURCE_USER_NOT_FOUND`     | source user not found                                           | `sourceCustomerId` not supplied, or the source user does not exist                                                                                   | ✅ Verify the source identifier and retry |
| `DAPI_KYC_TICKET_CUSTOMER_NOT_FOUND` | kyc ticket customer not found                                   | The target `customerId` does not exist or registration is incomplete                                                                                 |          ✅ Create the user first         |
| `DAPI_KYC_MIGRATION_ALREADY_EXISTS`  | migration KYC ticket already exists for this source or customer | The source user already has an in-progress or passed migration, or the target user already has usable KYC                                            |           ❌ No migration needed          |
| `DAPI_KYC_MIGRATION_NOT_ELIGIBLE`    | not eligible for KYC migration                                  | The source user's KYC is not eligible for reuse (not passed, inconsistent with the target user's details, or migration not enabled for this channel) |        ❌ Use the regular KYC flow        |
| `DAPI_POA_COUNTRY_INVALID`           | proof of address country not supported                          | The source user's proof-of-address country is out of scope                                                                                           |        ❌ Use the regular KYC flow        |
| `DAPI_TICKET_APPLY_LIMIT_EXCEEDED`   | Ticket application exceeds the limit                            | Too many applications for the same user; the intake rate limit was hit                                                                               |           ✅ Back off and retry           |

### guidance-link (`type=9`)

| Error code                         | Meaning                     | Trigger                                                                                              |
| ---------------------------------- | --------------------------- | ---------------------------------------------------------------------------------------------------- |
| `DAPI_SYS_ILLEGAL_PARAM`           | System illegal param        | `kycTicketId` or `errorRedirectUrl` is empty                                                         |
| `DAPI_KYC_TICKET_NOT_FOUND`        | kyc ticket not found        | Ticket does not exist / belongs to someone else / is not a migration ticket                          |
| `DAPI_KYC_TICKET_IS_IN_PROCESSING` | KYC ticket is in processing | The ticket has already been submitted and is still being processed; the operation cannot be repeated |
| `DAPI_KYC_TICKET_ALREADY_PASSED`   | KYC ticket already passed   | The ticket already passed — go straight to card issuance                                             |

## Prerequisites

* You hold the Enterprise ApiKey / SecretKey — see [First steps](../../getting-started/first-steps).
* You have [created the target user](../users/create-customer) in this model and hold their `customerId`.
* The source user has completed KYC under the DeCard-Managed model and you can obtain their `externalUserId`.
* DCS has enabled migration for your channel (otherwise `DAPI_KYC_MIGRATION_NOT_ELIGIBLE` is returned — contact the DCS team).

## Next steps

* Issue a card for the user after a successful migration → [Card issuance](../cards/card-issuing)
* The user's data has expired and needs re-verification → [Updating KYC information](./kyc-renewal)
* Check the current ticket status → [Query KYC](./query-kyc)
