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

> A queryable, notifiable ticket for every KYC application: API-upload and H5 modes, five states, four failure paths, and how to issue a virtual card in NORMAL mode from a PASSED ticket.

## 📄 Overview

A KYC ticket narrows "has this user passed KYC" down to **a single application**: how far it got, why it did not pass, and when a card can be issued. Tickets and card applications are two layers — first a PASSED ticket, then a card application created from it. Every state change is pushed through the `KYC_TICKET` webhook, and you can query `detail` at any time.

![KYC ticket state machine](https://mintlify.s3.us-west-1.amazonaws.com/dcs-0bf7a937/imgs/en/diagrams/va-kyc-ticket-states-light.svg)

The diagram shows the longest path. **`PENDING` is not a mandatory state**: an API-mode ticket may move straight from `INIT` or `NEED_VERIFY` to `PASSED` / `REJECTED` when the risk engine reports back. Handle whatever `status` you receive; do not assume states advance one step at a time.

## Two application modes

| Mode | When to use | What you do |
| :- | :- | :- |
| **H5** | The user uploads documents, does the face check and fills in details on the DCS-hosted H5 page | `POST /kyc-ticket/v1/apply-kyc-h5` → `POST /redirect/v2/guidance-link` (`action=KYC_APPLY`, with `kycTicketId` and `categoryId`) → hand the link to the user |
| **API** | You already collected the documents and career info and upload them in one call | `POST /kyc-ticket/v1/apply-kyc` → once the ticket is `NEED_VERIFY`, `POST /redirect/v2/guidance-link` (`action=KYC_LIVENESS`, `kycTicketId` required) → hand the face-verification link to the user |

> [!NOTE]
>
> Existing integrations are unaffected: calling `guidance-link` (`action=KYC_GUIDE`) without a ticket still works and DCS reuses or creates an H5 ticket for the user **when the link is issued**. The ticket ID is not returned by that endpoint, but a newly created ticket triggers a `KYC_TICKET` webhook carrying its `kycTicketId`, which you can then use with `detail`. `KYC_GUIDE` does not read `kycTicketId` — a value passed there is ignored; to issue a link for a ticket you created yourself, use `KYC_APPLY`.

## Ticket states

| State | Meaning | Final | Next step |
| :- | :- | :-: | :- |
| `INIT` | Ticket created; H5 user has not submitted yet, API documents received and about to be sent to risk | No | H5: hand the guidance link to the user; API: wait |
| `NEED_VERIFY` | Face verification pending | No | API mode only: get the face link with `action=KYC_LIVENESS` |
| `PENDING` | Submitted, under review | No | Wait for the webhook or poll `detail` |
| `PASSED` | Approved | Yes | Apply for a virtual card with `cardApplyMode=NORMAL` and the `kycTicketId` |
| `REJECTED` | Declined | Yes | Check `errorCode` / `errorMessage`; to retry, create a new ticket with a new `kycTicketRef` |

Partner-initiated creation is rejected whenever the user already has any in-flight (non-final) ticket, **including the H5 ticket DCS creates automatically when an `action=KYC_GUIDE` link is issued**; that automatic creation only looks for an in-flight H5 ticket and ignores API ones, so a user may hold both a partner-created API ticket and a DCS-created H5 ticket at the same time. The automatic ticket occupies the single in-flight slot and is never expired or reclaimed, so if the user opened a guidance link but never submitted, `apply-kyc` for that user keeps returning `KYC_TICKET_IS_IN_PROCESSING` — do not issue a `KYC_GUIDE` link first if you plan to use API mode. Re-creating with the same `kycTicketRef`: `apply-kyc` always returns `KYC_TICKET_REF_NOT_UNIQUE` (look the ticket up by `kycTicketRef` if a request timed out); `apply-kyc-h5` returns the original ticket while it is still `INIT` (network-retry case), and from `PENDING` onwards it is rejected by status (`KYC_TICKET_IS_IN_PROCESSING` / `KYC_TICKET_ALREADY_PASSED` / `KYC_TICKET_VERIFY_FAIL`); a new `kycTicketRef` while an in-flight ticket exists returns `KYC_TICKET_IS_IN_PROCESSING`.

## Four failure paths

| Where it fails | What the ticket shows | How you learn about it | Can the user retry |
| :- | :- | :- | :-: |
| Declined by risk review | `status=REJECTED`, `errorCode` is the rejection reason | Webhook / `detail` | New ticket with a new `kycTicketRef` |
| Guidance-link gate (ticket already `PENDING` / final) | Ticket unchanged | `guidance-link` returns the error code | Act on the error code |
| Card-issuing gate (ticket not `PASSED`) | Ticket unchanged, no card application created | `virtual-card/apply` returns `KYC_TICKET_NOT_PASSED` | Retry after `PASSED` |

## API mode: encrypted career info

`encryptedKycCareerInfo` = Base64( AES-GCM( key = Base64Decode(SecretKey), iv = Base64Decode(`iv`), plaintext = the `careerInfo` JSON ) ) with a 128-bit tag. DCS looks up your SecretKey from the `X-DAPI-API-KEY` header to decrypt and never logs the plaintext. Send at least one of `careerInfo` / `encryptedKycCareerInfo`; the ciphertext wins if both are present.

## Issuing a card from a ticket

Once the ticket is `PASSED`, call `POST /card/v1/virtual-card/apply` with `cardApplyMode=NORMAL` and `kycTicketId`; `kycInfo` is no longer needed inline. Omitting `cardApplyMode` means `COMPLETE`, identical to the previous behavior. Tickets are not bound to a card product: the KYC outcome is per user, so one `PASSED` ticket can back several cards of any product for that user. The apply and detail responses now include `kycTicketId` / `cardApplyMode`. See [Issuing cards](https://docs.thedecard.com/en/decard-managed/how-to-use/managing-cards/issuing-cards.md).

## Link validity and face-page errors

* The secret in the link is **single-use and valid for 300 seconds**; opening the page starts a **1-hour** session. Request a new link after expiry; the ticket is unaffected.
* The face page may return `PERMISSION_DENIED` (link used or expired), `SUMSUB_TOKEN_NOT_FOUND` (session is not a face session or the ticket does not belong to the user) or `KYC_TICKET_STATUS_NOT_LIVENESS` (ticket already submitted or final). **The first two are returned only by the face page to the browser, never in a partner API response** — they are listed here to help you triage user reports. `KYC_TICKET_STATUS_NOT_LIVENESS` is also returned to you directly when you call `guidance-link` with `action=KYC_LIVENESS` and the ticket is already `PENDING` or final.

## Error codes

| Code | Meaning |
| :- | :- |
| `KYC_TICKET_NOT_FOUND` | Ticket missing or not owned by this partner and user |
| `KYC_TICKET_MODE_MISMATCH` | The ticket mode does not match the action: `KYC_APPLY` only serves H5 tickets, `KYC_LIVENESS` only serves API tickets |
| `KYC_TICKET_REF_NOT_UNIQUE` | `kycTicketRef` already used by another user of this partner |
| `KYC_TICKET_IS_IN_PROCESSING` | An in-flight ticket already exists |
| `KYC_TICKET_ALREADY_PASSED` | Ticket already passed; apply for the card directly |
| `KYC_TICKET_VERIFY_FAIL` | Ticket rejected; use a new `kycTicketRef` |
| `KYC_TICKET_NOT_PASSED` | Ticket not passed, card application refused |
| `KYC_TICKET_STATUS_NOT_LIVENESS` | The ticket is already `PENDING` or final, so no face link can be issued |
| `KYC_TICKET_NOT_READY_FOR_LIVENESS` | The ticket is still `INIT` (the API upload is still being submitted for review), so no face link yet; retry once it turns `NEED_VERIFY` |
| `SUMSUB_TOKEN_NOT_FOUND` | Face-page session has no ticket or the ticket is not the session user's |

> Codes are returned with the module prefix of the endpoint (e.g. `CARD_KYC_TICKET_NOT_FOUND`); match on the actual string. For `REJECTED`, **API-mode** tickets carry the risk-engine codes, the same as for card applications, see the [application data dictionary](https://docs.thedecard.com/en/decard-managed/how-to-use/managing-cards/application-data-dictionary.md); **H5-mode** tickets use `KYC_REJECTED`, with the specific reason in `errorMessage`, or `TIMEOUT` when the user did not finish within the Sumsub time limit — you can prompt them to start again.

## Related endpoints

* `POST /kyc-ticket/v1/apply-kyc-h5` / `POST /kyc-ticket/v1/apply-kyc` / `GET /kyc-ticket/v1/detail`: see the [API reference](https://docs.thedecard.com/en/decard-managed/api-reference/index.md)
* `POST /redirect/v2/guidance-link`: new `kycTicketId`, `categoryId` and `action=KYC_APPLY` / `KYC_LIVENESS`, see [H5 guidance pages](https://docs.thedecard.com/en/decard-managed/integration-resources/h5-kyc-guidance.md)
* `KYC_TICKET` webhook: see [Webhook & WebSocket](https://docs.thedecard.com/en/decard-managed/integration-resources/webhook-websocket.md)
