openapi: 3.1.0
info:
  title: ZintGO POS API
  version: 1.0.0
  description: Store-scoped POS API for auth, customer lookup, points, rewards, redemption,
    and store config.
servers:
- url: /
  description: Relative API base path
security:
- PosBearerAuth: []
tags:
- name: Auth
- name: Customers
- name: Store
- name: Points
paths:
  /auth/getAuthToken:
    post:
      tags:
      - Auth
      summary: Issue a POS bearer token
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AuthTokenRequest'
            examples:
              default:
                value:
                  storeId: STORE_ID
                  clientSecret: SECRET
      responses:
        '200':
          description: Token issued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthTokenResponse'
        '401':
          $ref: '#/components/responses/InvalidCredentials'
        '403':
          $ref: '#/components/responses/Forbidden'
        '501':
          $ref: '#/components/responses/InvalidInput'
        '500':
          $ref: '#/components/responses/InternalError'
  /customers/add:
    post:
      tags:
      - Customers
      summary: Add or update a customer
      parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
      - $ref: '#/components/parameters/CorrelationId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CustomerAddRequest'
            examples:
              default:
                value:
                  customerEmail: CUSTOMER_EMAIL
                  customerPhone: CUSTOMER_PHONE
                  displayName: CUSTOMER_NAME
      responses:
        '200':
          description: Customer returned
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomerResponse'
        '420':
          $ref: '#/components/responses/DuplicateIdempotency'
        '401':
          $ref: '#/components/responses/AuthError'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '501':
          $ref: '#/components/responses/InvalidInput'
        '500':
          $ref: '#/components/responses/InternalError'
  /customers/read:
    get:
      tags:
      - Customers
      summary: Read a customer within the authenticated store
      description: customerUid, customerEmail, and customerPhone are optional fields;
        one is mandatory.
      parameters:
      - $ref: '#/components/parameters/CustomerUid'
      - $ref: '#/components/parameters/CustomerEmail'
      - $ref: '#/components/parameters/CustomerPhone'
      responses:
        '200':
          description: Customer returned only if wallet exists for the authenticated
            store
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomerResponse'
        '401':
          $ref: '#/components/responses/AuthError'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '501':
          $ref: '#/components/responses/InvalidInput'
        '500':
          $ref: '#/components/responses/InternalError'
  /store/config:
    get:
      tags:
      - Store
      summary: Read store reward config
      responses:
        '200':
          description: Store config returned
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StoreConfigResponse'
        '401':
          $ref: '#/components/responses/AuthError'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalError'
    post:
      tags:
      - Store
      summary: Update store reward config
      parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
      - $ref: '#/components/parameters/CorrelationId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StoreConfigUpdateRequest'
      responses:
        '200':
          description: Store config updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StoreConfigResponse'
        '420':
          $ref: '#/components/responses/DuplicateIdempotency'
        '401':
          $ref: '#/components/responses/AuthError'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '501':
          $ref: '#/components/responses/InvalidInput'
        '500':
          $ref: '#/components/responses/InternalError'
  /points/add:
    post:
      tags:
      - Points
      summary: Add explicit points
      parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
      - $ref: '#/components/parameters/CorrelationId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
              - $ref: '#/components/schemas/CustomerIdentifier'
              - type: object
                required:
                - points
                properties:
                  points:
                    type: integer
                    minimum: 1
                  note:
                    type: string
                    maxLength: 500
      responses:
        '200':
          description: Points added
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AddPointsResponse'
        '420':
          $ref: '#/components/responses/DuplicateIdempotency'
        '401':
          $ref: '#/components/responses/AuthError'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '501':
          $ref: '#/components/responses/InvalidInput'
        '500':
          $ref: '#/components/responses/InternalError'
  /points/addExtended:
    post:
      tags:
      - Points
      summary: Add points using store earn config
      parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
      - $ref: '#/components/parameters/CorrelationId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
              - $ref: '#/components/schemas/CustomerIdentifier'
              - type: object
                properties:
                  value:
                    type: number
                    exclusiveMinimum: 0
                  quantity:
                    type: number
                    exclusiveMinimum: 0
                  billAmount:
                    type: number
                    exclusiveMinimum: 0
                  amount:
                    type: number
                    exclusiveMinimum: 0
                  note:
                    type: string
                    maxLength: 500
      responses:
        '200':
          description: Points added with reward availability
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AddExtendedResponse'
        '420':
          $ref: '#/components/responses/DuplicateIdempotency'
        '401':
          $ref: '#/components/responses/AuthError'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '501':
          $ref: '#/components/responses/InvalidInput'
        '500':
          $ref: '#/components/responses/InternalError'
  /points/read:
    get:
      tags:
      - Points
      summary: Read customer points
      description: customerUid, customerEmail, and customerPhone are optional fields;
        one is mandatory.
      parameters:
      - $ref: '#/components/parameters/CustomerUid'
      - $ref: '#/components/parameters/CustomerEmail'
      - $ref: '#/components/parameters/CustomerPhone'
      responses:
        '200':
          description: Points returned
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReadPointsResponse'
        '401':
          $ref: '#/components/responses/AuthError'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '501':
          $ref: '#/components/responses/InvalidInput'
        '500':
          $ref: '#/components/responses/InternalError'
  /points/readExtended:
    get:
      tags:
      - Points
      summary: Read customer points and reward summary
      description: customerUid, customerEmail, and customerPhone are optional fields;
        one is mandatory.
      parameters:
      - $ref: '#/components/parameters/CustomerUid'
      - $ref: '#/components/parameters/CustomerEmail'
      - $ref: '#/components/parameters/CustomerPhone'
      responses:
        '200':
          description: Points and reward summary returned
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReadExtendedResponse'
        '401':
          $ref: '#/components/responses/AuthError'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '501':
          $ref: '#/components/responses/InvalidInput'
        '500':
          $ref: '#/components/responses/InternalError'
  /points/redeem:
    post:
      tags:
      - Points
      summary: Redeem one available reward
      parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
      - $ref: '#/components/parameters/CorrelationId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
              - $ref: '#/components/schemas/CustomerIdentifier'
              - type: object
                properties:
                  billAmount:
                    type: number
                    minimum: 0
                  amount:
                    type: number
                    minimum: 0
      responses:
        '200':
          description: Reward redeemed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RedeemResponse'
        '420':
          $ref: '#/components/responses/DuplicateIdempotency'
        '401':
          $ref: '#/components/responses/AuthError'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '412':
          $ref: '#/components/responses/FailedPrecondition'
        '501':
          $ref: '#/components/responses/InvalidInput'
        '500':
          $ref: '#/components/responses/InternalError'
