openapi: 3.0.3
info:
  title: Wink Core API
  version: 2026.04
  summary: REST APIs for biometric login, wallet, and payments.
  description: |
    The **Wink Core API** is the foundation of the Wink stack — every
    higher layer (WinkKey, Wink MCP) composes on these endpoints.

    ## What's here

    Three groups of endpoints:

    - **Login** — sessions, biometric enrolment (face / voice / palm /
      WinkPin), profile management, and 2FA. Lives on the **Login** host.
    - **Wallet** — saved users, cards, billing & shipping addresses.
      Lives on the **Payment** host.
    - **Payment** — payment session creation and authorization. Lives
      on the **Payment** host.

    ## Authentication

    Wink supports two auth modes you'll see throughout this reference:

    1. **Merchant credentials** — HTTP Basic with your `clientId` and
       `clientKey`. Used for server-to-server calls that bootstrap a
       session or operate on cardholder data.
    2. **Bearer session** — once a session is created, pass its
       `sessionId` as a Bearer token on user-scoped calls.

    A signature-based variant (private certificate) is also supported
    for enterprise integrations — request setup via *Get keys*.

    ## Base URLs

    All Wink developer access runs against **stage** — that's the
    sandbox tier external developers receive keys for. Login endpoints
    live on `stagelogin-api.winkapis.com`. Wallet and Payment endpoints
    live on `stagepayments-api.winkapis.com`. Both hosts are
    parameterised at the top of this page; production hosts are
    provisioned per-merchant once you go live.

    ## Conventions

    - All bodies are `application/json` unless an endpoint accepts a
      binary payload (face image, voice clip), in which case it expects
      `multipart/form-data`.
    - Times are ISO-8601 UTC. IDs are v4 UUIDs.
    - Currency amounts are integers in the smallest unit of the
      currency (cents for USD).
  contact:
    name: Wink Developer Support
    url: https://wink.cloud
  license:
    name: Proprietary
servers:
  - url: https://stagelogin-api.winkapis.com
    description: Login — stage (sandbox for external developers)
  - url: https://stagepayments-api.winkapis.com
    description: Payment & Wallet — stage (sandbox for external developers)
tags:
  - name: Login
    description: |
      Sessions, biometric enrolment (face / voice / palm / WinkPin),
      recognition, and profile management. All Login endpoints live on
      the **Login** host.
  - name: Wallet
    description: |
      Saved users, cards, and addresses (billing and shipping). Wallet
      endpoints live on the **Payment** host and require a session
      bearer token.
  - name: Payment
    description: |
      Create a payment session and authorize charges against a saved
      Wink card token. Payment endpoints live on the **Payment** host.
