openapi: 3.0.3
info:
  title: Smartbis API v2
  version: 1.9.0
  description: |
    Smartbis API v2 enables external systems to integrate directly with the Smartbis Loyalty Marketing platform,
    allowing businesses to automate customer engagement, register sales, validate coupons, and interact with the
    loyalty rewards engine in real time.

    The API is designed for secure server-to-server integrations and uses JSON communication with Bearer Token authentication.

    Base URL
    https://api.smartbis.com/v2/

    Authentication

    Step 1 — Generate Access Token  
    POST /auth/token

    Provide your `api_key` and `api_secret` to obtain an `access_token`.

    Operator credentials and permissions

    Each API Key identifies one Smartbis user. The administrator API Key operates in the main operation. For a
    registered store, use the API Key of an operator linked to that store. The operator may authenticate with their
    own Secret Key or with the administrator's Secret Key. Using the
    administrator's Secret Key does not grant administrator privileges: authorization always follows the user in the
    API Key and the same store permissions configured in the Smartbis app.

    • Sales permission: register sales, list related transactions, and refund the operator's own sales
    • Voucher validation permission: list, validate, and perform the existing manual voucher redemption for the linked store
    • Customer registration permission: create and update customers for the linked store
    • Customer visibility: according to the store settings, an operator may see all company customers or only
      customers related to that operator or registered by an authorized store

    Multi-store accounts

    For operators, the API derives the store from their permissions. To process a sale in another store, use
    the API Key of an operator authorized to register sales in that store and generate a separate Bearer token. The
    administrator API Key registers sales in the administrator's main operation; it does not select or distribute sales
    among registered stores. `POST /sales` does not accept `store_id` or another override field/header. If an operator is linked to more than one store for the same operation, the API returns
    `409 Conflict` instead of selecting a store arbitrarily.

    Step 2 — Use Bearer Token

    Include the token in every request header:

    Authorization: Bearer {access_token}

    Request Format

    • All requests must use `application/json`  
    • All responses are returned in JSON  
    • UTF-8 encoding is used throughout the API

    Error handling

    • `400 Bad Request`: malformed JSON or invalid request body
    • `401 Unauthorized`: missing, invalid, or expired credentials
    • `403 Forbidden`: authenticated user without the required permission
    • `404 Not Found`: resource not found within the user's authorized scope
    • `409 Conflict`: conflicting state or ambiguous store configuration
    • `415 Unsupported Media Type`: a request body was not sent as `application/json`
    • `422 Unprocessable Entity`: field or business-rule validation failed
    • `500 Internal Server Error`: the operation could not be completed safely

    Core Features

    • Customer Management  
    • Direct Referral Visibility
    • Sales Registration (loyalty engine trigger)  
    • Customer Transaction History and Refunds
    • Rewards access and inventory management  
    • Categories management  
    • Voucher validation
    • Participant plans and subscription access management
    • Dependents, inherited eligibility, and active-life reconciliation

    Write safety and authorization

    Every write operation requires `Idempotency-Key` (8–128 characters). Reusing the same key with the same request
    returns the stored response; reusing it with another payload returns `409 Conflict`. Token responses expose the
    effective scopes derived from the administrator role or the operator permissions already configured in Smartbis.

    Typical Integration Flow

    1. Generate an access token using the API Key of the administrator or operator who will perform the request
    2. Create or locate a customer  
    3. Register a sale using `sale_amount`  
    4. Smartbis processes loyalty rewards automatically
    5. List the customer's transactions to obtain a `transaction_id` when a refund is needed
    6. Refund the original transaction using its `transaction_id`
servers:
  - url: https://api.smartbis.com/v2
tags:
  - name: Auth
  - name: Customers
  - name: Referrals
  - name: Partners
  - name: Register Sales
  - name: Transactions
  - name: Coupons
  - name: Categories
  - name: Vouchers
  - name: Plans
  - name: Subscriptions
  - name: Dependents
  - name: Eligibility
  - name: Webhooks
  - name: Providers
  - name: Access Links