components:
  securitySchemes:
    PosBearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
  parameters:
    CustomerUid:
      name: customerUid
      in: query
      required: false
      schema:
        type: string
    CustomerEmail:
      name: customerEmail
      in: query
      required: false
      schema:
        type: string
    CustomerPhone:
      name: customerPhone
      in: query
      required: false
      schema:
        type: string
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      schema:
        type: string
        minLength: 8
        maxLength: 200
        pattern: ^[^/]+$
      description: Unique key for the authenticated store and write operation. Alias
        X-Idempotency-Key is also accepted.
    CorrelationId:
      name: Correlation-ID
      in: header
      required: true
      schema:
        type: string
        minLength: 1
        maxLength: 200
        pattern: ^[^/]+$
      description: POS transaction/request reference. Alias X-Correlation-ID is also
        accepted.
  responses:
    AuthError:
      description: Missing or invalid bearer token
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    InvalidCredentials:
      description: Invalid store credentials
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Forbidden:
      description: Forbidden
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    FailedPrecondition:
      description: Reward cannot be redeemed
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    DuplicateIdempotency:
      description: Idempotency key already used for the authenticated store
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    InvalidInput:
      description: Invalid or missing input
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    InternalError:
      description: Unexpected server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  schemas:
    AuthTokenRequest:
      type: object
      required:
      - storeId
      - clientSecret
      properties:
        storeId:
          type: string
        clientSecret:
          type: string
    AuthTokenResponse:
      type: object
      required:
      - access_token
      - token_type
      - expires_in
      properties:
        access_token:
          type: string
        token_type:
          type: string
          const: Bearer
        expires_in:
          type: integer
    CustomerIdentifier:
      type: object
      description: customerUid, customerEmail, and customerPhone are optional fields;
        one is mandatory.
      properties:
        customerUid:
          type: string
        customerEmail:
          type: string
        customerPhone:
          type: string
      anyOf:
      - required:
        - customerUid
      - required:
        - customerEmail
      - required:
        - customerPhone
    CustomerAddRequest:
      type: object
      description: customerEmail and customerPhone are optional fields; at least one
        is mandatory.
      properties:
        customerEmail:
          type: string
        customerPhone:
          type: string
        displayName:
          type: string
      anyOf:
      - required:
        - customerEmail
      - required:
        - customerPhone
    CustomerProfile:
      type: object
      properties:
        uid:
          type: string
        exists:
          type: boolean
        displayName:
          type: string
        email:
          type: string
        emailLower:
          type: string
        phone:
          type: string
        phoneE164:
          type: string
    WalletSummary:
      type: object
      properties:
        exists:
          type: boolean
        points:
          type: integer
    CustomerIdentityFields:
      type: object
      required:
      - customerUid
      - customerEmail
      - customerPhone
      properties:
        customerUid:
          type: string
        customerEmail:
          type: string
        customerPhone:
          type: string
    CustomerResponse:
      allOf:
      - type: object
        required:
        - storeId
        properties:
          storeId:
            type: string
          created:
            type: boolean
          matchedBy:
            type: string
            enum:
            - uid
            - email
            - phone
          customer:
            $ref: '#/components/schemas/CustomerProfile'
          wallet:
            $ref: '#/components/schemas/WalletSummary'
      - $ref: '#/components/schemas/CustomerIdentityFields'
    StoreConfig:
      type: object
      properties:
        qrType:
          type: string
          enum:
          - static
          - dynamic
        earnMode:
          type: string
          enum:
          - visit
          - quantity
          - bill
        pointsPerVisit:
          type: integer
        pointsPerQty:
          type: integer
        pointsPer10:
          type: integer
        rewardType:
          type: string
          enum:
          - free
          - percent
          - amount
        rewardThresholdPoints:
          type: integer
        rewardLabel:
          type: string
        rewardConfig:
          type: object
    StoreConfigResponse:
      type: object
      required:
      - storeId
      - config
      properties:
        storeId:
          type: string
        config:
          $ref: '#/components/schemas/StoreConfig'
    StoreConfigUpdateRequest:
      type: object
      properties:
        qrType:
          type: string
          enum:
          - static
          - dynamic
        earnMode:
          type: string
          enum:
          - visit
          - quantity
          - bill
        pointsPerVisit:
          type: integer
        pointsPerQty:
          type: integer
        pointsPer10:
          type: integer
        rewardType:
          type: string
          enum:
          - free
          - percent
          - amount
        rewardThresholdPoints:
          type: integer
        rewardConfig:
          type: object
        freeItem:
          type: string
        percentOff:
          type: integer
        percentTarget:
          type: string
          enum:
          - bill
          - item
        percentItem:
          type: string
        amountOff:
          type: integer
        amountTarget:
          type: string
          enum:
          - visit
          - item
        amountItem:
          type: string
    RewardSummary:
      type: object
      required:
      - rewardThresholdPoints
      - rewardType
      - rewardLabel
      - rewardConfig
      - availableRewards
      properties:
        rewardThresholdPoints:
          type: number
        rewardType:
          type:
          - string
          - 'null'
        rewardLabel:
          type: string
        rewardConfig:
          type: object
        availableRewards:
          type: integer
    AddPointsResponse:
      allOf:
      - type: object
        required:
        - storeId
        - pointsAdded
        - newPoints
        properties:
          storeId:
            type: string
          pointsAdded:
            type: integer
          newPoints:
            type: integer
      - $ref: '#/components/schemas/CustomerIdentityFields'
    AddExtendedResponse:
      allOf:
      - $ref: '#/components/schemas/AddPointsResponse'
      - type: object
        required:
        - rewardEligible
        - reward
        properties:
          rewardEligible:
            type: boolean
          reward:
            $ref: '#/components/schemas/RewardSummary'
    ReadPointsResponse:
      allOf:
      - type: object
        required:
        - storeId
        - points
        - walletExists
        properties:
          storeId:
            type: string
          points:
            type: integer
          walletExists:
            type: boolean
      - $ref: '#/components/schemas/CustomerIdentityFields'
    ReadExtendedResponse:
      allOf:
      - $ref: '#/components/schemas/ReadPointsResponse'
      - type: object
        required:
        - reward
        properties:
          reward:
            $ref: '#/components/schemas/RewardSummary'
    RedeemResponse:
      allOf:
      - type: object
        required:
        - storeId
        - txId
        - pointsRedeemed
        - newPoints
        - rewardLabel
        - reward
        properties:
          storeId:
            type: string
          txId:
            type: string
          pointsRedeemed:
            type: number
          newPoints:
            type: number
          rewardLabel:
            type: string
          billAmount:
            type:
            - number
            - 'null'
          discountAmount:
            type:
            - number
            - 'null'
          finalBillAmount:
            type:
            - number
            - 'null'
          reward:
            $ref: '#/components/schemas/RewardSummary'
      - $ref: '#/components/schemas/CustomerIdentityFields'
    ErrorResponse:
      type: object
      properties:
        ok:
          type: boolean
          const: false
        error:
          type: string
        message:
          type: string
