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

# Create cardholder

> Create a new cardholder with KYC data for this partner, or re-import KYC onto an existing one.

Share-token sources (`sumsub`, `didit`) capture a point-in-time snapshot of the donor session. If you later correct your KYC workflow — for example by adding address verification — re-submit this endpoint with the same `externalUserId` and a **freshly minted** share token to re-import the corrected session onto the existing cardholder. Extracted identity and address fields overwrite the stored values; unrelated data is preserved. The `kycSource` must match the one the cardholder was created with, and the cardholder must still be `active`.

Responds **201** when a new cardholder was created and **200** when an existing one was re-imported. Re-import is refused with **409** `CARDHOLDER_KYC_LOCKED` once the identity has left our system: the KYC was already submitted to the card provider, or a card has been issued. Share tokens are single-use, so a refused re-import does not consume the token — but every accepted one does.



## OpenAPI

````yaml /partner-openapi.json post /partner/cardholders
openapi: 3.1.0
info:
  title: Contro Partner API
  version: 1.0.0
  license:
    name: Proprietary
    url: https://contro.me/terms
  description: >-
    The Contro Partner API enables Card-as-a-Service (CaaS) partners to
    programmatically issue cards, manage cardholders, and monitor transactions.


    ## Authentication


    All requests require your partner API key in the `x-contro-api-key` header:


    ```

    x-contro-api-key: sk_live_...

    ```


    Use `sk_test_*` keys for sandbox and `sk_live_*` keys for production.


    ## Rate Limits


    The API allows 1,000 requests per minute per API key. When exceeded,
    responses return HTTP 429 with a `Retry-After` header.


    ## Pagination


    List endpoints use page-based pagination:


    ```json

    {
      "data": [...],
      "page": 1,
      "limit": 20,
      "total": 137
    }

    ```


    Pass `?page=2&limit=20` to paginate. `limit` accepts 1–100 (default 20);
    `page` defaults to 1.


    ## Errors


    All errors return:


    ```json

    {
      "success": false,
      "error": "Human-readable message"
    }

    ```
servers:
  - url: https://api.contro.me/v1
    description: Production server for live traffic
  - url: https://stg-api.contro.dev/v1
    description: Sandbox environment for testing
security:
  - apiKey: []
paths:
  /partner/cardholders:
    post:
      tags:
        - Partner - Cardholders
      summary: Create cardholder
      description: >-
        Create a new cardholder with KYC data for this partner, or re-import KYC
        onto an existing one.


        Share-token sources (`sumsub`, `didit`) capture a point-in-time snapshot
        of the donor session. If you later correct your KYC workflow — for
        example by adding address verification — re-submit this endpoint with
        the same `externalUserId` and a **freshly minted** share token to
        re-import the corrected session onto the existing cardholder. Extracted
        identity and address fields overwrite the stored values; unrelated data
        is preserved. The `kycSource` must match the one the cardholder was
        created with, and the cardholder must still be `active`.


        Responds **201** when a new cardholder was created and **200** when an
        existing one was re-imported. Re-import is refused with **409**
        `CARDHOLDER_KYC_LOCKED` once the identity has left our system: the KYC
        was already submitted to the card provider, or a card has been issued.
        Share tokens are single-use, so a refused re-import does not consume the
        token — but every accepted one does.
      operationId: createCardholder
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateCardholderBody'
      responses:
        '200':
          description: >-
            Existing cardholder re-imported from a fresh share token. Returned
            instead of 201 when a cardholder already existed for this
            externalUserId.
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    description: Contro cardholder ID
                    example: ch_abc123
                  externalUserId:
                    type: string
                    description: Your unique identifier for this user
                    example: user_42
                  firstName:
                    type: string
                    description: Cardholder's first name
                    example: Jane
                  lastName:
                    type: string
                    description: Cardholder's last name
                    example: Doe
                  email:
                    type: string
                    description: Email address
                    example: jane@example.com
                  phoneNumber:
                    type:
                      - string
                      - 'null'
                    description: E.164 formatted phone number, or null if not provided
                    example: '+14155552671'
                  kycSource:
                    type: string
                    description: 'KYC submission method. One of: api, sumsub, web'
                    example: api
                  kycStatus:
                    type: string
                    description: >-
                      KYC verification status. One of: pending, approved,
                      rejected
                    example: approved
                  status:
                    type: string
                    description: >-
                      Cardholder account status. One of: active, suspended,
                      closed
                    example: active
                  createdAt:
                    type: string
                    description: ISO 8601 creation timestamp
                    example: '2026-03-20T14:30:00Z'
                required:
                  - id
                  - externalUserId
                  - firstName
                  - lastName
                  - email
                  - phoneNumber
                  - kycSource
                  - kycStatus
                  - status
                  - createdAt
        '201':
          description: Cardholder created
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    description: Contro cardholder ID
                    example: ch_abc123
                  externalUserId:
                    type: string
                    description: Your unique identifier for this user
                    example: user_42
                  firstName:
                    type: string
                    description: Cardholder's first name
                    example: Jane
                  lastName:
                    type: string
                    description: Cardholder's last name
                    example: Doe
                  email:
                    type: string
                    description: Email address
                    example: jane@example.com
                  phoneNumber:
                    type:
                      - string
                      - 'null'
                    description: E.164 formatted phone number, or null if not provided
                    example: '+14155552671'
                  kycSource:
                    type: string
                    description: 'KYC submission method. One of: api, sumsub, web'
                    example: api
                  kycStatus:
                    type: string
                    description: >-
                      KYC verification status. One of: pending, approved,
                      rejected
                    example: approved
                  status:
                    type: string
                    description: >-
                      Cardholder account status. One of: active, suspended,
                      closed
                    example: active
                  createdAt:
                    type: string
                    description: ISO 8601 creation timestamp
                    example: '2026-03-20T14:30:00Z'
                required:
                  - id
                  - externalUserId
                  - firstName
                  - lastName
                  - email
                  - phoneNumber
                  - kycSource
                  - kycStatus
                  - status
                  - createdAt
        '400':
          description: Bad Request
        '401':
          description: Unauthorized
        '404':
          description: Not Found
        '409':
          description: >-
            Either a cardholder with this email already exists under this
            partner (`EMAIL_ALREADY_REGISTERED`; the existing cardholder ID is
            returned so the caller can self-heal), or the cardholder's KYC can
            no longer be re-imported (`CARDHOLDER_KYC_LOCKED`).
          content:
            application/json:
              schema:
                anyOf:
                  - $ref: '#/components/schemas/EmailAlreadyRegisteredError'
                  - $ref: '#/components/schemas/CardholderKycLockedError'
        '429':
          description: Rate Limited
        '500':
          description: Internal Server Error
