Mobile User

KYC

Validate identity documents, submit cardholder KYC, and read the latest KYC status.

Flow Summary

The current KYC flow is:

Step 1. Upload KYC files
POST /api/v1/media/file

Step 2. Validate document number
POST /api/v1/cardholder-kyc/validate

Step 3. Submit KYC request
POST /api/v1/cardholder-kyc

Step 4. Read latest KYC result
GET /api/v1/cardholder-kyc/latest

Step 5. Confirm cardholder creation
GET /api/v1/cardholder/latest

All KYC routes below should be treated as protected routes and sent with:

Authorization: Bearer <access_token>

The two POST endpoints also pass through DecryptField(), so they support either plain JSON or the encrypted wrapper described in Overview.

1. Upload KYC Files First

The KYC create request expects file references, not raw file bytes.

Upload the document images first with File Upload, then pass the returned object path or internal URL into the KYC payload.

Typical files:

  • front document image
  • back document image for id_number
  • optional selfie or supporting photo

2. Validate Document Number

POST /api/v1/cardholder-kyc/validate

This checks whether the same document number is already in use or already pending verification for another account.

Request

{
  "document_type": "passport",
  "number_id": "P12345678"
}

Success response

{
  "status_code": 200,
  "message": "Document is valid",
  "data": null
}

Rules

  • document_type must be one of passport, visa, or id_number
  • number_id is trimmed before duplicate checks
  • Duplicate checks ignore old records in FAILED, DECLINED, and NOT_VERIFY

Common failures

  • 409: the document is already pending on another account
  • 409: the document is already verified on another account

3. Submit KYC Request

POST /api/v1/cardholder-kyc

This creates a new cardholder KYC request for the authenticated mobile user. The backend fills agent_id and mobile_user_id from the auth context.

Request

{
  "document_type": "passport",
  "number_id": "P12345678",
  "front_kyc_url": "media/private/kyc/front-passport.jpg",
  "photo_urls": "media/private/kyc/selfie.jpg",
  "first_name": "Sok",
  "last_name": "Dara",
  "country_id": 114,
  "date_of_birth": "1995-04-18",
  "issue_date": "2022-01-01",
  "expire_date": "2032-01-01",
  "gender": "MALE",
  "file_type": "jpg"
}

If document_type is id_number, back_kyc_url is also required.

Required field rules

  • document_type: passport, visa, id_number
  • gender: MALE, FEMALE
  • file_type: jpg, png, pdf, jpeg
  • front_kyc_url: always required
  • back_kyc_url: required only for id_number

Important backend rules

  • The mobile user must have both is_phone_verified = true and is_email_verified = true
  • first_name and last_name must contain letters only
  • If date_of_birth is provided, the user must be at least 18 years old
  • The same user cannot submit another KYC while they already have PENDING or APPROVED
  • The same document number cannot be reused if another active KYC already owns it
  • Incoming internal file URLs are sanitized before storage

Success response

{
  "status_code": 201,
  "message": "Success",
  "data": {
    "id": 87,
    "uuid": "0d65f58d-47f6-43d7-84b2-4bdc8d44e391",
    "first_name": "Sok",
    "last_name": "Dara",
    "document_type": "passport",
    "number_id": "P12345678",
    "status": "PENDING",
    "file_type": "jpg",
    "kyc_urls": {
      "front_kyc_url": "https://...",
      "back_kyc_url": ""
    },
    "photo_urls": "https://..."
  }
}

Notes

  • The response returns transformed file URLs, not just the raw stored object keys
  • A manager notification is created in the background after submission
  • If the server setting IsAutoApproveKYC is enabled, the KYC may be auto-approved immediately instead of remaining PENDING

4. Read Latest KYC

GET /api/v1/cardholder-kyc/latest

This returns the latest KYC record for the current user, with transformed URLs and translated reject reasons.

Success response shape

{
  "status_code": 200,
  "message": "Success",
  "data": {
    "uuid": "0d65f58d-47f6-43d7-84b2-4bdc8d44e391",
    "status": "PENDING",
    "card_status": "PENDING",
    "is_kyc_expire": false,
    "reject_reason": "",
    "kyc_urls": {
      "front_kyc_url": "https://...",
      "back_kyc_url": ""
    },
    "mobile_user": {
      "phone": "012345678",
      "email": "user@example.com"
    }
  }
}

Notes

  • Send Accept-Language: zh if you want translated reject_reason text when available
  • The handler comment says this returns pending/approved KYC, but the current repository implementation returns the most recent KYC row for the user
  • If the latest KYC is DECLINED and no card status exists yet, the API maps card_status to a failed state in the response

5. Confirm Cardholder Creation

GET /api/v1/cardholder/latest

Once KYC is approved, call this endpoint to confirm the backend has already created a successful cardholder record.

Use the dedicated Cardholder page for the full response shape and behavior details.

Integration Notes

  1. Upload media first, then validate the document number before creating KYC.
  2. Treat validate as a pre-check only; the create endpoint still performs the real conflict checks.
  3. Poll or reload latest after submission to show status changes in the app.
  4. After approval, use Cardholder to confirm the successful cardholder record exists before entering card flows.
Copyright © 2026