paths:
  /auth/token:
    post:
      tags:
        - Auth
      summary: Generate Token
      operationId: postGenerateToken
      description: |
        Generates a cryptographically signed, opaque Bearer token used by subsequent requests. The API Key determines the authenticated
        user. An operator may use their own Secret Key or the administrator's Secret Key, but the resulting token
        keeps the identity and permissions of the operator in the API Key.

        An active operator must have at least one API permission in an active store. For multi-store integrations,
        generate and keep a separate token for each operator/store context. Tokens expire after 24 hours.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                api_key:
                  type: string
                  example: '{{api_key}}'
                api_secret:
                  type: string
                  example: '{{api_secret}}'
              required:
                - api_key
                - api_secret
            example:
              api_key: '{{api_key}}'
              api_secret: '{{api_secret}}'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  code:
                    type: integer
                    example: 200
                  status:
                    type: string
                    example: success
                  data:
                    type: object
                    properties:
                      access_token:
                        type: string
                      token_type:
                        type: string
                        example: Bearer
                      expires_in:
                        type: integer
                        example: 86400
                      scopes:
                        type: array
                        description: Effective authorization scopes derived from the authenticated user's current Smartbis permissions.
                        items: {type: string}
                        example: ['customers:read', 'customers:write']
                    required:
                      - access_token
                      - token_type
                      - expires_in
                      - scopes
                required:
                  - code
                  - status
                  - data
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Token could not be generated safely.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /partners:
    get:
      tags: [Partners]
      summary: List partners
      operationId: getPartners
      description: |
        Lists the authenticated company's own partners. The administrator's master location, marketplace locations,
        and automatically generated integration locations are excluded. Both active and inactive partners are
        returned unless the optional `active` filter is provided. Administrator access is required.
      security: [{bearerAuth: []}]
      parameters:
        - name: page
          in: query
          required: false
          schema: {type: integer, minimum: 1, default: 1}
        - name: per_page
          in: query
          required: false
          schema: {type: integer, minimum: 1, maximum: 200, default: 50}
        - name: active
          in: query
          required: false
          description: Filters partners by active status. Accepts `true`, `false`, `1`, or `0`.
          schema: {type: boolean}
      responses:
        '200':
          description: Partners returned successfully.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/PartnerListResponse'}
        '401':
          description: Unauthorized.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Error'}
        '403':
          description: Administrator access is required.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Error'}
        '422':
          description: Invalid active filter.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Error'}
    post:
      tags: [Partners]
      summary: Create a partner
      operationId: postPartner
      description: |
        Creates and immediately activates an own partner using the same registration rules as the Smartbis panel.
        The selected segment must be active and belong to the authenticated company. Creation respects the company's
        plan: MicroClub does not allow own partners, and MiniClub allows at most five active partners. Free accounts
        retain the existing unrestricted panel behavior.

        By default, the partner inherits the administrator location's conversion. Set
        `uses_administrator_conversion` to `false` and provide `conversion` to configure a local value. For cashback
        programs, `conversion` is the user-facing percentage (for example, `5` means 5% and is stored as `0.05`). For
        point programs, it is the number of points corresponding to one monetary unit. Operator permissions,
        financial limits, integration credentials, and image uploads remain panel-only. Administrator access is required.
      security: [{bearerAuth: []}]
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/CreatePartnerRequest'}
            examples:
              inheritedConversion:
                summary: Create a partner using the administrator conversion
                value:
                  name: Downtown Store
                  description: Main downtown service location.
                  segment_id: 7
                  phone: '+551130000000'
                  whatsapp: '+5511999999999'
                  email: store@example.com
                  uses_administrator_conversion: true
              customCashback:
                summary: Create a partner with a custom cashback percentage
                value:
                  name: North Store
                  segment_id: 7
                  phone: '+551130000001'
                  whatsapp: '+5511999999998'
                  uses_administrator_conversion: false
                  conversion: 5
                  website_enabled: true
                  website: https://store.example.com
      responses:
        '201':
          description: Partner created successfully.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/PartnerResponse'}
        '401':
          description: Unauthorized.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Error'}
        '403':
          description: Administrator access is required or the current plan does not allow own partners.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Error'}
        '404':
          description: Active segment not found in the authenticated company.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Error'}
        '409':
          description: Active partner limit reached or company state changed concurrently.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Error'}
        '422':
          description: Invalid field, value, conversion configuration, or idempotency key.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Error'}
        '500':
          description: Partner could not be created safely.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Error'}

  /partners/{partner_id}:
    parameters:
      - name: partner_id
        in: path
        required: true
        schema: {type: integer, minimum: 1}
    get:
      tags: [Partners]
      summary: Get partner details
      operationId: getPartner
      description: |
        Returns a partner belonging to the authenticated company. Internal integration settings, API credentials,
        operator permissions, financial configuration, and the administrator's master location are never exposed.
        Administrator access is required.
      security: [{bearerAuth: []}]
      responses:
        '200':
          description: Partner returned successfully.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/PartnerResponse'}
        '401':
          description: Unauthorized.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Error'}
        '403':
          description: Administrator access is required.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Error'}
        '404':
          description: Partner not found in the authenticated company.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Error'}
    patch:
      tags: [Partners]
      summary: Update, activate, or deactivate a partner
      operationId: patchPartner
      description: |
        Partially updates an own partner. Omitted fields remain unchanged. Send `active: true` to activate the partner
        or `active: false` to deactivate it; no separate status endpoint exists. Activation respects the company's
        plan and requires at least one operator already linked to the partner. As in the Smartbis panel, deactivation
        removes all operator links for sales registration, voucher validation, and customer registration. Operators
        must be reassigned in the panel before that partner can subsequently be reactivated.

        Updating the local conversion or changing between local and administrator conversion atomically recalculates
        the redemption points of the partner's value-based coupons using the existing Smartbis calculation. For
        cashback programs, `conversion` is the user-facing percentage. Operator permissions, financial limits,
        integration credentials, and image uploads cannot be changed through this endpoint. Administrator access is required.
      security: [{bearerAuth: []}]
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/UpdatePartnerRequest'}
            examples:
              profile:
                value:
                  name: Updated Store
                  phone: '+551130000002'
                  email: updated-store@example.com
              customConversion:
                value:
                  uses_administrator_conversion: false
                  conversion: 7.5
              inheritConversion:
                value:
                  uses_administrator_conversion: true
              deactivate:
                value:
                  active: false
              activate:
                value:
                  active: true
      responses:
        '200':
          description: Partner updated successfully.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/PartnerResponse'}
        '401':
          description: Unauthorized.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Error'}
        '403':
          description: Administrator access is required or the current plan does not allow own partners.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Error'}
        '404':
          description: Partner or active segment not found in the authenticated company.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Error'}
        '409':
          description: Activation limit reached, no operator is linked, or the administrator conversion is unavailable.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Error'}
        '422':
          description: Invalid field, value, conversion configuration, or idempotency key.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Error'}
        '500':
          description: Partner or related coupon points could not be updated safely.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Error'}

  /customers/{customer_id}/dependents:
    parameters:
      - name: customer_id
        in: path
        required: true
        schema: {type: integer}
    get:
      tags: [Dependents]
      summary: List a holder's dependents
      operationId: getCustomerDependents
      description: Returns dependents belonging to the authenticated company. Eligibility is inherited from the holder. Administrator scope is required.
      security: [{bearerAuth: []}]
      responses:
        '200':
          description: Dependents returned successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  code: {type: integer, example: 200}
                  status: {type: string, example: success}
                  data:
                    type: object
                    properties:
                      total: {type: integer}
                      items: {type: array, items: {$ref: '#/components/schemas/Dependent'}}
        '403': {description: 'Missing dependents:read scope', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}
        '404': {description: Holder not found, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}
    post:
      tags: [Dependents]
      summary: Create a dependent
      operationId: postCustomerDependent
      description: Creates a dependent linked to the holder, respecting company isolation and the holder plan limit. Administrator scope is required.
      security: [{bearerAuth: []}]
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/DependentWrite'}
      responses:
        '201': {description: Dependent created, content: {application/json: {schema: {$ref: '#/components/schemas/DependentResponse'}}}}
        '409': {description: Duplicate document or plan limit reached, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}
        '422': {description: Invalid field or idempotency key, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}
  /dependents/{dependent_id}:
    parameters:
      - name: dependent_id
        in: path
        required: true
        schema: {type: integer}
    get:
      tags: [Dependents]
      summary: Get a dependent and individual eligibility
      operationId: getDependent
      security: [{bearerAuth: []}]
      responses:
        '200': {description: Dependent returned, content: {application/json: {schema: {$ref: '#/components/schemas/DependentResponse'}}}}
        '404': {description: Dependent not found in the authenticated company, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}
    patch:
      tags: [Dependents]
      summary: Update a dependent
      operationId: patchDependent
      description: |
        Partially updates the dependent fields supported by the existing Smartbis data model. Omitted fields remain
        unchanged. Send an empty string to clear the optional phone, birth date, or identity document.

        The holder and public document are immutable through this endpoint. Dependents do not have an independent
        active status; their eligibility is inherited from the holder. This endpoint does not delete dependents.
        Administrator scope is required.
      security: [{bearerAuth: []}]
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/DependentUpdate'}
      responses:
        '200': {description: Dependent updated, content: {application/json: {schema: {$ref: '#/components/schemas/DependentResponse'}}}}
        '404': {description: Dependent not found in the authenticated company, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}
        '422': {description: Invalid field or idempotency key, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}
  /eligibility/reconciliation:
    get:
      tags: [Eligibility]
      summary: Reconcile active lives by plan and period
      operationId: getEligibilityReconciliation
      description: Returns active holders and dependents. Dependents inherit the holder's eligibility. Clinical data is never returned.
      security: [{bearerAuth: []}]
      parameters:
        - {name: plan_id, in: query, required: true, schema: {type: integer, minimum: 1}}
        - {name: from, in: query, required: false, schema: {type: string, format: date}}
        - {name: to, in: query, required: false, schema: {type: string, format: date}}
        - {name: page, in: query, required: false, schema: {type: integer, minimum: 1, default: 1}}
        - {name: per_page, in: query, required: false, schema: {type: integer, minimum: 1, maximum: 500, default: 100}}
      responses:
        '200': {description: Active lives returned, content: {application/json: {schema: {$ref: '#/components/schemas/ReconciliationResponse'}}}}
        '422': {description: Invalid plan or period, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}
  /plans:
    get:
      tags:
        - Plans
      summary: List participant plans
      operationId: getPlans
      description: Lists the participant plans configured by the authenticated company. Administrator access is required.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Plans returned successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlanListResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Administrator access is required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /plans/{plan_id}:
    get:
      tags:
        - Plans
      summary: Get participant plan
      operationId: getPlan
      security:
        - bearerAuth: []
      parameters:
        - name: plan_id
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: Plan returned successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  code:
                    type: integer
                    example: 200
                  status:
                    type: string
                    example: success
                  data:
                    type: object
                    properties:
                      plan:
                        $ref: '#/components/schemas/Plan'
        '404':
          description: Plan not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /subscriptions:
    get:
      tags:
        - Subscriptions
      summary: List participant subscriptions
      operationId: getSubscriptions
      description: Lists participant plan access records. By default, customers without a subscription history are omitted. Administrator access is required.
      security:
        - bearerAuth: []
      parameters:
        - name: status
          in: query
          required: false
          schema:
            type: string
            enum: [active, past_due, expired, cancelled, none]
        - name: page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: per_page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
      responses:
        '200':
          description: Subscriptions returned successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubscriptionListResponse'
        '403':
          description: Administrator access is required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /subscriptions/{customer_id}:
    get:
      tags:
        - Subscriptions
      summary: Get a participant subscription
      operationId: getSubscription
      security:
        - bearerAuth: []
      parameters:
        - name: customer_id
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: Subscription returned successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubscriptionResponse'
        '404':
          description: Customer not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    patch:
      tags:
        - Subscriptions
      summary: Manage participant plan access
      operationId: patchSubscription
      description: |
        Activates, marks past due, cancels, or clears a participant's plan access in Smartbis. This endpoint manages
        entitlement only. It does not create charges, refund payments, or cancel subscriptions in Stripe, Mercado Pago,
        or another payment gateway. Administrator access is required.
      security:
        - bearerAuth: []
      parameters:
        - name: customer_id
          in: path
          required: true
          schema:
            type: integer
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ManageSubscriptionRequest'
      responses:
        '200':
          description: Subscription access updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ManageSubscriptionResponse'
        '404':
          description: Customer or active plan not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Invalid subscription transition or field
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /customers/{customer_id}/transactions:
    get:
      tags:
        - Transactions
      summary: List Customer Transactions
      operationId: getCustomerTransactions
      description: |
        Returns the customer's paginated transaction history. The `transaction_id` field is the unique identifier
        of the transaction and must be used when requesting a refund. Only transactions where `refundable` is `true`
        are eligible for refund. Operators need permission to register sales and receive only transactions registered by their
        own operator identity. Administrators can view the company-wide history.

        `wallet_balance` is calculated from the customer's ledger for the authenticated company instead of using the
        cached customer value. `balance_mode` indicates whether the company uses a global wallet or balances separated
        by partner. In partner mode, administrators receive every partner balance, while operators receive balances
        only for partners where they have sales-registration permission. This endpoint is the canonical statement;
        no separate wallet-statement endpoint exists.
      security:
        - bearerAuth: []
      parameters:
        - name: customer_id
          in: path
          required: true
          description: Smartbis customer identifier.
          schema:
            type: integer
            example: 12345
        - name: page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            default: 1
            example: 1
        - name: per_page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
            example: 50
      responses:
        '200':
          description: Customer transactions returned successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransactionListResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: The authenticated operator does not have permission to register sales.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Customer not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /customers/{customer_id}/referrals:
    get:
      tags:
        - Referrals
      summary: List direct customer referrals
      operationId: getCustomerReferrals
      description: |
        Returns only customers directly referred by the selected customer. The endpoint does not expand a
        multilevel referral network and does not create, recalculate, or reverse referral rewards. Both the selected
        customer and returned referrals follow the authenticated user's company and customer-visibility rules.
        `wallet_balance` is calculated from each referred customer's ledger for the authenticated company.

        A direct relationship may come from a valid participant invitation or, when both related company settings are
        enabled, from customer creation by an operator. Relationship assignment is separate from reward issuance.
        Referral rewards remain controlled by the existing participant confirmation process and are recorded as normal
        ledger transactions. Use `GET /customers/{customer_id}/transactions` for the existing statement; no separate
        referral statement or referral-reward reversal endpoint exists.
      security:
        - bearerAuth: []
      parameters:
        - name: customer_id
          in: path
          required: true
          description: Smartbis identifier of the referring customer.
          schema:
            type: integer
            example: 12345
        - name: page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: per_page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
      responses:
        '200':
          description: Direct referrals returned successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReferralListResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: The authenticated operator has no operational customer permission.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Customer not found within the authorized scope
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /customers:
    get:
      tags:
        - Customers
      summary: List or filter customers
      operationId: getListCustomers
      description: |
        Returns customers with optional pagination, public document, phone, or customer ID filters. The same
        visibility rule is applied to lists and direct filters. Depending on the customer visibility setting of an
        authorized store, the operator may view all company customers. Otherwise, results are limited to customers
        related to that operator or registered by an authorized store. Both active and inactive customers may be
        returned; inspect the `active` field before using a customer in an operational flow.
      security:
        - bearerAuth: []
      parameters:
        - name: page
          in: query
          required: false
          schema:
            type: integer
            example: 1
        - name: per_page
          in: query
          required: false
          schema:
            type: integer
            example: 50
        - name: customer_id
          in: query
          required: false
          description: Smartbis internal customer identifier.
          schema:
            type: integer
            example: 12345
        - name: document
          in: query
          required: false
          description: Public document configured for the customer, such as CPF, CNPJ, passport, DNI, RUT, or NIF.
          schema:
            type: string
          examples:
            cpf:
              summary: CPF document
              value: '529.982.247-25'
            cnpj:
              summary: CNPJ document
              value: '11.222.333/0001-81'
            passport:
              summary: Passport document
              value: 'AB123456'
        - name: phone
          in: query
          required: false
          description: Customer phone number. It is normalized using the company's country configuration.
          schema:
            type: string
            example: '5511993999994'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomerListResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: The authenticated user has no operational store permission.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    post:
      tags:
        - Customers
      summary: Create Customer
      operationId: postCreateCustomer
      description: |
        Creates a customer with optional international address fields. `country` uses the ISO 3166-1 alpha-2 format.
        `city_code` is optional; for Brazilian addresses it represents the IBGE municipality code. Address creation
        does not depend on an external postal-code lookup. The `document` type follows the company's customer
        configuration and may be a CPF, CNPJ, passport, DNI, RUT, NIF, or another configured public document.
        Operators must have permission to register customers; the new customer is linked to that operator's
        authorized store.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  example: Test Customer
                document:
                  type: string
                  description: Public document configured by the company, such as CPF, CNPJ, passport, DNI, RUT, or NIF.
                  example: '529.982.247-25'
                phone:
                  type: string
                  example: '11993999994'
                email:
                  type: string
                  example: customer@example.com
                country:
                  type: string
                  minLength: 2
                  maxLength: 2
                  description: ISO 3166-1 alpha-2 country code.
                  example: BR
                postal_code:
                  type: string
                  example: 88032-005
                state:
                  type: string
                  example: SC
                city:
                  type: string
                  example: Florianopolis
                city_code:
                  type: string
                  description: Optional administrative city code. For Brazil, use the IBGE municipality code.
                  example: '4205407'
                street:
                  type: string
                  example: Rodovia Jose Carlos Daux
                number:
                  type: string
                  example: '4150'
                complement:
                  type: string
                  example: Suite 10
                district:
                  type: string
                  example: Saco Grande
                birth_date:
                  type: string
                  example: '1990-01-20'
                gender:
                  type: string
                  example: M
                password:
                  type: string
                  example: '123456'
              required:
                - name
                - phone
                - password
            examples:
              withCpfDocument:
                summary: Create a customer using a CPF document
                value:
                  name: Test Customer
                  document: '529.982.247-25'
                  phone: '11993999994'
                  email: customer@example.com
                  country: BR
                  password: '123456'
              withCnpjDocument:
                summary: Create a business customer using a CNPJ document
                value:
                  name: Test Coworking Ltda
                  document: '11.222.333/0001-81'
                  phone: '11993999994'
                  email: financeiro@example.com
                  country: BR
                  password: '123456'
              withPassportDocument:
                summary: Create a customer using a passport document
                value:
                  name: International Customer
                  document: 'AB123456'
                  phone: '2025550123'
                  email: international@example.com
                  country: US
                  password: '123456'
      responses:
        '201':
          description: Customer created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateCustomerResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: The operator does not have permission to create customers.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: Customer already exists, or the operator is linked to more than one store for customer creation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

        '500':
          description: Customer could not be created safely.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /customers/{customer_id}:
    patch:
      tags:
        - Customers
      summary: Update Customer
      operationId: patchCustomer
      description: |
        Partially updates an existing customer visible to the authenticated operator. Omitted fields remain unchanged.
        Send `active: true` to activate the customer or `active: false` to deactivate the customer. The authenticated
        operator cannot deactivate their own customer record, and reactivation respects the company's participant limit.

        Passwords, wallet balances, plans, and subscriptions cannot be changed through this endpoint. A public document
        can be added when the existing document is empty, but an existing document cannot be replaced. Use the dedicated
        subscription endpoint for plan access changes.
      security:
        - bearerAuth: []
      parameters:
        - name: customer_id
          in: path
          required: true
          description: Smartbis internal customer identifier.
          schema:
            type: integer
            minimum: 1
            example: 12345
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateCustomerRequest'
            examples:
              profile:
                summary: Update contact and address fields
                value:
                  name: Updated Customer
                  phone: '5511993999994'
                  email: updated@example.com
                  postal_code: 88032-005
                  city: Florianopolis
              deactivate:
                summary: Deactivate a customer
                value:
                  active: false
              activate:
                summary: Activate a customer
                value:
                  active: true
      responses:
        '200':
          description: Customer updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UpdateCustomerResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: The authenticated operator does not have permission to update customers.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Customer not found or not visible to the authenticated operator.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: The document, phone, or email is already in use.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Invalid field, value, status transition, or participant limit.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Customer could not be updated safely.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /sales:
    post:
      tags:
        - Register Sales
      summary: Register Sale
      operationId: postRegisterSale
      description: |
        Registers a new sale by `customer_id`, the customer's public `document`, or `phone`. The document type follows the
        company's customer configuration and may be a CPF, CNPJ, passport, DNI, RUT, NIF, or another configured
        public document. `sale_amount` must be greater than zero. To reverse a sale, list the customer's transactions
        and use the refund endpoint with the original `transaction_id`.

        **Authorization and store selection:** an operator's sale is processed by the single active store that grants
        sales permission to the operator in the API Key. For another store, use the API Key of an operator authorized
        there and generate a separate token. The administrator API Key registers a sale in the administrator's main
        operation and does not distribute it among registered stores.
        This endpoint does not accept `store_id` or any
        other request field/header to override the resolved store.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - $ref: '#/components/schemas/RegisterSaleByDocument'
                - $ref: '#/components/schemas/RegisterSaleByPhone'
                - $ref: '#/components/schemas/RegisterSaleByCustomerId'
            examples:
              byCpfDocument:
                summary: Register sale using a CPF document
                value:
                  document: '529.982.247-25'
                  sale_amount: 100.0
                  description: Sale registered via API
              byCnpjDocument:
                summary: Register sale using a CNPJ document
                value:
                  document: '11.222.333/0001-81'
                  sale_amount: 100.0
                  description: Sale registered via API
              byPassportDocument:
                summary: Register sale using a passport document
                value:
                  document: 'AB123456'
                  sale_amount: 100.0
                  description: Sale registered via API
              byPhone:
                summary: Register sale using a phone number
                value:
                  phone: '5511993999994'
                  sale_amount: 100.0
                  description: Sale registered via API
              byCustomerId:
                summary: Register sale using a customer ID
                value:
                  customer_id: 12345
                  sale_amount: 100.0
                  description: Sale registered via API
      responses:
        '201':
          description: Sale registered successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RegisterSaleResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: The operator does not have permission to register sales.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: The operator is linked to more than one active store for sales, or the administrator's main operation is not configured.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Customer not found or inactive.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /transactions/{transaction_id}/refund:
    post:
      tags:
        - Transactions
      summary: Refund Transaction
      operationId: postRefundTransaction
      description: |
        Refunds the full value generated by the original transaction. The refund amount cannot be supplied or changed
        by the integration. A transaction can only be refunded once, and only transactions returned with
        `refundable: true` are eligible. Operators need permission to register sales and can refund only transactions originally
        registered by their own operator identity in an authorized store.
      security:
        - bearerAuth: []
      parameters:
        - name: transaction_id
          in: path
          required: true
          description: Transaction identifier returned by the customer transaction history endpoint.
          schema:
            type: integer
            example: 123456
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RefundTransactionRequest'
            example:
              reason: Customer requested sale cancellation
      responses:
        '200':
          description: Transaction refunded successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RefundTransactionResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: The authenticated operator does not have permission to register sales.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Transaction not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: Transaction already refunded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Transaction is not refundable or request validation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /coupons:
    get:
      tags:
        - Coupons
      summary: List Coupons
      operationId: getListCoupons
      description: |
        Lists active rewards from stores where the operator has permission to register sales or validate vouchers.
        Administrators can list rewards across the company.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CouponListResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: The operator has neither sales nor voucher permission.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    post:
      tags:
        - Coupons
      summary: Create Coupon or Reward
      operationId: postCoupon
      description: |
        Creates and immediately publishes a coupon or reward using the same business fields as the Smartbis panel.
        The target `store_id` is required and must identify an active, non-marketplace store in the authenticated
        company. Category and geographic coordinates are inherited from that store. The operation respects the
        store's active coupon limit.

        When `automatic_calculation` is false, `redemption_points` is required and is stored as the final amount
        deducted on redemption. When it is true, Smartbis calculates the amount from `estimated_value`,
        `benefit_percentage`, and the effective conversion configured for the store. Images are managed in the panel
        and are not accepted by this JSON endpoint. Administrator access is required.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateCouponRequest'
            examples:
              manualPoints:
                summary: Publish a reward with a manually defined redemption cost
                value:
                  store_id: 10
                  description: Free Product
                  details: Redeem this reward at the selected store.
                  type: 3
                  estimated_value: 50
                  benefit_percentage: 100
                  redemption_points: 500
                  automatic_calculation: false
                  stock: 100
                  limit: 1
                  validity_days: 30
              automaticPoints:
                summary: Let Smartbis calculate the redemption cost
                value:
                  store_id: 10
                  description: 10 Percent Off
                  type: 2
                  estimated_value: 100
                  benefit_percentage: 10
                  automatic_calculation: true
                  stock: 50
                  limit: 1
                  validity_days: 15
              customRedirect:
                summary: Publish a reward that redirects to an external HTTPS destination
                value:
                  store_id: 10
                  description: Online Discount
                  type: 2
                  estimated_value: 20
                  benefit_percentage: 10
                  redemption_points: 200
                  automatic_calculation: false
                  stock: 100
                  limit: 1
                  validity_days: 30
                  custom_redirect_enabled: true
                  custom_redirect_url: https://shop.example.com/redeem
                  custom_code: SAVE10
      responses:
        '201':
          description: Coupon created and published successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateCouponResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Administrator access is required.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Store not found in the authenticated company.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: Store coupon limit reached.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Invalid field, value, calculation mode, redirect configuration, or idempotency key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Coupon could not be created safely.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /coupons/{coupon_id}:
    patch:
      tags:
        - Coupons
      summary: Update Coupon or Reward
      operationId: patchCoupon
      description: |
        Partially updates an existing coupon or reward. Send `active: true` to activate it or `active: false` to
        deactivate it. Reactivation respects the active coupon limit configured for the original store. The target
        store and inherited category cannot be changed.

        `automatic_calculation` is an update instruction rather than persisted state. Send it as `true` to recalculate
        the redemption cost from the resulting estimated value, benefit percentage, and current effective store
        conversion. Otherwise the existing redemption cost remains unchanged unless `redemption_points` is supplied.
        Do not send both automatic calculation and redemption points. Stock is intentionally excluded; use the
        dedicated stock endpoint. Administrator access is required.
      security:
        - bearerAuth: []
      parameters:
        - name: coupon_id
          in: path
          required: true
          schema: {type: integer, minimum: 1}
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateCouponRequest'
            examples:
              content:
                summary: Update reward content and redemption rules
                value:
                  description: Updated Reward
                  details: Updated redemption instructions.
                  estimated_value: 75
                  benefit_percentage: 100
                  redemption_points: 750
                  limit: 2
                  validity_days: 45
              recalculate:
                summary: Recalculate the redemption cost using the store conversion
                value:
                  estimated_value: 100
                  benefit_percentage: 20
                  automatic_calculation: true
              deactivate:
                value:
                  active: false
              activate:
                value:
                  active: true
      responses:
        '200':
          description: Coupon updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UpdateCouponResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Administrator access is required.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Coupon not found in the authenticated company.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: Coupon store inactive, concurrent state change, or active coupon limit reached.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Invalid or unsupported field, value, calculation mode, redirect configuration, or idempotency key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Coupon could not be updated safely.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /coupons/{coupon_id}/stock:
    patch:
      tags:
        - Coupons
      summary: Update Reward Stock
      operationId: patchUpdateRewardStock
      description: Administrator-only operation.
      security:
        - bearerAuth: []
      parameters:
        - name: coupon_id
          in: path
          required: true
          schema:
            type: integer
            minimum: 1
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                stock:
                  type: integer
                  minimum: 0
                  example: 80
              required:
                - stock
            example:
              stock: 80
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Administrator access is required.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Reward not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

        '500':
          description: Reward stock could not be updated safely.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    put:
      tags:
        - Coupons
      summary: Replace Reward Stock
      operationId: putUpdateRewardStock
      description: Administrator-only operation. This method has the same behavior as PATCH for the `stock` field.
      security:
        - bearerAuth: []
      parameters:
        - name: coupon_id
          in: path
          required: true
          schema:
            type: integer
            minimum: 1
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                stock:
                  type: integer
                  minimum: 0
                  example: 80
              required:
                - stock
            example:
              stock: 80
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Administrator access is required.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Reward not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

        '500':
          description: Reward stock could not be updated safely.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /categories:
    get:
      tags:
        - Categories
      summary: List Categories
      operationId: getListCategories
      description: Requires permission to register sales, permission to validate vouchers, or administrator access.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: The operator has neither sales nor voucher permission.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    post:
      tags:
        - Categories
      summary: Create Category
      operationId: postCreateCategory
      description: Administrator-only operation.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                description:
                  type: string
                  example: Food & Beverage
              required:
                - description
            example:
              description: Food & Beverage
      responses:
        '201':
          description: Category created successfully
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Administrator access is required.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

        '500':
          description: Category could not be created safely.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /categories/{category_id}:
    patch:
      tags:
        - Categories
      summary: Update Category
      operationId: patchCategory
      description: |
        Partially updates a category. Send `active: true` to activate it or `active: false` to deactivate it.
        Deactivation is logical and records the status change time; it does not delete the category or rewrite existing
        reward and partner references. Administrator access is required.
      security:
        - bearerAuth: []
      parameters:
        - name: category_id
          in: path
          required: true
          schema:
            type: integer
            minimum: 1
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateCategoryRequest'
            examples:
              rename:
                value:
                  description: Food & Beverage
              deactivate:
                value:
                  active: false
              activate:
                value:
                  active: true
      responses:
        '200':
          description: Category updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CategoryResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Administrator access is required.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Category not found in the authenticated company.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Invalid field, value, or idempotency key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Category could not be updated safely.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /vouchers/manual-redemptions:
    post:
      tags:
        - Vouchers
      summary: Perform Manual Voucher Redemption
      operationId: postManualVoucherRedemption
      description: |
        Performs the existing Smartbis manual redemption flow. This is not a generic voucher issuance endpoint.
        Smartbis verifies that manual redemption is enabled, resolves the store from the authenticated operator,
        validates the active customer and available balance, creates the voucher, records the matching negative
        `Resgate` transaction, updates the customer's cached balance, and commits all changes atomically.

        When the company uses per-partner balances, sufficiency is checked in the resolved store. Otherwise the global
        customer balance is used. Point-based programs accept whole-number amounts; cashback programs accept two
        decimal places. Voucher validity and minimum-order rules come from the company configuration.

        The voucher is created as already completed and validated by the operator, exactly like the manual operation in
        the panel. The operation is irreversible: an issued manual-redemption voucher cannot be edited, cancelled, or
        reversed through the API. The operator must have voucher permission for exactly one store; administrators use
        the configured main store.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ManualVoucherRedemptionRequest'
            example:
              customer_id: 12345
              amount: 100
      responses:
        '201':
          description: Manual voucher redemption completed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ManualVoucherRedemptionResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Manual redemption is disabled or the operator lacks voucher permission.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Active customer not found or not visible to the operator.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: Insufficient balance, ambiguous store permission, missing administrator store, or concurrent state change.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Invalid amount, customer, field, or idempotency key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: The voucher and balance transaction could not be committed atomically.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /vouchers:
    get:
      tags:
        - Vouchers
      summary: List Vouchers
      operationId: getListVouchers
      description: |
        Requires permission to validate vouchers. Operators receive only vouchers issued by stores where they have that
        permission. Administrators can list vouchers across the company.
      security:
        - bearerAuth: []
      parameters:
        - name: page
          in: query
          required: false
          schema:
            type: integer
            example: 1
        - name: per_page
          in: query
          required: false
          schema:
            type: integer
            example: 50
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: The operator does not have permission to validate vouchers.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /vouchers/{voucher_code}/validate:
    post:
      tags:
        - Vouchers
      summary: Validate Voucher
      operationId: postValidateVoucher
      description: |
        Requires voucher validation permission for the store that issued the voucher. An operator cannot validate their own
        non-cashback voucher. Reward vouchers and cashback vouchers are supported, subject to their expiration and
        the store's configured redemption limits. A voucher can be validated only once.
      security:
        - bearerAuth: []
      parameters:
        - name: voucher_code
          in: path
          required: true
          schema:
            type: string
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Missing store permission, or the operator attempted to validate their own voucher.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Voucher not found in an authorized store.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: Voucher already validated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Voucher validation could not be committed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /webhooks:
    get:
      tags: [Webhooks]
      summary: List webhook endpoints
      operationId: getWebhooks
      security: [{bearerAuth: []}]
      responses:
        '200': {description: Webhook endpoints returned, content: {application/json: {schema: {$ref: '#/components/schemas/WebhookListResponse'}}}}
        '403': {description: Administrator scope required, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}
    post:
      tags: [Webhooks]
      summary: Create a webhook endpoint
      operationId: postWebhook
      description: Creates an HTTPS endpoint. The signing secret is returned once and stored encrypted by Smartbis.
      security: [{bearerAuth: []}]
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/WebhookCreateRequest'}
      responses:
        '201': {description: Webhook created, content: {application/json: {schema: {$ref: '#/components/schemas/WebhookCreateResponse'}}}}
        '422': {description: Invalid URL or event, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}
  /webhooks/deliveries:
    get:
      tags: [Webhooks]
      summary: List webhook delivery attempts
      operationId: getWebhookDeliveries
      security: [{bearerAuth: []}]
      parameters:
        - {name: page, in: query, schema: {type: integer, minimum: 1, default: 1}}
        - {name: per_page, in: query, schema: {type: integer, minimum: 1, maximum: 200, default: 50}}
      responses:
        '200': {description: Delivery audit returned, content: {application/json: {schema: {$ref: '#/components/schemas/WebhookDeliveryListResponse'}}}}
  /webhooks/{webhook_id}:
    patch:
      tags: [Webhooks]
      summary: Update, deactivate, or rotate a webhook endpoint
      operationId: patchWebhook
      security: [{bearerAuth: []}]
      parameters:
        - {name: webhook_id, in: path, required: true, schema: {type: integer, minimum: 1}}
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                url: {type: string, format: uri, pattern: '^https://'}
                events: {type: array, minItems: 1, uniqueItems: true, items: {type: string, enum: [subscription.activated, subscription.past_due, subscription.expired, subscription.cancelled]}}
                active: {type: boolean}
                rotate_secret: {type: boolean}
      responses:
        '200': {description: Webhook updated; signing_secret is returned only when rotated, content: {application/json: {schema: {$ref: '#/components/schemas/WebhookCreateResponse'}}}}
        '404': {description: Webhook not found, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}
  /eligibility/history:
    get:
      tags: [Eligibility]
      summary: List eligibility change history
      operationId: getEligibilityHistory
      security: [{bearerAuth: []}]
      parameters:
        - {name: beneficiary_type, in: query, schema: {type: string, enum: [holder, dependent]}}
        - {name: beneficiary_id, in: query, schema: {type: integer, minimum: 1}}
        - {name: page, in: query, schema: {type: integer, minimum: 1, default: 1}}
        - {name: per_page, in: query, schema: {type: integer, minimum: 1, maximum: 200, default: 50}}
      responses:
        '200': {description: Eligibility history returned, content: {application/json: {schema: {$ref: '#/components/schemas/EligibilityHistoryListResponse'}}}}
  /beneficiaries/{beneficiary_type}/{beneficiary_id}/providers/{provider_key}:
    parameters:
      - {name: beneficiary_type, in: path, required: true, schema: {type: string, enum: [holder, dependent]}}
      - {name: beneficiary_id, in: path, required: true, schema: {type: integer, minimum: 1}}
      - {name: provider_key, in: path, required: true, schema: {type: string, pattern: '^[A-Za-z0-9._-]{2,100}$'}}
    get:
      tags: [Providers]
      summary: Get an external beneficiary identifier
      operationId: getBeneficiaryProvider
      security: [{bearerAuth: []}]
      responses:
        '200': {description: Provider link returned, content: {application/json: {schema: {$ref: '#/components/schemas/ProviderLinkResponse'}}}}
        '404': {description: Beneficiary or provider link not found, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}
    put:
      tags: [Providers]
      summary: Create or update an external beneficiary identifier
      operationId: putBeneficiaryProvider
      security: [{bearerAuth: []}]
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                external_beneficiary_id: {type: string, maxLength: 255}
              required: [external_beneficiary_id]
      responses:
        '200': {description: Provider link saved, content: {application/json: {schema: {$ref: '#/components/schemas/ProviderLinkResponse'}}}}
        '409': {description: External identifier conflict, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}
  /access-links:
    post:
      tags: [Access Links]
      summary: Issue a short-lived, single-use access token
      operationId: postAccessLink
      security: [{bearerAuth: []}]
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/AccessLinkRequest'}
      responses:
        '201': {description: Access token issued, content: {application/json: {schema: {$ref: '#/components/schemas/AccessLinkResponse'}}}}
        '409': {description: Provider link must be configured first, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}
  /access-links/consume:
    post:
      tags: [Access Links]
      summary: Consume a single-use access token
      operationId: postConsumeAccessLink
      description: Public exchange endpoint authenticated by the signed, expiring, single-use token itself.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                access_token: {type: string}
              required: [access_token]
      responses:
        '200': {description: Access token consumed, content: {application/json: {schema: {$ref: '#/components/schemas/AccessLinkConsumeResponse'}}}}
        '401': {description: Invalid or expired signature, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}
        '409': {description: Token already used or expired, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: opaque
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: Unique key for this logical write (8 to 128 letters, digits, dot, underscore, colon, or hyphen).
      schema:
        type: string
        minLength: 8
        maxLength: 128
        pattern: '^[A-Za-z0-9._:-]+$'
  schemas:
    Error:
      type: object
      properties:
        code:
          type: integer
          example: 403
        status:
          type: string
          example: error
        data:
          oneOf:
            - type: object
              additionalProperties: true
            - type: array
              items: {}
        message:
          type: string
          example: Forbidden
        request_id:
          type: string
          example: 879c2e5f32fe9a8bdd07845a
        error:
          type: object
          properties:
            type: {type: string, example: forbidden}
            message: {type: string, example: Forbidden}
      required:
        - code
        - status
        - data
        - message
    Partner:
      type: object
      properties:
        partner_id: {type: integer, example: 42}
        name: {type: string, example: Downtown Store}
        description: {type: string}
        segment_id: {type: integer, nullable: true, example: 7}
        segment_name: {type: string, nullable: true, example: Restaurants}
        phone: {type: string, example: '+551130000000'}
        whatsapp: {type: string, example: '+5511999999999'}
        email: {type: string, format: email, example: store@example.com}
        map_enabled: {type: integer, enum: [0, 1], example: 1}
        map_embed_url: {type: string, format: uri, nullable: true}
        website_enabled: {type: integer, enum: [0, 1], example: 1}
        website: {type: string, nullable: true, example: example.com}
        uses_administrator_conversion: {type: integer, enum: [0, 1], example: 1}
        conversion:
          type: number
          format: float
          nullable: true
          description: Stored local conversion factor; null when administrator conversion is inherited. Cashback percentages are normalized, so 5% is returned as 0.05.
          example: 0.05
        active: {type: integer, enum: [0, 1], example: 1}
        status_changed_at: {type: string, nullable: true, example: '2026-10-01 14:30:00'}
        created_at: {type: string, example: '2026-01-10 09:00:00'}
      required: [partner_id, name, description, map_enabled, website_enabled, uses_administrator_conversion, active, created_at]
    CreatePartnerRequest:
      type: object
      additionalProperties: false
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 25
          description: Letters, numbers, spaces, and ampersands only.
          example: Downtown Store
        description: {type: string, example: Main downtown service location.}
        segment_id: {type: integer, minimum: 1, example: 7}
        phone: {type: string, minLength: 1, maxLength: 300, example: '+551130000000'}
        whatsapp: {type: string, minLength: 1, maxLength: 20, example: '+5511999999999'}
        email: {type: string, format: email, maxLength: 200, example: store@example.com}
        map_enabled: {type: boolean, default: false}
        map_embed_url:
          type: string
          format: uri
          description: Google Maps embed URL beginning with `https://www.google.com/maps/embed?`.
        website_enabled: {type: boolean, default: false}
        website: {type: string, maxLength: 200, example: 'https://store.example.com'}
        uses_administrator_conversion:
          type: boolean
          default: true
          description: When true, the partner dynamically inherits the administrator location's conversion.
        conversion:
          type: number
          format: float
          exclusiveMinimum: true
          minimum: 0
          description: Required only for a custom conversion. In cashback programs this is a percentage.
      required: [name, segment_id, phone, whatsapp]
    UpdatePartnerRequest:
      type: object
      additionalProperties: false
      minProperties: 1
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 25
          description: Letters, numbers, spaces, and ampersands only.
        description: {type: string}
        segment_id: {type: integer, minimum: 1}
        phone: {type: string, minLength: 1, maxLength: 300}
        whatsapp: {type: string, minLength: 1, maxLength: 20}
        email: {type: string, format: email, maxLength: 200}
        map_enabled: {type: boolean}
        map_embed_url:
          type: string
          format: uri
          description: Google Maps embed URL beginning with `https://www.google.com/maps/embed?`.
        website_enabled: {type: boolean}
        website: {type: string, maxLength: 200}
        uses_administrator_conversion:
          type: boolean
          description: When true, the partner dynamically inherits the administrator location's conversion.
        conversion:
          type: number
          format: float
          exclusiveMinimum: true
          minimum: 0
          description: Custom conversion value. In cashback programs this is a percentage.
        active:
          type: boolean
          description: Activates or deactivates the partner. Deactivation removes the partner's operator links.
    PartnerResponse:
      type: object
      properties:
        code: {type: integer, example: 200}
        status: {type: string, example: success}
        data:
          type: object
          properties:
            partner: {$ref: '#/components/schemas/Partner'}
          required: [partner]
        request_id: {type: string}
      required: [code, status, data, request_id]
    PartnerListResponse:
      type: object
      properties:
        code: {type: integer, example: 200}
        status: {type: string, example: success}
        data:
          type: object
          properties:
            total: {type: integer, example: 2}
            page: {type: integer, example: 1}
            per_page: {type: integer, example: 50}
            total_pages: {type: integer, example: 1}
            items:
              type: array
              items: {$ref: '#/components/schemas/Partner'}
          required: [total, page, per_page, total_pages, items]
        request_id: {type: string}
      required: [code, status, data, request_id]
    DependentEligibility:
      type: object
      properties:
        status: {type: string, enum: [active, past_due, expired, cancelled, none]}
        inherited_from_holder: {type: boolean, example: true}
        holder_customer_id: {type: integer}
        status_changed_at: {type: string, nullable: true}
      required: [status, inherited_from_holder, holder_customer_id]
    Dependent:
      type: object
      properties:
        dependent_id: {type: integer}
        holder_customer_id: {type: integer}
        name: {type: string}
        document: {type: string}
        phone: {type: string}
        birth_date: {type: string, format: date, nullable: true}
        identity_document: {type: string}
        country: {type: string}
        eligibility: {$ref: '#/components/schemas/DependentEligibility'}
      required: [dependent_id, holder_customer_id, name, document, eligibility]
    DependentWrite:
      type: object
      properties:
        name: {type: string}
        document: {type: string}
        phone: {type: string}
        birth_date: {type: string, format: date}
        identity_document: {type: string}
        country: {type: string}
      required: [name, document]
    DependentUpdate:
      type: object
      additionalProperties: false
      minProperties: 1
      properties:
        name: {type: string, minLength: 3}
        phone:
          type: string
          description: Send an empty string to clear the optional phone.
        birth_date:
          type: string
          description: Valid YYYY-MM-DD date, or an empty string to clear it.
        identity_document: {type: string}
        country:
          type: string
          minLength: 2
          maxLength: 2
          description: ISO 3166-1 alpha-2 country code.
    DependentResponse:
      type: object
      properties:
        code: {type: integer}
        status: {type: string}
        request_id: {type: string}
        data:
          type: object
          properties:
            dependent: {$ref: '#/components/schemas/Dependent'}
      required: [code, status, data]
    ActiveLife:
      type: object
      properties:
        beneficiary_type: {type: string, enum: [holder, dependent]}
        beneficiary_id: {type: integer}
        holder_customer_id: {type: integer}
        name: {type: string}
        document: {type: string}
        plan_id: {type: integer}
        status: {type: string, enum: [active]}
        status_changed_at: {type: string, nullable: true}
      required: [beneficiary_type, beneficiary_id, holder_customer_id, name, plan_id, status]
    ReconciliationResponse:
      type: object
      properties:
        code: {type: integer, example: 200}
        status: {type: string, example: success}
        request_id: {type: string}
        data:
          type: object
          properties:
            total: {type: integer}
            page: {type: integer}
            per_page: {type: integer}
            total_pages: {type: integer}
            items: {type: array, items: {$ref: '#/components/schemas/ActiveLife'}}
      required: [code, status, data]
    WebhookEndpoint:
      type: object
      properties:
        webhook_id: {type: integer}
        url: {type: string, format: uri}
        events: {type: array, items: {type: string, enum: [subscription.activated, subscription.past_due, subscription.expired, subscription.cancelled]}}
        active: {type: boolean}
        created_at: {type: string}
        updated_at: {type: string}
      required: [webhook_id, url, events, active]
    WebhookCreateRequest:
      type: object
      properties:
        url: {type: string, format: uri, pattern: '^https://'}
        events: {type: array, minItems: 1, uniqueItems: true, items: {type: string, enum: [subscription.activated, subscription.past_due, subscription.expired, subscription.cancelled]}}
      required: [url, events]
    WebhookCreateResponse:
      type: object
      properties:
        code: {type: integer, example: 201}
        status: {type: string, example: success}
        data:
          allOf:
            - $ref: '#/components/schemas/WebhookEndpoint'
            - type: object
              properties:
                signing_secret: {type: string, description: Returned only once.}
      required: [code, status, data]
    WebhookListResponse:
      type: object
      properties:
        code: {type: integer}
        status: {type: string}
        data:
          type: object
          properties:
            total: {type: integer}
            items: {type: array, items: {$ref: '#/components/schemas/WebhookEndpoint'}}
      required: [code, status, data]
    WebhookDelivery:
      type: object
      properties:
        delivery_id: {type: integer}
        webhook_id: {type: integer}
        event_id: {type: string, format: uuid}
        event_type: {type: string}
        attempt_count: {type: integer}
        next_attempt_at: {type: string, nullable: true}
        delivered_at: {type: string, nullable: true}
        last_http_status: {type: integer, nullable: true}
        last_error: {type: string, nullable: true}
        created_at: {type: string}
    WebhookDeliveryListResponse:
      type: object
      properties:
        code: {type: integer}
        status: {type: string}
        data:
          type: object
          properties:
            total: {type: integer}
            page: {type: integer}
            per_page: {type: integer}
            total_pages: {type: integer}
            items: {type: array, items: {$ref: '#/components/schemas/WebhookDelivery'}}
    EligibilityHistory:
      type: object
      properties:
        history_id: {type: integer}
        beneficiary_type: {type: string, enum: [holder, dependent]}
        beneficiary_id: {type: integer}
        previous_status: {type: string}
        new_status: {type: string}
        reason: {type: string, nullable: true}
        external_reference: {type: string, nullable: true}
        operator_id: {type: integer}
        changed_at: {type: string}
    EligibilityHistoryListResponse:
      type: object
      properties:
        code: {type: integer}
        status: {type: string}
        data:
          type: object
          properties:
            total: {type: integer}
            page: {type: integer}
            per_page: {type: integer}
            total_pages: {type: integer}
            items: {type: array, items: {$ref: '#/components/schemas/EligibilityHistory'}}
    ProviderLinkResponse:
      type: object
      properties:
        code: {type: integer}
        status: {type: string}
        data:
          type: object
          properties:
            beneficiary_type: {type: string, enum: [holder, dependent]}
            beneficiary_id: {type: integer}
            provider_key: {type: string}
            external_beneficiary_id: {type: string}
      required: [code, status, data]
    AccessLinkRequest:
      type: object
      properties:
        beneficiary_type: {type: string, enum: [holder, dependent]}
        beneficiary_id: {type: integer, minimum: 1}
        provider_key: {type: string}
        expires_in: {type: integer, minimum: 60, maximum: 900, default: 300}
      required: [beneficiary_type, beneficiary_id, provider_key]
    AccessLinkResponse:
      type: object
      properties:
        code: {type: integer, example: 201}
        status: {type: string, example: success}
        data:
          type: object
          properties:
            access_token: {type: string}
            token_type: {type: string, enum: [one_time]}
            expires_in: {type: integer}
            expires_at: {type: string, format: date-time}
      required: [code, status, data]
    AccessLinkConsumeResponse:
      type: object
      properties:
        code: {type: integer, example: 200}
        status: {type: string, example: success}
        data:
          type: object
          properties:
            beneficiary_type: {type: string, enum: [holder, dependent]}
            beneficiary_id: {type: integer}
            provider_key: {type: string}
            external_beneficiary_id: {type: string, nullable: true}
            consumed_at: {type: string}
      required: [code, status, data]
    Plan:
      type: object
      properties:
        plan_id:
          type: integer
          example: 12
        name:
          type: string
          example: Telemedicina Individual
        description:
          type: string
        type:
          type: integer
        setup_fee:
          type: number
          format: float
          example: 0
        prices:
          type: object
          properties:
            monthly: {type: number, format: float}
            semiannual: {type: number, format: float}
            annual: {type: number, format: float}
            lifetime: {type: number, format: float}
        active:
          type: integer
          enum: [0, 1]
      required: [plan_id, name, type, setup_fee, prices, active]
    PlanListResponse:
      type: object
      properties:
        code: {type: integer, example: 200}
        status: {type: string, example: success}
        data:
          type: object
          properties:
            total: {type: integer}
            items:
              type: array
              items:
                $ref: '#/components/schemas/Plan'
      required: [code, status, data]
    Subscription:
      type: object
      properties:
        customer_id: {type: integer}
        customer_name: {type: string}
        document: {type: string}
        phone: {type: string}
        plan_id: {type: integer, nullable: true}
        plan_name: {type: string, nullable: true}
        status:
          type: string
          enum: [active, past_due, expired, cancelled, none]
        renewal_period_months:
          type: integer
          nullable: true
          enum: [0, 1, 6, 12]
        amount: {type: number, format: float, nullable: true}
        payment_method:
          type: string
          nullable: true
          enum: [pix, card, bank_slip]
        contract_until: {type: string, nullable: true, example: '2027-09-24'}
        status_changed_at: {type: string, nullable: true}
        customer_active: {type: integer, enum: [0, 1]}
      required: [customer_id, customer_name, status, customer_active]
    SubscriptionResponse:
      type: object
      properties:
        code: {type: integer, example: 200}
        status: {type: string, example: success}
        data:
          type: object
          properties:
            subscription:
              $ref: '#/components/schemas/Subscription'
      required: [code, status, data]
    SubscriptionListResponse:
      type: object
      properties:
        code: {type: integer, example: 200}
        status: {type: string, example: success}
        data:
          type: object
          properties:
            total: {type: integer}
            page: {type: integer}
            per_page: {type: integer}
            total_pages: {type: integer}
            items:
              type: array
              items:
                $ref: '#/components/schemas/Subscription'
      required: [code, status, data]
    ManageSubscriptionRequest:
      type: object
      description: plan_id and renewal_period_months are required when status is active. contract_until is also required for renewable plans and may be omitted for a lifetime plan (renewal_period_months = 0).
      properties:
        status:
          type: string
          enum: [active, past_due, cancelled, none]
        plan_id: {type: integer}
        renewal_period_months: {type: integer, enum: [0, 1, 6, 12]}
        amount: {type: number, format: float, minimum: 0}
        payment_method: {type: string, enum: [pix, card, bank_slip]}
        contract_until: {type: string, format: date}
        reason: {type: string, maxLength: 255}
        external_reference: {type: string, maxLength: 255}
      required: [status]
    ManageSubscriptionResponse:
      type: object
      properties:
        code: {type: integer, example: 200}
        status: {type: string, example: success}
        data:
          type: object
          properties:
            subscription:
              $ref: '#/components/schemas/Subscription'
            billing_managed: {type: boolean, example: false}
            event:
              type: object
              nullable: true
              description: Event queued atomically with the eligibility history when the status changes.
              properties:
                type:
                  type: string
                  enum: [subscription.activated, subscription.past_due, subscription.cancelled]
                delivery_status: {type: string, enum: [queued, no_subscribers]}
                queued_deliveries: {type: integer, minimum: 0}
        message:
          type: string
          example: Subscription access updated successfully; billing gateways were not changed
      required: [code, status, data, message]
    Customer:
      type: object
      properties:
        customer_id:
          type: integer
          example: 12345
        name:
          type: string
          example: Test Customer
        document:
          type: string
          description: Public document configured by the company, such as CPF, CNPJ, passport, DNI, RUT, or NIF.
          example: 529.982.247-25
        cpf:
          type: string
          deprecated: true
          description: Legacy alias of `document`, retained for backward compatibility.
          example: 529.982.247-25
        phone:
          type: string
          example: '5511993999994'
        email:
          type: string
          example: customer@example.com
        birth_date:
          type: string
          example: '1990-01-20'
        birthday:
          type: string
          deprecated: true
          description: Legacy alias of `birth_date`, retained for backward compatibility.
          example: '1990-01-20'
        gender:
          type: string
          example: M
        identity_document:
          type: string
          example: '1234567'
        country:
          type: string
          description: ISO 3166-1 alpha-2 country code.
          example: BR
        postal_code:
          type: string
          example: 88032-005
        state:
          type: string
          example: SC
        city:
          type: string
          example: Florianopolis
        city_code:
          type: string
          description: Optional administrative city code. For Brazil, this is the IBGE municipality code.
          example: '4205407'
        street:
          type: string
          example: Rodovia Jose Carlos Daux
        number:
          type: string
          example: '4150'
        complement:
          type: string
          example: Suite 10
        district:
          type: string
          example: Saco Grande
        wallet_balance:
          type: number
          format: float
          example: 0
        active:
          type: integer
          enum:
            - 0
            - 1
          example: 1
        created_at:
          type: string
          example: '2026-08-04 14:30:00'
      required:
        - customer_id
        - name
        - phone
        - country
        - postal_code
        - state
        - city
        - city_code
        - street
        - number
        - complement
        - district
        - wallet_balance
        - active
        - created_at
    CustomerListResponse:
      type: object
      properties:
        code:
          type: integer
          example: 200
        status:
          type: string
          example: success
        data:
          type: object
          properties:
            total:
              type: integer
              example: 1
            page:
              type: integer
              example: 1
            per_page:
              type: integer
              example: 50
            total_pages:
              type: integer
              example: 1
            items:
              type: array
              items:
                $ref: '#/components/schemas/Customer'
          required:
            - total
            - page
            - per_page
            - total_pages
            - items
      required:
        - code
        - status
        - data
    Referral:
      type: object
      properties:
        customer_id:
          type: integer
          example: 23456
        name:
          type: string
          example: Referred Customer
        phone:
          type: string
          example: '5511999999999'
        email:
          type: string
          example: customer@example.com
        wallet_balance:
          type: number
          format: float
          description: Current balance calculated from this customer's ledger for the authenticated company.
          example: 25.5
        active:
          type: integer
          enum: [0, 1]
          example: 1
        created_at:
          type: string
          description: Date and time in `YYYY-MM-DD HH:mm:ss` format.
          example: '2026-10-01 14:30:00'
      required: [customer_id, name, phone, email, wallet_balance, active, created_at]
    ReferralListResponse:
      type: object
      properties:
        code:
          type: integer
          example: 200
        status:
          type: string
          example: success
        data:
          type: object
          properties:
            total:
              type: integer
              example: 1
            page:
              type: integer
              example: 1
            per_page:
              type: integer
              example: 50
            total_pages:
              type: integer
              example: 1
            items:
              type: array
              items:
                $ref: '#/components/schemas/Referral'
          required: [total, page, per_page, total_pages, items]
      required: [code, status, data]
    CreateCustomerResponse:
      type: object
      properties:
        code:
          type: integer
          example: 201
        status:
          type: string
          example: success
        data:
          type: object
          properties:
            result:
              type: object
              additionalProperties: true
            customer:
              $ref: '#/components/schemas/Customer'
          required:
            - result
            - customer
        message:
          type: string
          example: Customer created successfully
      required:
        - code
        - status
        - data
        - message
    UpdateCustomerRequest:
      type: object
      additionalProperties: false
      minProperties: 1
      properties:
        name: {type: string, minLength: 1, example: Updated Customer}
        document:
          type: string
          description: May only be added when the existing customer document is empty.
          example: '529.982.247-25'
        cpf:
          type: string
          deprecated: true
          description: Legacy alias of `document`.
        phone: {type: string, example: '5511993999994'}
        email: {type: string, format: email, example: updated@example.com}
        country:
          type: string
          minLength: 2
          maxLength: 2
          example: BR
        postal_code: {type: string, example: 88032-005}
        state: {type: string, example: SC}
        city: {type: string, example: Florianopolis}
        city_code: {type: string, example: '4205407'}
        street: {type: string, example: Rodovia Jose Carlos Daux}
        number: {type: string, example: '4150'}
        complement: {type: string, example: Suite 10}
        district: {type: string, example: Saco Grande}
        birth_date: {type: string, format: date, example: '1990-01-20'}
        gender: {type: string, example: M}
        identity_document: {type: string, example: '1234567'}
        active:
          type: boolean
          description: Set to true to activate or false to deactivate the customer.
    UpdateCustomerResponse:
      type: object
      properties:
        code: {type: integer, example: 200}
        status: {type: string, example: success}
        data:
          type: object
          properties:
            customer:
              $ref: '#/components/schemas/Customer'
          required: [customer]
        message: {type: string, example: Customer updated successfully}
      required: [code, status, data, message]
    Category:
      type: object
      properties:
        category_id: {type: integer, example: 10}
        description: {type: string, maxLength: 25, example: Food & Beverage}
        active:
          type: integer
          enum: [0, 1]
          example: 1
        status_changed_at:
          type: string
          nullable: true
          description: Date and time in `YYYY-MM-DD HH:mm:ss` format when the active status last changed.
          example: '2026-10-01 14:30:00'
      required: [category_id, description, active]
    UpdateCategoryRequest:
      type: object
      additionalProperties: false
      minProperties: 1
      properties:
        description:
          type: string
          minLength: 1
          maxLength: 25
          example: Food & Beverage
        active:
          type: boolean
          description: Set to true to activate or false to deactivate the category.
    CategoryResponse:
      type: object
      properties:
        code: {type: integer, example: 200}
        status: {type: string, example: success}
        data:
          type: object
          properties:
            category: {$ref: '#/components/schemas/Category'}
          required: [category]
        message: {type: string, example: Category updated successfully}
      required: [code, status, data, message]
    Coupon:
      type: object
      properties:
        coupon_id: {type: integer, example: 123}
        store_id: {type: integer, example: 10}
        category_id: {type: integer, nullable: true, example: 5}
        description: {type: string, maxLength: 25, example: Free Product}
        details: {type: string, example: Redeem this reward at the selected store.}
        required_value: {type: number, format: float, minimum: 0, example: 500}
        stock: {type: number, minimum: 0, example: 100}
        type:
          type: integer
          minimum: 1
          maximum: 14
          description: Existing Smartbis reward type from 1 through 14.
          example: 3
        estimated_value: {type: number, format: float, minimum: 0, example: 50}
        benefit_percentage: {type: number, format: float, minimum: 0, maximum: 100, example: 100}
        validity_days: {type: integer, minimum: 0, example: 30}
        limit: {type: integer, minimum: 0, example: 1}
        custom_redirect_enabled:
          type: integer
          enum: [0, 1]
          example: 0
        custom_redirect_url: {type: string, format: uri, nullable: true}
        custom_code: {type: string, nullable: true, maxLength: 50}
        active:
          type: integer
          enum: [0, 1]
          example: 1
        status_changed_at: {type: string, nullable: true}
        created_at: {type: string, example: '2026-10-01 14:30:00'}
      required: [coupon_id, store_id, description, required_value, stock, type, estimated_value, benefit_percentage, validity_days, limit, custom_redirect_enabled, active, created_at]
    CreateCouponRequest:
      type: object
      additionalProperties: false
      properties:
        store_id: {type: integer, minimum: 1}
        description: {type: string, minLength: 1, maxLength: 25}
        details: {type: string}
        type:
          type: integer
          minimum: 1
          maximum: 14
          description: '1: Cashback; 2: Discount; 3: Product; 4: Free Shipping; 5: Gift; 6: Upgrade; 7: Early Access; 8: Extra Points; 9: Progressive Discount; 10: Product Discount; 11: First Purchase; 12: Referral; 13: Birthday; 14: Free Form.'
        estimated_value: {type: number, format: float, minimum: 0}
        benefit_percentage: {type: number, format: float, minimum: 0, maximum: 100}
        redemption_points:
          type: number
          format: float
          minimum: 0
          description: Required when automatic_calculation is false; ignored when it is true.
        automatic_calculation: {type: boolean, default: false}
        stock: {type: integer, minimum: 0}
        limit: {type: integer, minimum: 0, default: 0}
        validity_days: {type: integer, minimum: 0, default: 0}
        custom_redirect_enabled: {type: boolean, default: false}
        custom_redirect_url:
          type: string
          format: uri
          pattern: '^https://'
        custom_code: {type: string, maxLength: 50}
      required: [store_id, description, type, estimated_value, benefit_percentage, stock]
    CreateCouponResponse:
      type: object
      properties:
        code: {type: integer, example: 201}
        status: {type: string, example: success}
        data:
          type: object
          properties:
            coupon: {$ref: '#/components/schemas/Coupon'}
          required: [coupon]
        message: {type: string, example: Coupon created successfully}
      required: [code, status, data, message]
    UpdateCouponRequest:
      type: object
      additionalProperties: false
      minProperties: 1
      properties:
        description: {type: string, minLength: 1, maxLength: 25}
        details: {type: string}
        type:
          type: integer
          minimum: 1
          maximum: 14
          description: Existing Smartbis reward type from 1 through 14.
        estimated_value: {type: number, format: float, minimum: 0}
        benefit_percentage: {type: number, format: float, minimum: 0, maximum: 100}
        redemption_points:
          type: number
          format: float
          minimum: 0
          description: Explicit replacement for the redemption cost. Do not combine with automatic_calculation true.
        automatic_calculation:
          type: boolean
          description: Send true to recalculate redemption points for this update. False is not persisted as state.
        limit: {type: integer, minimum: 0}
        validity_days: {type: integer, minimum: 0}
        custom_redirect_enabled: {type: boolean}
        custom_redirect_url:
          type: string
          format: uri
          pattern: '^https://'
        custom_code: {type: string, maxLength: 50}
        active:
          type: boolean
          description: Set to true to activate or false to deactivate the coupon.
    UpdateCouponResponse:
      type: object
      properties:
        code: {type: integer, example: 200}
        status: {type: string, example: success}
        data:
          type: object
          properties:
            coupon: {$ref: '#/components/schemas/Coupon'}
          required: [coupon]
        message: {type: string, example: Coupon updated successfully}
      required: [code, status, data, message]
    CouponListResponse:
      type: object
      properties:
        code: {type: integer, example: 200}
        status: {type: string, example: success}
        data:
          type: object
          properties:
            total: {type: integer, minimum: 0}
            items:
              type: array
              items: {$ref: '#/components/schemas/Coupon'}
          required: [total, items]
      required: [code, status, data]
    ManualVoucherRedemptionRequest:
      type: object
      additionalProperties: false
      properties:
        customer_id:
          type: integer
          minimum: 1
          description: Active Smartbis customer identifier visible to the authenticated operator.
        amount:
          type: number
          format: float
          exclusiveMinimum: true
          minimum: 0
          description: Amount deducted from the customer's available points or cashback balance.
      required: [customer_id, amount]
    ManualVoucherRedemptionResponse:
      type: object
      properties:
        code: {type: integer, example: 201}
        status: {type: string, example: success}
        data:
          type: object
          properties:
            voucher:
              type: object
              additionalProperties: true
            transaction_id:
              type: integer
              description: Identifier of the negative Resgate transaction created in the customer ledger.
            wallet_balance:
              type: number
              format: float
              description: Updated global customer wallet balance.
            irreversible:
              type: boolean
              enum: [true]
              description: Confirms that this manual voucher redemption cannot be reversed through the API.
          required: [voucher, transaction_id, wallet_balance, irreversible]
        message: {type: string, example: Manual voucher redemption completed successfully}
      required: [code, status, data, message]
    RegisterSaleByDocument:
      type: object
      properties:
        document:
          type: string
          description: Public document configured by the company, such as CPF, CNPJ, passport, DNI, RUT, or NIF.
          example: '529.982.247-25'
        sale_amount:
          type: number
          format: float
          exclusiveMinimum: true
          minimum: 0
          description: Sale amount. Must be greater than zero; negative values cannot be used for refunds.
          example: 100.0
        description:
          type: string
          example: Sale registered via API
      required:
        - document
        - sale_amount
        - description
    RegisterSaleByPhone:
      type: object
      properties:
        phone:
          type: string
          description: Customer phone number. It is normalized using the company's country configuration.
          example: '5511993999994'
        sale_amount:
          type: number
          format: float
          exclusiveMinimum: true
          minimum: 0
          description: Sale amount. Must be greater than zero; negative values cannot be used for refunds.
          example: 100.0
        description:
          type: string
          example: Sale registered via API
      required:
        - phone
        - sale_amount
        - description
    RegisterSaleByCustomerId:
      type: object
      properties:
        customer_id:
          oneOf:
            - type: integer
              example: 12345
            - type: string
              example: '12345'
          description: Smartbis internal customer identifier.
        sale_amount:
          type: number
          format: float
          exclusiveMinimum: true
          minimum: 0
          description: Sale amount. Must be greater than zero; negative values cannot be used for refunds.
          example: 100.0
        description:
          type: string
          example: Sale registered via API
      required:
        - customer_id
        - sale_amount
        - description
    RegisterSaleResponse:
      type: object
      properties:
        code:
          type: integer
          example: 201
        status:
          type: string
          example: success
        data:
          type: object
          properties:
            customer_id:
              type: integer
              example: 12345
            document:
              type: string
              example: '529.982.247-25'
            sale_amount:
              type: number
              format: float
              example: 100.0
            store_id:
              type: integer
              example: 10
            store_name:
              type: string
              example: Main Store
            operator_id:
              type: integer
              example: 149752
            operator_name:
              type: string
              example: Store Operator
            wallet_balance:
              type: number
              format: float
              example: 110.0
            result:
              type: object
              additionalProperties: true
          required:
            - customer_id
            - sale_amount
            - store_id
            - store_name
            - operator_id
            - operator_name
            - wallet_balance
            - result
        message:
          type: string
          example: Sale registered successfully
      required:
        - code
        - status
        - data
        - message
    Transaction:
      type: object
      properties:
        transaction_id:
          type: integer
          description: Unique transaction identifier corresponding to the transaction record ID.
          example: 123456
        customer_id:
          type: integer
          example: 12345
        sale_amount:
          type: number
          format: float
          example: 100.0
        points_amount:
          type: number
          format: float
          example: 10.0
        type:
          type: string
          example: Credito
        description:
          type: string
          example: Sale registered via API
        source:
          type: string
          example: API
        store_id:
          type: integer
          example: 10
        store_name:
          type: string
          example: Main Store
        operator_id:
          type: integer
          example: 149752
        operator_name:
          type: string
          example: Store Operator
        created_at:
          type: string
          description: Date and time in `YYYY-MM-DD HH:mm:ss` format.
          example: '2026-08-04 14:30:00'
        refunded:
          type: boolean
          example: false
        refundable:
          type: boolean
          example: true
      required:
        - transaction_id
        - customer_id
        - sale_amount
        - points_amount
        - type
        - operator_id
        - operator_name
        - created_at
        - refunded
        - refundable
    TransactionListResponse:
      type: object
      properties:
        code:
          type: integer
          example: 200
        status:
          type: string
          example: success
        data:
          type: object
          properties:
            total:
              type: integer
              example: 1
            page:
              type: integer
              example: 1
            per_page:
              type: integer
              example: 50
            total_pages:
              type: integer
              example: 1
            wallet_balance:
              type: number
              format: float
              description: Current balance calculated from the authenticated company's customer ledger.
              example: 125.5
            balance_mode:
              type: string
              enum: [global, partner]
              description: Indicates whether the company uses one global wallet or balances separated by partner.
              example: partner
            partner_balances:
              type: array
              description: Partner balance composition in partner mode. Empty in global mode.
              items:
                $ref: '#/components/schemas/PartnerBalance'
            items:
              type: array
              items:
                $ref: '#/components/schemas/Transaction'
          required:
            - total
            - page
            - per_page
            - total_pages
            - wallet_balance
            - balance_mode
            - partner_balances
            - items
      required:
        - code
        - status
        - data
    PartnerBalance:
      type: object
      properties:
        partner_id:
          type: integer
          minimum: 0
          description: Smartbis location identifier. Zero may represent a legacy administrator movement without a location ID.
          example: 10
        partner_name:
          type: string
          example: Downtown Store
        balance:
          type: number
          format: float
          example: 75.5
      required: [partner_id, partner_name, balance]
    RefundTransactionRequest:
      type: object
      properties:
        reason:
          type: string
          maxLength: 100
          description: Reason for refunding the transaction.
          example: Customer requested sale cancellation
      required:
        - reason
    RefundTransactionResponse:
      type: object
      properties:
        code:
          type: integer
          example: 200
        status:
          type: string
          example: success
        data:
          type: object
          properties:
            transaction_id:
              type: integer
              example: 123456
            customer_id:
              type: integer
              example: 12345
            refunded_amount:
              type: number
              format: float
              example: 10.0
            wallet_balance:
              type: number
              format: float
              example: 90.0
            result:
              type: object
              additionalProperties: true
          required:
            - transaction_id
            - customer_id
            - refunded_amount
            - wallet_balance
        message:
          type: string
          example: Transaction refunded successfully
      required:
        - code
        - status
        - data