components:
  schemas:
    CreateCardholderBody:
      anyOf:
        - type: object
          properties:
            externalUserId:
              type: string
              minLength: 1
              description: Your unique identifier for this user. Min 1 character
              example: user_42
            email:
              type: string
              format: email
              description: Valid email address
              example: jane@example.com
            phoneNumber:
              type: string
              description: E.164 formatted phone number
              example: '+14155552671'
            kycSource:
              type: string
              enum:
                - web
              description: KYC via Contro-hosted web link
            kycSessionId:
              type: string
              minLength: 1
              description: KYC session ID from POST /partner/kyc-sessions
          required:
            - externalUserId
            - email
            - phoneNumber
            - kycSource
            - kycSessionId
        - type: object
          properties:
            externalUserId:
              type: string
              minLength: 1
              description: Your unique identifier for this user. Min 1 character
              example: user_42
            email:
              type: string
              format: email
              description: Valid email address
              example: jane@example.com
            phoneNumber:
              type: string
              description: E.164 formatted phone number
              example: '+14155552671'
            kycSource:
              type: string
              enum:
                - sumsub
              description: KYC via Sumsub share token
            sumsubShareToken:
              type: string
              minLength: 1
              description: Sumsub share token from the donor partner
            residenceCountryCode:
              type: string
              minLength: 2
              maxLength: 2
              description: Residence country (ISO 3166-1 alpha-2)
              example: US
            residenceStateProvince:
              type: string
              description: Residence state/province
            residenceCity:
              type: string
              description: Residence city
            residenceAddressDetail:
              type: string
              description: Residence address
            postalCode:
              type: string
              description: Postal code
          required:
            - externalUserId
            - email
            - phoneNumber
            - kycSource
            - sumsubShareToken
    EmailAlreadyRegisteredError:
      type: object
      properties:
        success:
          type: boolean
          enum:
            - false
        error:
          type: object
          properties:
            code:
              type: string
              enum:
                - EMAIL_ALREADY_REGISTERED
              description: Stable error code for client-side handling
            message:
              type: string
              description: Human-readable error message
            existingCardholderId:
              type: string
              description: >-
                ID of the existing cardholder under this partner that already
                uses this email
              example: ch_abc123
          required:
            - code
            - message
            - existingCardholderId
      required:
        - success
        - error
    CardholderKycLockedError:
      type: object
      properties:
        success:
          type: boolean
          enum:
            - false
        error:
          type: object
          properties:
            code:
              type: string
              enum:
                - CARDHOLDER_KYC_LOCKED
              description: Stable error code for client-side handling
            message:
              type: string
              description: Human-readable error message
            cardholderId:
              type: string
              description: ID of the cardholder whose KYC can no longer be re-imported
              example: ch_abc123
            reason:
              type: string
              enum:
                - provider_submitted
                - card_issued
              description: >-
                Why the re-import was refused: the KYC was already submitted to
                the card provider, or a card has been issued
              example: card_issued
          required:
            - code
            - message
            - cardholderId
            - reason
      required:
        - success
        - error
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: x-contro-api-key
      description: Partner API key (sk_live_* for production, sk_test_* for sandbox)

````