components:
  securitySchemes:
    basicAuth:
      type: http
      scheme: basic
      description: |
        HTTP Basic auth using your **merchant clientId** as the username
        and **merchant clientKey** as the password. Used for
        server-to-server calls.
    bearerAuth:
      type: http
      scheme: bearer
      description: |
        Bearer token — typically the `sessionId` returned from
        `POST /wink/v1/session`.
    clientIdHeader:
      type: apiKey
      in: header
      name: client-id
      description: Merchant client ID, used together with `signature` for cert-based auth.
    signatureHeader:
      type: apiKey
      in: header
      name: signature
      description: |
        Request signature in the form `s=<sig>,keyid=<keyId>`. Used for
        cert-based (private-key) auth as an alternative to Basic.

  schemas:
    Session:
      type: object
      properties:
        sessionId:
          type: string
          format: uuid
          description: Bearer token for subsequent user-scoped calls.
        expiresAt:
          type: string
          format: date-time
        returnUrl:
          type: string
        cancelUrl:
          type: string
      example:
        sessionId: 7c83e1aa-d4f3-4a52-9b22-3e5b8a0e1c61
        expiresAt: '2026-04-30T21:15:00Z'
        returnUrl: https://merchant.example.com/callback
        cancelUrl: https://merchant.example.com/cancel

    UserProfile:
      type: object
      properties:
        clientToken:
          type: string
          format: uuid
        firstName:
          type: string
        lastName:
          type: string
        contactNo:
          type: string
        email:
          type: string
          format: email
        dateOfBirth:
          type: string
          format: date-time
      example:
        clientToken: 9da4062f-723c-445c-8dac-9bbfc84868bd
        firstName: Aisha
        lastName: Khan
        contactNo: '+14155551234'
        email: aisha@example.com
        dateOfBirth: '1990-04-15T00:00:00Z'

    Card:
      type: object
      properties:
        winkCardToken:
          type: string
          description: Wink-issued token referencing the stored card.
        nickName:
          type: string
        last4:
          type: string
        brand:
          type: string
          enum: [visa, mastercard, amex, discover]
        expiryMonth:
          type: string
        expiryYear:
          type: string
        isDefault:
          type: boolean
        billingAddressId:
          type: string
          format: uuid
      example:
        winkCardToken: card_YiVRVkz1T5Khfh
        nickName: Personal default
        last4: '1111'
        brand: mastercard
        expiryMonth: '12'
        expiryYear: '2029'
        isDefault: true
        billingAddressId: 34bdf64c-a340-4e41-b33c-54fce9f5a9e1

    Address:
      type: object
      properties:
        addressId:
          type: string
          format: uuid
        fullName:
          type: string
        addressLine1:
          type: string
        addressLine2:
          type: string
        city:
          type: string
        state:
          type: string
        country:
          type: string
        zipCode:
          type: string
        phoneNumber:
          type: string
        addressType:
          type: integer
          description: '1 = Billing, 2 = Shipping'
          enum: [1, 2]
        addressLabel:
          type: integer
        customAddressLabel:
          type: string
        isDefault:
          type: boolean
      example:
        addressId: f771eed1-092c-4b41-b9c4-97e56427e165
        fullName: Aisha Khan
        addressLine1: 350 Mission Street
        addressLine2: Suite 500
        city: San Francisco
        state: CA
        country: US
        zipCode: '94105'
        phoneNumber: '+14155551234'
        addressType: 1
        addressLabel: 1
        customAddressLabel: Home
        isDefault: true

    PaymentSession:
      type: object
      properties:
        paymentSessionId:
          type: string
          format: uuid
        amount:
          type: integer
          description: Amount in the smallest currency unit (cents for USD).
        currencyCode:
          type: string
        status:
          type: string
          enum: [pending, authorized, captured, cancelled]
        checkoutUrl:
          type: string
          format: uri
      example:
        paymentSessionId: 1f9b2c4e-7a05-4f71-91d4-68d0c1c2a771
        amount: 4999
        currencyCode: USD
        status: pending
        checkoutUrl: https://qapayments-api.winkapis.com/checkout/1f9b2c4e

    AuthorizationResult:
      type: object
      properties:
        authorizationId:
          type: string
        status:
          type: string
          enum: [authorized, declined, requires_action]
        amount:
          type: integer
        currencyCode:
          type: string
        cardLast4:
          type: string
        processedAt:
          type: string
          format: date-time
      example:
        authorizationId: auth_5sH3pK9XmnqRtEv
        status: authorized
        amount: 4999
        currencyCode: USD
        cardLast4: '1111'
        processedAt: '2026-04-30T20:42:11Z'

    RecognitionResult:
      type: object
      properties:
        clientToken:
          type: string
          format: uuid
        recognized:
          type: boolean
        confidence:
          type: number
          format: float
          minimum: 0
          maximum: 1
        livenessPassed:
          type: boolean
        firstName:
          type: string
        lastName:
          type: string
      example:
        clientToken: 9da4062f-723c-445c-8dac-9bbfc84868bd
        recognized: true
        confidence: 0.93
        livenessPassed: true
        firstName: Aisha
        lastName: Khan

    Error:
      type: object
      properties:
        code:
          type: string
        message:
          type: string
        traceId:
          type: string
          format: uuid
      example:
        code: invalid_request
        message: returnUrl must be a valid HTTPS URL.
        traceId: 4c1d2e9a-78b5-4d22-a5b1-9e0f9c11cc07

  responses:
    BadRequest:
      description: Invalid request — see error message for details.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: Missing or invalid credentials.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            code: unauthorized
            message: Bearer token is missing or expired.
            traceId: 4c1d2e9a-78b5-4d22-a5b1-9e0f9c11cc07
    ServerError:
      description: Server error — Wink will retry idempotent operations automatically.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            code: internal_error
            message: Unexpected error. Reference traceId in support requests.
            traceId: 4c1d2e9a-78b5-4d22-a5b1-9e0f9c11cc07

paths:
  # ─── Login ────────────────────────────────────────────────────────
  /wink/v1/session:
    post:
      tags: [Login]
      summary: Create session
      description: |
        Bootstrap a session for a user flow. Returns a `sessionId` you'll
        pass as a Bearer token on subsequent user-scoped calls (recognition,
        profile, wallet).
      operationId: createSession
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [returnUrl, cancelUrl]
              properties:
                returnUrl:
                  type: string
                  format: uri
                  description: Where to redirect on successful completion.
                cancelUrl:
                  type: string
                  format: uri
                  description: Where to redirect if the user cancels.
            example:
              returnUrl: https://merchant.example.com/callback
              cancelUrl: https://merchant.example.com/cancel
      security:
        - basicAuth: []
      responses:
        '200':
          description: Session created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Session'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /wink/v1/merchant/Configuration:
    get:
      tags: [Login]
      summary: Get merchant configuration
      description: Returns merchant-level settings — enabled biometric modalities, branding, supported flows.
      operationId: getMerchantConfiguration
      security:
        - basicAuth: []
      responses:
        '200':
          description: Merchant configuration.
          content:
            application/json:
              example:
                merchantId: mer_8KdN2pXa9
                name: Acme Storefront
                enabledModalities: [face, voice, winkpin]
                livenessRequired: true
                logoUrl: https://merchant.example.com/logo.png
                primaryColor: '#3D3C8A'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /wink/v1/Lookup/recognition-message:
    get:
      tags: [Login]
      summary: Get recognition challenge
      description: |
        Returns a one-time recognition message and ID. The user reads
        this aloud (voice modality) or it is shown alongside a face
        capture as anti-replay material.
      operationId: getRecognitionMessage
      security:
        - basicAuth: []
      responses:
        '200':
          description: A fresh recognition challenge.
          content:
            application/json:
              example:
                recognitionMessageId: 99976e12-d99f-48af-a8ef-251a7f6ef999
                message: 'Please say: blue dolphin seven'
                expiresAt: '2026-04-30T20:30:00Z'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /wink/v1/enroll-login/face:
    post:
      tags: [Login]
      summary: Enrol or recognise face
      description: |
        Submit a face image. Behaviour depends on `RequestType`:
        `1` = recognise (existing user), `2` = enrol (new user),
        `3` = enrol-or-recognise (auto).
      operationId: enrollLoginFace
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [InputFile, RequestType, LivenessRequestMode, ExternalDeviceId, PaymentIntent, threshold]
              properties:
                InputFile:
                  type: string
                  format: binary
                  description: JPEG or PNG, max 5 MB.
                RequestType:
                  type: integer
                  enum: [1, 2, 3]
                  description: '1 = recognise, 2 = enrol, 3 = enrol-or-recognise.'
                LivenessRequestMode:
                  type: integer
                  enum: [0, 1, 2]
                  description: '0 = passive, 1 = active blink, 2 = active turn.'
                ExternalDeviceId:
                  type: string
                  description: Free-form device identifier — POS terminal, kiosk ID, etc.
                PaymentIntent:
                  type: boolean
                  description: Whether this recognition will be used to authorise payment.
                threshold:
                  type: string
                  description: Match confidence threshold, 0.0–1.0.
            example:
              RequestType: 1
              LivenessRequestMode: 0
              ExternalDeviceId: kiosk-sf-01
              PaymentIntent: true
              threshold: '0.7'
      security:
        - basicAuth: []
      responses:
        '200':
          description: Recognition or enrolment outcome.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecognitionResult'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /wink/v1/2FA/recognition:
    post:
      tags: [Login]
      summary: Run second-factor recognition
      description: |
        Verify a second factor on top of a primary login. Supports voice
        (clip + recognition message), palm, Okta, and WinkPin via the
        `RecognitionType` field.

        **Auth:** Bearer **mfaToken** — the intermediate JWT returned by
        `POST /wink/v1/enroll-login/face` when face confidence is
        "Yellow" (`requiredSecondFactorAuth = true`). A session bearer
        or accessToken will be rejected with 401.
      operationId: secondFactorRecognition
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [recognitionMessageId, liveness, EnrollAndRecognition, RecognitionType]
              properties:
                InputFile:
                  type: string
                  format: binary
                  description: Voice clip (WAV/M4A) when `RecognitionType` is Voice.
                recognitionMessageId:
                  type: string
                  format: uuid
                  description: ID returned from `GET /wink/v1/Lookup/recognition-message`.
                liveness:
                  type: boolean
                EnrollAndRecognition:
                  type: integer
                  enum: [0, 1]
                  description: '0 = recognise only, 1 = enrol and recognise.'
                RecognitionType:
                  type: string
                  enum: [Voice, Palm, Okta, ManualWinkPin]
                ExternalId:
                  type: string
                  description: Required for Palm, Okta, and WinkPin recognition.
            example:
              recognitionMessageId: 99976e12-d99f-48af-a8ef-251a7f6ef999
              liveness: true
              EnrollAndRecognition: 0
              RecognitionType: Voice
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Recognition result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecognitionResult'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /wink/v1/client-consent/approve:
    post:
      tags: [Login]
      summary: Approve client consent
      description: |
        Records that the user has approved the merchant's data-sharing
        consent for this session.
      operationId: approveClientConsent
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Consent recorded.
          content:
            application/json:
              example:
                consentId: cns_3kZ9aQpV1Yt
                approvedAt: '2026-04-30T20:18:33Z'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /wink/v1/enroll-login/voice:
    post:
      tags: [Login]
      summary: Enrol or recognise voice
      description: |
        Submit a voice clip alongside the recognition message returned
        from `GET /wink/v1/Lookup/recognition-message`.

        **Auth:** Bearer **accessToken** (the JWT issued after a fully
        successful primary login). A session bearer or winkSeed will be
        rejected with 401. For the WinkSeed-tenant variant used during
        multi-step enrolment (face → voice), see
        `POST /winkseed/v1/enroll-login/voice`.
      operationId: enrollLoginVoice
      parameters:
        - name: inputToken
          in: query
          required: false
          schema:
            type: integer
          example: 205
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [InputFile, recognitionMessageId, liveness, EnrollAndRecognition]
              properties:
                InputFile:
                  type: string
                  format: binary
                  description: WAV or M4A, 3–10 seconds.
                recognitionMessageId:
                  type: string
                  format: uuid
                liveness:
                  type: boolean
                EnrollAndRecognition:
                  type: integer
                  enum: [0, 1]
            example:
              recognitionMessageId: 99976e12-d99f-48af-a8ef-251a7f6ef999
              liveness: true
              EnrollAndRecognition: 1
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Recognition result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecognitionResult'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /winkseed/v1/enroll-login/voice:
    post:
      tags: [Login]
      summary: Enrol or recognise voice (WinkSeed)
      description: WinkSeed-tenant variant of `POST /wink/v1/enroll-login/voice`.
      operationId: enrollLoginVoiceWinkSeed
      parameters:
        - name: inputToken
          in: query
          required: false
          schema:
            type: integer
          example: 205
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [InputFile, recognitionMessageId, liveness, EnrollAndRecognition]
              properties:
                InputFile:
                  type: string
                  format: binary
                recognitionMessageId:
                  type: string
                  format: uuid
                liveness:
                  type: boolean
                EnrollAndRecognition:
                  type: integer
                  enum: [0, 1]
            example:
              recognitionMessageId: 99976e12-d99f-48af-a8ef-251a7f6ef999
              liveness: true
              EnrollAndRecognition: 1
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Recognition result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecognitionResult'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /wink/v1/profile:
    post:
      tags: [Login]
      summary: Create user profile
      description: |
        Create a Wink-managed profile for a user. Returns a `clientToken`
        that subsequent calls use to reference this profile.
      operationId: createUserProfile
      parameters:
        - name: clientId
          in: header
          required: false
          schema:
            type: string
          example: mer_8KdN2pXa9
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [firstName, lastName, contactNo, email]
              properties:
                clientToken:
                  type: string
                  format: uuid
                  description: Optional — supply to upsert against an existing token.
                firstName:
                  type: string
                lastName:
                  type: string
                contactNo:
                  type: string
                email:
                  type: string
                  format: email
                dateOfBirth:
                  type: string
                  format: date-time
            example:
              firstName: Aisha
              lastName: Khan
              contactNo: '+14155551234'
              email: aisha@example.com
              dateOfBirth: '1990-04-15T00:00:00Z'
      security:
        - basicAuth: []
      responses:
        '200':
          description: Profile created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserProfile'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }
    put:
      tags: [Login]
      summary: Update user profile
      operationId: updateUserProfile
      parameters:
        - name: clientId
          in: header
          required: false
          schema:
            type: string
          example: mer_8KdN2pXa9
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                qCToken:
                  type: string
                firstName:
                  type: string
                lastName:
                  type: string
                contactNo:
                  type: string
                email:
                  type: string
                  format: email
                dateOfBirth:
                  type: string
                  format: date-time
            example:
              qCToken: qct_8865-222-123
              firstName: Aisha
              lastName: Khan-Patel
              contactNo: '+14155551234'
              email: aisha.kp@example.com
              dateOfBirth: '1990-04-15T00:00:00Z'
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Profile updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserProfile'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }
    get:
      tags: [Login]
      summary: Get user profile
      operationId: getUserProfile
      parameters:
        - name: clientId
          in: header
          required: false
          schema:
            type: string
          example: mer_8KdN2pXa9
      security:
        - bearerAuth: []
      responses:
        '200':
          description: User profile.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserProfile'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /winkseed/v1/profile:
    put:
      tags: [Login]
      summary: Update user profile (WinkSeed)
      description: WinkSeed-tenant variant of `PUT /wink/v1/profile`.
      operationId: updateUserProfileWinkSeed
      parameters:
        - name: clientId
          in: header
          required: false
          schema:
            type: string
          example: mer_8KdN2pXa9
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                qCToken:
                  type: string
                firstName:
                  type: string
                lastName:
                  type: string
                contactNo:
                  type: string
                email:
                  type: string
                  format: email
                dateOfBirth:
                  type: string
                  format: date-time
                PalmId:
                  type: string
            example:
              qCToken: qct_8865-222-123
              firstName: Aisha
              lastName: Khan
              contactNo: '+14155551234'
              email: aisha@example.com
              dateOfBirth: '1990-04-15T00:00:00Z'
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Profile updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserProfile'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }
    get:
      tags: [Login]
      summary: Get user profile (WinkSeed)
      operationId: getUserProfileWinkSeed
      parameters:
        - name: clientId
          in: header
          required: false
          schema:
            type: string
          example: mer_8KdN2pXa9
      security:
        - bearerAuth: []
      responses:
        '200':
          description: User profile.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserProfile'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /wink/v1/profile/ClientToken/{clientToken}:
    delete:
      tags: [Login]
      summary: Delete profile by client token
      operationId: deleteProfileByToken
      parameters:
        - name: clientToken
          in: path
          required: true
          schema:
            type: string
            format: uuid
          example: 9da4062f-723c-445c-8dac-9bbfc84868bd
        - name: clientId
          in: header
          required: false
          schema:
            type: string
          example: mer_8KdN2pXa9
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Profile deleted.
          content:
            application/json:
              example:
                deleted: true
                clientToken: 9da4062f-723c-445c-8dac-9bbfc84868bd
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /winkseed/v1/profile/ClientToken/{clientToken}:
    delete:
      tags: [Login]
      summary: Delete profile by client token (WinkSeed)
      operationId: deleteProfileByTokenWinkSeed
      parameters:
        - name: clientToken
          in: path
          required: true
          schema:
            type: string
            format: uuid
          example: 9da4062f-723c-445c-8dac-9bbfc84868bd
        - name: clientId
          in: header
          required: false
          schema:
            type: string
          example: mer_8KdN2pXa9
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Profile deleted.
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /wink/v1/profile/enroll/mfa:
    post:
      tags: [Login]
      summary: Enrol second factor after login
      description: |
        Add a second factor (palm or Okta) to a profile after the user
        has already authenticated with their primary modality.
      operationId: enrollMfaAfterLogin
      parameters:
        - name: clientId
          in: header
          required: false
          schema:
            type: string
          example: mer_8KdN2pXa9
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [RequestType, ExternalId]
              properties:
                RequestType:
                  type: integer
                  enum: [1, 2]
                  description: '1 = Palm, 2 = Okta.'
                ExternalId:
                  type: string
            example:
              RequestType: 1
              ExternalId: palm_aisha_left
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Second factor enrolled.
          content:
            application/json:
              example:
                enrolled: true
                factorId: f2_palm_8KdN
                requestType: 1
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /winkseed/v1/profile/enroll/mfa:
    post:
      tags: [Login]
      summary: Enrol second factor after login (WinkSeed)
      operationId: enrollMfaAfterLoginWinkSeed
      parameters:
        - name: clientId
          in: header
          required: false
          schema:
            type: string
          example: mer_8KdN2pXa9
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [RequestType, ExternalId]
              properties:
                RequestType:
                  type: integer
                  enum: [1, 2]
                ExternalId:
                  type: string
            example:
              RequestType: 1
              ExternalId: palm_aisha_left
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Second factor enrolled.
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /wink/v1/device/register:
    post:
      tags: [Login]
      summary: Register device
      description: |
        Register a hardware device (POS terminal, kiosk, mobile) so that
        recognition events from it can be attributed and rate-limited.
      operationId: registerDevice
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [deviceId, deviceType, osName, osVersion, appVersion]
              properties:
                deviceId:
                  type: string
                  description: Unique hardware identifier.
                deviceType:
                  type: integer
                  enum: [1, 2, 3]
                  description: '1 = iOS, 2 = Android, 3 = POS / kiosk.'
                osName:
                  type: string
                osVersion:
                  type: string
                appVersion:
                  type: string
            example:
              deviceId: device_a1b2c3d4
              deviceType: 3
              osName: Wink-POS
              osVersion: '13'
              appVersion: '4.2.0'
      security:
        - basicAuth: []
      responses:
        '200':
          description: Device registered.
          content:
            application/json:
              example:
                deviceId: device_a1b2c3d4
                registered: true
                registeredAt: '2026-04-30T20:15:00Z'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /api/ConfidentialClient/verify-client:
    post:
      tags: [Login]
      summary: Verify confidential client
      description: |
        Verify a confidential-client OAuth credential. Used by server-side
        integrations that hold a `clientSecret` to bootstrap a session
        without Basic auth.
      operationId: verifyConfidentialClient
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [clientId, clientSecret]
              properties:
                clientId:
                  type: string
                accessToken:
                  type: string
                clientSecret:
                  type: string
            example:
              clientId: mer_8KdN2pXa9
              accessToken: at_2YpzM4Q9nFrL
              clientSecret: cs_Test_Czk8vfHhsBSqRSnpiyL
      responses:
        '200':
          description: Client verified.
          content:
            application/json:
              example:
                verified: true
                clientId: mer_8KdN2pXa9
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /wink/v1/merchant/external-iam-provider/{providerId}:
    get:
      tags: [Login]
      summary: Get external IAM provider
      description: |
        Returns the external identity provider (Okta, Azure AD, Google
        Workspace, etc.) configured for this merchant, identified by
        the integer `providerId`.
      operationId: getExternalIamProvider
      parameters:
        - name: providerId
          in: path
          required: true
          schema:
            type: integer
          example: 1
      security:
        - basicAuth: []
      responses:
        '200':
          description: External IAM provider configuration.
          content:
            application/json:
              example:
                providerId: 1
                providerType: okta
                domain: acme.okta.com
                ssoUrl: https://acme.okta.com/app/wink/sso/saml
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /api/UserIntegration/user-info-ableneo:
    get:
      tags: [Login]
      summary: Get user info (Ableneo integration)
      description: |
        Wink-to-Ableneo SSO bridge — returns Ableneo user info for the
        currently authenticated session.
      operationId: getUserInfoAbleneo
      parameters:
        - name: oAUTH_Request_Id
          in: header
          required: false
          schema:
            type: string
          example: 56def195-26e6-45de-9b6c-4faf701d2266
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Ableneo user info.
          content:
            application/json:
              example:
                sub: aisha@example.com
                given_name: Aisha
                family_name: Khan
                email: aisha@example.com
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /wink/v1/profile/Validate/contactNo:
    post:
      tags: [Login]
      summary: Validate contact number
      description: Check whether a phone number is already associated with a Wink profile.
      operationId: validateContactNo
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [contactNo]
              properties:
                contactNo:
                  type: string
            example:
              contactNo: '+14155551234'
      security:
        - basicAuth: []
      responses:
        '200':
          description: Validation result.
          content:
            application/json:
              example:
                contactNo: '+14155551234'
                exists: false
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /wink/v1/profile/Validate/Email:
    post:
      tags: [Login]
      summary: Validate email
      description: Check whether an email address is already associated with a Wink profile.
      operationId: validateEmail
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [Email]
              properties:
                Email:
                  type: string
                  format: email
            example:
              Email: aisha@example.com
      security:
        - basicAuth: []
      responses:
        '200':
          description: Validation result.
          content:
            application/json:
              example:
                exists: true
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /wink/v1/profile/ext:
    post:
      tags: [Login]
      summary: Create external-user profile
      description: |
        Create a Wink profile for a user already authenticated by an
        external IAM (the merchant's own identity provider). Returns a
        `winkSeed` for follow-up biometric enrolment.
      operationId: createExternalUserProfile
      security:
        - basicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [clientToken, firstName, lastName, contactNo, email]
              properties:
                clientToken:
                  type: string
                  format: uuid
                firstName:
                  type: string
                lastName:
                  type: string
                contactNo:
                  type: string
                email:
                  type: string
                  format: email
                dateOfBirth:
                  type: string
                  format: date-time
            example:
              clientToken: 6078d2d8-74de-4929-8476-5d191286adf5
              firstName: Hardik
              lastName: Sanghavi
              contactNo: '54245454588'
              email: hardik1223@example.com
              dateOfBirth: '1944-01-02T00:00:00'
      responses:
        '200':
          description: External-user profile created.
          content:
            application/json:
              example:
                success: true
                extUserId: 88679314-378d-45c1-93ee-65ebe64e2f51
                winkAuth:
                  winkSeed: eyJhbGci...
                  oAuthRequestId: 602ab57a-4f58-466c-82a5-f244535e19c1
                responseStatuCode: Success
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /wink/v1/profile/palm:
    post:
      tags: [Login]
      summary: Get profile by palm ID
      description: |
        Look up a user profile by their enrolled palm identifier. Returns
        a `winkSeed` for subsequent calls. Set `PaymentIntent: true` to
        receive payment-related tokens in the response.
      operationId: getProfileByPalm
      security:
        - basicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [PalmId]
              properties:
                PalmId:
                  type: string
                PaymentIntent:
                  type: boolean
            example:
              PalmId: palm_880098
              PaymentIntent: true
      responses:
        '200':
          description: Profile returned.
          content:
            application/json:
              example:
                user:
                  firstName: Aisha
                  lastName: Khan
                  email: aisha@example.com
                  winkTag: ';stage-aisha'
                winkAuth:
                  winkSeed: eyJhbGci...
                  oAuthRequestId: a368e278-cba9-4807-ad0c-649b5bd4a7ac
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  # /wink/v1/profile/clients omitted from public spec — Wink restricts
  # the endpoint to a small allowlist of internal client IDs, so
  # external developers always get 401. Documenting it would lead them
  # into a dead end. Add back if a partner is granted the scope.

  /api/UserAPI/v1/users/palmid:
    get:
      tags: [Login]
      summary: Look up users by palm ID
      description: |
        Internal UserAPI lookup by palm ID — does NOT return `winkAuth`
        tokens. Pass your merchant ID via the `ClientId` header.
      operationId: getUsersByPalmId
      parameters:
        - name: palmId
          in: query
          required: true
          schema:
            type: string
          example: palm_880098
        - name: paymentIntent
          in: query
          required: false
          schema:
            type: boolean
          example: true
        - name: ClientId
          in: header
          required: false
          schema:
            type: string
          example: winkwallet
      responses:
        '200':
          description: User info returned.
          content:
            application/json:
              example:
                user:
                  firstName: Aisha
                  lastName: Khan
                  email: aisha@example.com
                  winkTag: ';stage-aisha'
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /wink/v1/re-enroll/palm:
    post:
      tags: [Login]
      summary: Re-enrol palm
      description: |
        Update the palm biometric identifier for the authenticated user.
        Pass the user's `accessToken` as Bearer.
      operationId: reEnrollPalm
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [PalmId]
              properties:
                PalmId:
                  type: string
                  description: New palm identifier.
            example:
              PalmId: palm_v_7777
      responses:
        '200':
          description: Palm re-enrolled.
          content:
            application/json:
              example:
                responseStatuCode: Success
                message: null
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /wink/v1/re-enroll/voice:
    post:
      tags: [Login]
      summary: Re-enrol voice
      description: |
        Update the voice biometric for the authenticated user. Note the
        multipart field is `VoiceFile` (not `InputFile` like the other
        voice endpoints). Pass the user's `accessToken` as Bearer.
      operationId: reEnrollVoice
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [VoiceFile]
              properties:
                VoiceFile:
                  type: string
                  format: binary
                  description: New voice audio (WAV, M4A, or MP3).
                recognitionMessageId:
                  type: string
                  format: uuid
                liveness:
                  type: boolean
            example:
              recognitionMessageId: 99976e12-d99f-48af-a8ef-251a7f6ef999
              liveness: true
      responses:
        '200':
          description: Voice re-enrolled.
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /wink/v1/2FA/delete:
    post:
      tags: [Login]
      summary: Delete second factor
      description: |
        Remove an enrolled biometric for the authenticated user.
        `biometricType`: `1` = Face, `2` = Voice, `3` = Palm. Pass the
        user's `accessToken` as Bearer.
      operationId: deleteSecondFactor
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [biometricType]
              properties:
                biometricType:
                  type: integer
                  enum: [1, 2, 3]
                  description: '1 = Face, 2 = Voice, 3 = Palm.'
            example:
              biometricType: 2
      responses:
        '200':
          description: Biometric deleted.
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /wink/v1/profile/verification:
    post:
      tags: [Login]
      summary: Verify documents
      description: |
        Submit a document-verification result against the current user's
        profile. Authenticated as the user — pass the user's accessToken
        as Bearer.
      operationId: verifyDocs
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                verificationType:
                  type: integer
                  enum: [0, 1, 2]
                  description: '0 = ID document, 1 = address, 2 = selfie liveness.'
                verifiedBy:
                  type: integer
                  enum: [0, 1, 2]
                  description: '0 = self, 1 = agent, 2 = system.'
            example:
              verificationType: 0
              verifiedBy: 2
      responses:
        '200':
          description: Verification recorded.
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /wink/winkpin/generate:
    post:
      tags: [Login]
      summary: Generate WinkPin
      description: |
        Generate a one-time WinkPin (numeric code) the user can use as a
        fallback factor when biometrics are unavailable.
      operationId: generateWinkPin
      responses:
        '200':
          description: WinkPin generated.
          content:
            application/json:
              example:
                winkPin: '4892'
                expiresAt: '2026-04-30T20:25:00Z'
                pinId: pin_kZ9aQpV1Yt
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /wink/v1/anonymous/recognition/voice:
    post:
      tags: [Login]
      summary: Anonymous voice recognition (call centre)
      description: |
        Submit a voice clip from a non-authenticated caller (typical use:
        IVR call-centre identification). Returns a `clientToken` if a
        match is found.
      operationId: anonymousVoiceRecognition
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [InputFile]
              properties:
                InputFile:
                  type: string
                  format: binary
                  description: Voice clip, 3–10 seconds.
      responses:
        '200':
          description: Recognition result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecognitionResult'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  # ─── Wallet ───────────────────────────────────────────────────────
  /winkseed/V1/wallet/user:
    post:
      tags: [Wallet]
      summary: Save wallet user (WinkSeed)
      description: Create a wallet record for the currently-authenticated user under WinkSeed auth.
      operationId: walletSaveUserWinkSeed
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                winkId:
                  type: string
                  format: uuid
                token:
                  type: string
                  format: uuid
            example:
              winkId: 7cb85ba2-1bf5-4779-ab75-ef74e2cab746
              token: e6f393ee-0f37-465e-99d9-0793511d210c
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Wallet user saved.
          content:
            application/json:
              example:
                winkId: 7cb85ba2-1bf5-4779-ab75-ef74e2cab746
                created: true
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /winkseed/V1/wallet/card:
    get:
      tags: [Wallet]
      summary: List cards (WinkSeed)
      operationId: walletGetCardsWinkSeed
      parameters:
        - name: merchantSpecific
          in: query
          required: false
          schema:
            type: string
          example: 'true'
      security:
        - bearerAuth: []
      responses:
        '200':
          description: List of cards.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Card'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }
    post:
      tags: [Wallet]
      summary: Save card (WinkSeed)
      description: |
        Save a card to the user's wallet. Wink returns a `winkCardToken`
        — the raw card number is never stored unencrypted on your side.
      operationId: walletSaveCardWinkSeed
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [nameOnCard, cardNumberAlias, verificationValueAlias, expiryYear, expiryMonth]
              properties:
                nameOnCard:
                  type: string
                cardNumberAlias:
                  type: string
                  description: PCI-tokenised card number (use Wink Web SDK to obtain).
                verificationValueAlias:
                  type: string
                  description: PCI-tokenised CVV (use Wink Web SDK to obtain).
                expiryYear:
                  type: string
                expiryMonth:
                  type: string
                nickName:
                  type: string
                isDefault:
                  type: boolean
                IsAutoPayOn:
                  type: boolean
                billingAddressId:
                  type: string
                  format: uuid
            example:
              nameOnCard: Aisha Khan
              cardNumberAlias: '5555555555551111'
              verificationValueAlias: '123'
              expiryYear: '2029'
              expiryMonth: '12'
              nickName: Personal default
              isDefault: true
              IsAutoPayOn: true
              billingAddressId: 34bdf64c-a340-4e41-b33c-54fce9f5a9e1
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Card saved.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Card'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /WinkSeed/V1/wallet/Address:
    post:
      tags: [Wallet]
      summary: Save billing address (WinkSeed)
      operationId: walletSaveBillingAddressWinkSeed
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Address'
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Address saved.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Address'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }
    get:
      tags: [Wallet]
      summary: List shipping addresses (WinkSeed)
      operationId: walletGetShippingAddressWinkSeed
      parameters:
        - name: addressType
          in: query
          required: false
          schema:
            type: integer
          example: 2
      security:
        - bearerAuth: []
      responses:
        '200':
          description: List of addresses.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Address'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /winkseed/V1/wallet/Address:
    post:
      tags: [Wallet]
      summary: Save shipping address (WinkSeed)
      operationId: walletSaveShippingAddressWinkSeed
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Address'
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Address saved.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Address'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }
    get:
      tags: [Wallet]
      summary: List shipping addresses
      operationId: walletGetShippingAddress
      parameters:
        - name: addressType
          in: query
          required: false
          schema:
            type: integer
          example: 2
      security:
        - bearerAuth: []
      responses:
        '200':
          description: List of addresses.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Address'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /winkSeed/V1/wallet/Address:
    get:
      tags: [Wallet]
      summary: List billing addresses
      operationId: walletGetBillingAddress
      parameters:
        - name: addressType
          in: query
          required: false
          schema:
            type: integer
          example: 1
        - name: merchantSpecific
          in: query
          required: false
          schema:
            type: string
          example: 'true'
      security:
        - bearerAuth: []
      responses:
        '200':
          description: List of addresses.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Address'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /V1/wallet/user:
    post:
      tags: [Wallet]
      summary: Save wallet user
      operationId: walletSaveUser
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                winkId:
                  type: string
                  format: uuid
                token:
                  type: string
                  format: uuid
            example:
              winkId: 7cb85ba2-1bf5-4779-ab75-ef74e2cab746
              token: e6f393ee-0f37-465e-99d9-0793511d210c
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Wallet user saved.
          content:
            application/json:
              example:
                winkId: 7cb85ba2-1bf5-4779-ab75-ef74e2cab746
                created: true
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }
    # GET /V1/wallet/user was deprecated by Wink; only POST remains.

  /V1/wallet/card:
    get:
      tags: [Wallet]
      summary: List cards
      operationId: walletGetCards
      parameters:
        - name: merchantSpecific
          in: query
          required: false
          schema:
            type: string
          example: 'true'
      security:
        - bearerAuth: []
      responses:
        '200':
          description: List of cards.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Card'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }
    post:
      tags: [Wallet]
      summary: Save card
      description: |
        Save a card to the user's wallet. Wink returns a `winkCardToken`
        — the raw card number is never stored unencrypted on your side.
        Use the Web SDK or our hosted card form to obtain
        `cardNumberAlias` and `verificationValueAlias` without your
        servers ever touching PAN.
      operationId: walletSaveCard
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [nameOnCard, cardNumberAlias, verificationValueAlias, expiryYear, expiryMonth]
              properties:
                nameOnCard:
                  type: string
                cardNumberAlias:
                  type: string
                verificationValueAlias:
                  type: string
                expiryYear:
                  type: string
                expiryMonth:
                  type: string
                nickName:
                  type: string
                isDefault:
                  type: boolean
                IsAutoPayOn:
                  type: boolean
                billingAddressId:
                  type: string
                  format: uuid
            example:
              nameOnCard: Aisha Khan
              cardNumberAlias: '5555555555551111'
              verificationValueAlias: '123'
              expiryYear: '2029'
              expiryMonth: '12'
              nickName: Personal default
              isDefault: true
              IsAutoPayOn: true
              billingAddressId: 34bdf64c-a340-4e41-b33c-54fce9f5a9e1
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Card saved.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Card'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /V1/wallet/Address:
    post:
      tags: [Wallet]
      summary: Save billing address
      operationId: walletSaveBillingAddress
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Address'
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Address saved.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Address'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /V1/wallet/Address/{addressId}:
    delete:
      tags: [Wallet]
      summary: Delete address
      operationId: walletDeleteAddress
      parameters:
        - name: addressId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          example: f771eed1-092c-4b41-b9c4-97e56427e165
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Address deleted.
          content:
            application/json:
              example:
                addressId: f771eed1-092c-4b41-b9c4-97e56427e165
                deleted: true
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  # ─── Payment ──────────────────────────────────────────────────────
  /V1/payment/session:
    post:
      tags: [Payment]
      summary: Create payment session
      description: |
        Create a payment session for a checkout flow. Returns a
        `paymentSessionId` and (for hosted checkout) a `checkoutUrl` to
        which you redirect the user.

        `amount` is an integer in the smallest currency unit — `4999`
        means **$49.99** in USD.
      operationId: createPaymentSession
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [amount, currencyCode, integrationType]
              properties:
                paymentId:
                  type: string
                  description: Optional merchant-side reference.
                customerToken:
                  type: string
                integrationToken:
                  type: string
                amount:
                  type: integer
                  description: Smallest currency unit (cents for USD).
                currencyCode:
                  type: string
                  example: USD
                returnUrl:
                  type: string
                  format: uri
                cancelUrl:
                  type: string
                  format: uri
                description:
                  type: string
                integrationType:
                  type: string
                  enum: [checkout, inline, agent]
                IntegrationMode:
                  type: integer
                  enum: [0, 1]
                  description: '0 = test, 1 = live.'
            example:
              amount: 4999
              currencyCode: USD
              returnUrl: https://merchant.example.com/order/complete
              cancelUrl: https://merchant.example.com/cart
              description: Order #A-10421 — 1 × Wink Hoodie
              integrationType: checkout
              IntegrationMode: 1
      security:
        - basicAuth: []
      responses:
        '200':
          description: Payment session created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentSession'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }

  /V1/payment/authorize:
    post:
      tags: [Payment]
      summary: Authorize payment
      description: |
        Authorize a payment against a saved card token. Returns the
        authorization ID and status. Capture happens automatically for
        single-step flows; multi-step flows can capture later via the
        Capture endpoint (coming soon).
      operationId: authorizePayment
      parameters:
        - name: X-Wink-Seed
          in: header
          required: false
          schema:
            type: string
          example: ws_8KdN2pXa9
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [winkCardToken]
              properties:
                winkCardToken:
                  type: string
                  description: Token returned from `POST /V1/wallet/card`.
            example:
              winkCardToken: card_YiVRVkz1T5Khfh
      security:
        - basicAuth: []
      responses:
        '200':
          description: Authorization result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthorizationResult'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '500': { $ref: '#/components/responses/ServerError' }
