openapi: 3.2.0
info:
  title: Billing & Ledger Service API
  description: >
    The Billing & Ledger Service is the financial engine of the NPS Billing
    Platform. It manages pricing (Rates, Rate Cards),

    subscription configurations, allocation rules for split billing scenarios
    (divorced households, subsidies), charge lifecycle,

    and double-entry accounting ledger.


    ## Key Concepts


    ### Charge Lifecycle

    Charges follow a strict lifecycle with immutability after invoicing:

    - **PENDING** → Created but not validated

    - **BILLED** → Validated and ready to invoice (still editable)

    - **INVOICED** → Invoiced (IMMUTABLE - moved to SettledCharge)

    - **PAID** → Paid directly without invoice

    - **VOID** → Cancelled


    ### Allocation Configuration

    Defines how charges are split between multiple accounts (e.g., divorced
    parents, subsidy agencies).

    Three rule types:

    - **RESPONSIBLE_PARTY**: Percentage-based split

    - **COVERAGE_TRANSFER**: Fixed amount covered (e.g., $25 subsidy per charge)

    - **BILLING_CAP**: Maximum amount per period


    ### Double-Entry Ledger

    All financial transactions follow the accounting equation (Debits =
    Credits).

    Journal entries are immutable once created - corrections use adjustment
    entries.


    ### Monetary Values

    All monetary amounts are stored as **DECIMAL type in cents** (e.g., 5000 =
    $50.00).

    Use DECIMAL or NUMERIC type (BigDecimal in Java) for precision and to avoid
    rounding errors.

    Never use integers or floating-point for money.
  version: 1.0.0
servers:
  - url: https://api.uat.nelnetpay.com/billing
    description: UAT server
  - url: https://api.nelnetpay.com/billing
    description: Production server
tags:
  - name: Rates
    description: Manage rate catalog (pricing for services)
  - name: RateCards
    description: Manage rate card groupings for UI organization
  - name: Subscriptions
    description: Manage billable entity subscriptions to rates
  - name: AllocationConfigurations
    description: Manage charge allocation configurations for split billing
  - name: Charges
    description: Manage billable charges (mutable pre-invoice state)
  - name: SettledCharges
    description: Query settled charges (immutable post-invoice state)
  - name: Refunds
    description: Manage refunds for invoices, charges, or standalone refunds
  - name: Adjustments
    description: Manage manual adjustments and corrections
  - name: Ledger
    description: Manage double-entry accounting ledger
  - name: Exports
    description: Export data in CSV format for GL integrations
paths:
  /rates:
    get:
      tags:
        - Rates
      summary: List Rates
      description: >
        List all rates (pricing catalog) for the authenticated merchant with
        pagination and filtering.

        Rates define how much each type of service costs.
      operationId: listRates
      parameters:
        - $ref: '#/components/parameters/RateTypeFilter'
        - $ref: '#/components/parameters/TagFilter'
        - $ref: '#/components/parameters/PageParam'
        - $ref: '#/components/parameters/PageSizeParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RateListResponse'
              example:
                results:
                  - id: 550e8400-e29b-41d4-a716-446655440001
                    entityId: 550e8400-e29b-41d4-a716-446655440000
                    description: Pre-K Daily Rate
                    type: service fee
                    pricePerUnit: 5000
                    tags:
                      billing.category: service fee
                      billing.program: PRE_K
                      billing.taxable: 'false'
                      billing.glCode: '4000'
                    createdAt: '2026-01-15T10:30:00Z'
                    updatedAt: '2026-01-15T10:30:00Z'
                pagination:
                  totalRecords: 1
                  currentPage: 1
                  totalPages: 1
                  nextPage: null
                  prevPage: null
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/TooManyRequests'
      security:
        - OAuth2:
            - billing:read
    post:
      tags:
        - Rates
      summary: Create Rate
      description: >
        Create a new rate in the pricing catalog.


        **Rate Types:**

        - `service fee` - Regular service fee/service fees

        - `LATE_FEE` - Late payment penalties

        - `REGISTRATION` - One-time registration fees

        - `DISCOUNT` - Discount rates (use discountPercentage instead of
        pricePerUnit)

        - `OTHER` - Miscellaneous charges
      operationId: createRate
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateRateRequest'
            example:
              description: Pre-K Daily Rate
              type: service fee
              pricePerUnit: 5000
              tags:
                billing.category: service fee
                billing.program: PRE_K
                billing.taxable: 'false'
                billing.glCode: '4000'
      responses:
        '201':
          description: Rate created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Rate'
        '400':
          $ref: '#/components/responses/UnprocessableEntity'
        '401':
          $ref: '#/components/responses/Unauthorized'
      security:
        - OAuth2:
            - billing:write
  /rates/{rateId}:
    get:
      tags:
        - Rates
      summary: Get Rate Details
      description: Retrieve details of a specific rate
      operationId: getRate
      parameters:
        - $ref: '#/components/parameters/RateId'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Rate'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
      security:
        - OAuth2:
            - billing:read
    patch:
      tags:
        - Rates
      summary: Update Rate
      description: >
        Update a rate's information (partial update).


        **Note:** Updating a rate does NOT affect existing charges or
        subscriptions.

        They retain the rate version from when they were created.
      operationId: updateRate
      parameters:
        - $ref: '#/components/parameters/RateId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateRateRequest'
            example:
              description: Pre-K Daily Rate (2026)
              pricePerUnit: 5500
      responses:
        '200':
          description: Rate updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Rate'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
      security:
        - OAuth2:
            - billing:write
    delete:
      tags:
        - Rates
      summary: Delete Rate
      description: >
        Soft delete a rate (marked as deleted but retained for audit purposes).


        **Restrictions:** Cannot delete a rate that is referenced by active
        subscriptions or unbilled charges.
      operationId: deleteRate
      parameters:
        - $ref: '#/components/parameters/RateId'
      responses:
        '204':
          description: Rate deleted successfully
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
      security:
        - OAuth2:
            - billing:write
  /rateCards:
    get:
      tags:
        - RateCards
      summary: List Rate Cards
      description: |
        List all rate cards for the authenticated merchant.
        Rate cards group related rates for easier UI presentation and selection.
      operationId: listRateCards
      parameters:
        - $ref: '#/components/parameters/RateCardScopeFilter'
        - $ref: '#/components/parameters/PageParam'
        - $ref: '#/components/parameters/PageSizeParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RateCardListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
      security:
        - OAuth2:
            - billing:read
    post:
      tags:
        - RateCards
      summary: Create Rate Card
      description: |
        Create a new rate card to group related rates.

        **Scope:**
        - `MERCHANT` - Available to all accounts
        - `ACCOUNT` - Available to specific accounts only
      operationId: createRateCard
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateRateCardRequest'
            example:
              name: Pre-K Rates 2026
              scope: MERCHANT
              tags:
                billing.program: PRE_K
                billing.year: '2026'
      responses:
        '201':
          description: Rate card created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RateCard'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
      security:
        - OAuth2:
            - billing:write
  /rateCards/{rateCardId}:
    get:
      tags:
        - RateCards
      summary: Get Rate Card Details
      description: Retrieve details of a specific rate card including associated rates
      operationId: getRateCard
      parameters:
        - $ref: '#/components/parameters/RateCardId'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RateCardWithRates'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
      security:
        - OAuth2:
            - billing:read
    patch:
      tags:
        - RateCards
      summary: Update Rate Card
      description: Update a rate card's information (partial update)
      operationId: updateRateCard
      parameters:
        - $ref: '#/components/parameters/RateCardId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateRateCardRequest'
      responses:
        '200':
          description: Rate card updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RateCard'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
      security:
        - OAuth2:
            - billing:write
    delete:
      tags:
        - RateCards
      summary: Delete Rate Card
      description: Delete a rate card (does not delete associated rates)
      operationId: deleteRateCard
      parameters:
        - $ref: '#/components/parameters/RateCardId'
      responses:
        '204':
          description: Rate card deleted successfully
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
      security:
        - OAuth2:
            - billing:write
  /rateCards/{rateCardId}/rates/{rateId}:
    post:
      tags:
        - RateCards
      summary: Add Rate to Rate Card
      description: Associate a rate with a rate card (idempotent)
      operationId: addRateToRateCard
      parameters:
        - $ref: '#/components/parameters/RateCardId'
        - $ref: '#/components/parameters/RateId'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                displayOrder:
                  type: integer
                  description: Order in which rate appears in the card (lower = first)
                  default: 0
      responses:
        '201':
          description: Rate added to rate card successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RateCardRate'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
      security:
        - OAuth2:
            - billing:write
    delete:
      tags:
        - RateCards
      summary: Remove Rate from Rate Card
      description: Remove a rate from a rate card (idempotent)
      operationId: removeRateFromRateCard
      parameters:
        - $ref: '#/components/parameters/RateCardId'
        - $ref: '#/components/parameters/RateId'
      responses:
        '204':
          description: Rate removed from rate card successfully
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
      security:
        - OAuth2:
            - billing:write
  /subscriptions:
    get:
      tags:
        - Subscriptions
      summary: List Subscriptions
      description: >
        List all subscriptions for the authenticated merchant.

        Subscriptions link billable entities to rates with allocation
        configurations.
      operationId: listSubscriptions
      parameters:
        - $ref: '#/components/parameters/BillableEntityIdFilter'
        - $ref: '#/components/parameters/SubscriptionStatusFilter'
        - $ref: '#/components/parameters/PageParam'
        - $ref: '#/components/parameters/PageSizeParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubscriptionListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
      security:
        - OAuth2:
            - billing:read
    post:
      tags:
        - Subscriptions
      summary: Create Subscription
      description: >
        Create a new subscription linking a billable entity to a rate.


        **Validation:** The billable entity must be associated with ALL accounts
        referenced

        in the allocation configuration. This is validated via Profile Service.


        **Billing Frequency:**

        - `DAILY` - Charges generated daily

        - `WEEKLY` - Charges generated weekly (anchorDay = day of week 1-7)

        - `BIWEEKLY` - Charges generated every two weeks

        - `MONTHLY` - Charges generated monthly (anchorDay = day of month 1-31)
      operationId: createSubscription
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateSubscriptionRequest'
            example:
              billableEntityId: 550e8400-e29b-41d4-a716-446655440010
              rateId: 550e8400-e29b-41d4-a716-446655440001
              allocationConfigId: 550e8400-e29b-41d4-a716-446655440020
              discountRateIds:
                - 550e8400-e29b-41d4-a716-446655440002
              billingFrequency: WEEKLY
              billingAnchorDay: 1
              billingTimezone: America/New_York
              startDate: '2026-01-20'
      responses:
        '201':
          description: Subscription created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Subscription'
        '400':
          description: >
            Validation failed. Common causes:

            - Billable entity not associated with all accounts in allocation
            configuration

            - Allocation configuration does not cover 100%

            - Referenced rate or discount rates not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                result:
                  status: ERROR
                error:
                  responseCode: '400'
                  responseMessage:
                    - 400 Error
        '401':
          $ref: '#/components/responses/Unauthorized'
      security:
        - OAuth2:
            - billing:write
  /subscriptions/{subscriptionId}:
    get:
      tags:
        - Subscriptions
      summary: Get Subscription Details
      description: Retrieve details of a specific subscription
      operationId: getSubscription
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Subscription'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
      security:
        - OAuth2:
            - billing:read
    patch:
      tags:
        - Subscriptions
      summary: Update Subscription
      description: |
        Update a subscription's information (partial update).

        **Version Tracking:** Each update increments the subscription version.
        Existing charges retain the version from when they were created.
      operationId: updateSubscription
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateSubscriptionRequest'
      responses:
        '200':
          description: Subscription updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Subscription'
        '400':
          $ref: '#/components/responses/UnprocessableEntity'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
      security:
        - OAuth2:
            - billing:write
  /subscriptions/{subscriptionId}/pause:
    post:
      tags:
        - Subscriptions
      summary: Pause Subscription
      description: Pause a subscription to temporarily stop charge generation
      operationId: pauseSubscription
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
      responses:
        '200':
          description: Subscription paused successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Subscription'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Subscription is already paused or cancelled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                result:
                  status: ERROR
                error:
                  responseCode: '409'
                  responseMessage:
                    - 409 Error
      security:
        - OAuth2:
            - billing:write
  /subscriptions/{subscriptionId}/resume:
    post:
      tags:
        - Subscriptions
      summary: Resume Subscription
      description: Resume a paused subscription
      operationId: resumeSubscription
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
      responses:
        '200':
          description: Subscription resumed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Subscription'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Subscription is not paused
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                result:
                  status: ERROR
                error:
                  responseCode: '409'
                  responseMessage:
                    - 409 Error
      security:
        - OAuth2:
            - billing:write
  /subscriptions/{subscriptionId}/cancel:
    post:
      tags:
        - Subscriptions
      summary: Cancel Subscription
      description: >
        Cancel a subscription permanently.


        **Note:** This does not void existing charges. Use charge void endpoints
        for that.
      operationId: cancelSubscription
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                effectiveDate:
                  type: string
                  format: date
                  description: Date when cancellation takes effect (defaults to today)
                reason:
                  type: string
                  description: Reason for cancellation
      responses:
        '200':
          description: Subscription cancelled successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Subscription'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Subscription is already cancelled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                result:
                  status: ERROR
                error:
                  responseCode: '409'
                  responseMessage:
                    - 409 Error
      security:
        - OAuth2:
            - billing:write
  /allocationConfigurations:
    get:
      tags:
        - AllocationConfigurations
      summary: List Allocation Configurations
      description: >
        List all allocation configurations for the authenticated merchant.


        Allocation configurations define how charges are split between multiple
        accounts

        (e.g., divorced parents, household + subsidy agency).
      operationId: listAllocationConfigurations
      parameters:
        - $ref: '#/components/parameters/PageParam'
        - $ref: '#/components/parameters/PageSizeParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AllocationConfigurationListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
      security:
        - OAuth2:
            - billing:read
    post:
      tags:
        - AllocationConfigurations
      summary: Create Allocation Configuration
      description: >
        Create a new allocation configuration with rules.


        **Important:** The total allocation percentage does NOT need to equal
        100% at creation time.

        This allows for incremental household enrollment. Validation of 100%
        happens at **charge creation time**.


        **Rule Types:**

        - `RESPONSIBLE_PARTY` - Split by percentage (e.g., Mom 50%, Dad 50%)

        - `COVERAGE_TRANSFER` - Fixed amount covered (e.g., subsidy covers $25
        per charge)

        - `BILLING_CAP` - Maximum amount per period (e.g., subsidy covers up to
        $400/month)


        **Processing Order:** Rules are processed in `priority` order (1 =
        first).

        COVERAGE_TRANSFER and BILLING_CAP typically run before
        RESPONSIBLE_PARTY.
      operationId: createAllocationConfiguration
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateAllocationConfigurationRequest'
            example:
              name: Emma - household + State Subsidy Split
              description: State subsidy covers $25/charge, remainder to household
              rules:
                - priority: 1
                  ruleType: COVERAGE_TRANSFER
                  accountId: 550e8400-e29b-41d4-a716-446655440030
                  fixedAmount: 2500
                - priority: 2
                  ruleType: RESPONSIBLE_PARTY
                  accountId: 550e8400-e29b-41d4-a716-446655440031
                  percentage: 100
      responses:
        '201':
          description: Allocation configuration created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AllocationConfiguration'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
      security:
        - OAuth2:
            - billing:write
  /allocationConfigurations/{allocationConfigId}:
    get:
      tags:
        - AllocationConfigurations
      summary: Get Allocation Configuration Details
      description: >-
        Retrieve details of a specific allocation configuration including all
        rules
      operationId: getAllocationConfiguration
      parameters:
        - $ref: '#/components/parameters/AllocationConfigId'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AllocationConfiguration'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
      security:
        - OAuth2:
            - billing:read
    patch:
      tags:
        - AllocationConfigurations
      summary: Update Allocation Configuration
      description: >
        Update an allocation configuration (partial update).


        **Version Tracking:** Each update increments the version.

        Existing charges and subscriptions retain the version from when they
        were created.
      operationId: updateAllocationConfiguration
      parameters:
        - $ref: '#/components/parameters/AllocationConfigId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateAllocationConfigurationRequest'
      responses:
        '200':
          description: Allocation configuration updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AllocationConfiguration'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
      security:
        - OAuth2:
            - billing:write
    delete:
      tags:
        - AllocationConfigurations
      summary: Delete Allocation Configuration
      description: >
        Soft delete an allocation configuration.


        **Restrictions:** Cannot delete if referenced by active subscriptions or
        unbilled charges.
      operationId: deleteAllocationConfiguration
      parameters:
        - $ref: '#/components/parameters/AllocationConfigId'
      responses:
        '204':
          description: Allocation configuration deleted successfully
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
      security:
        - OAuth2:
            - billing:write
  /allocationConfigurations/{allocationConfigId}/validate:
    post:
      tags:
        - AllocationConfigurations
      summary: Validate Allocation Configuration
      description: >
        Validate an allocation configuration for a specific billable entity.


        **Validation Checks:**

        1. All accounts in allocation rules are associated with the billable
        entity

        2. RESPONSIBLE_PARTY rules total 100% of remaining amount

        3. No circular dependencies in rules


        Use this before creating charges to catch configuration issues early.
      operationId: validateAllocationConfiguration
      parameters:
        - $ref: '#/components/parameters/AllocationConfigId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - billableEntityId
              properties:
                billableEntityId:
                  type: string
                  format: uuid
                  description: Billable entity to validate against
      responses:
        '200':
          description: Validation result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AllocationValidationResult'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
      security:
        - OAuth2:
            - billing:read
  /charges:
    get:
      tags:
        - Charges
      summary: List Charges
      description: >
        List charges (mutable pre-invoice state) for the authenticated merchant.


        **Status Filtering:**

        - `PENDING` - Created but not validated

        - `BILLED` - Ready to invoice

        - `VOID` - Cancelled


        Charges with status `INVOICED` or `PAID` are in the SettledCharges
        table.
      operationId: listCharges
      parameters:
        - $ref: '#/components/parameters/ChargeStatusFilter'
        - $ref: '#/components/parameters/BillableEntityIdFilter'
        - $ref: '#/components/parameters/AccountIdFilter'
        - $ref: '#/components/parameters/EventDateFromFilter'
        - $ref: '#/components/parameters/EventDateToFilter'
        - $ref: '#/components/parameters/PageParam'
        - $ref: '#/components/parameters/PageSizeParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChargeListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
      security:
        - OAuth2:
            - billing:read
    post:
      tags:
        - Charges
      summary: Create Charge
      description: >
        Create a new charge for a billable entity.


        **Validation at Creation:**

        1. Fetches all accounts associated with billableEntityId (via Profile
        Service)

        2. Validates billableEntity is associated with ALL accounts in
        allocationConfig

        3. Validates allocationConfig covers 100% of ALL accounts for the
        billableEntity

        4. Returns 422 if validation fails


        **Charge Calculation:**

        - `amount` = quantity × rate.pricePerUnit

        - `proratedAmount` = amount × prorationFactor

        - `netAmount` = proratedAmount - sum(discountAmounts)


        **Idempotency:** Include `Idempotency-Key` header to prevent duplicate
        charges.
      operationId: createCharge
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateChargeRequest'
            example:
              billableEntityId: 550e8400-e29b-41d4-a716-446655440010
              rateId: 550e8400-e29b-41d4-a716-446655440001
              quantity: 5
              allocationConfigId: 550e8400-e29b-41d4-a716-446655440020
              discountRateIds:
                - 550e8400-e29b-41d4-a716-446655440002
              eventDate: '2026-01-20'
      responses:
        '201':
          description: Charge created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Charge'
        '400':
          description: >
            Validation failed. Common causes:

            - Billable entity not associated with all accounts in allocation
            configuration

            - Allocation configuration does not cover 100%

            - Referenced rate or discount rates not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                result:
                  status: ERROR
                error:
                  responseCode: '400'
                  responseMessage:
                    - 400 Error
        '401':
          $ref: '#/components/responses/Unauthorized'
      security:
        - OAuth2:
            - billing:write
  /charges/{chargeId}:
    get:
      tags:
        - Charges
      summary: Get Charge Details
      description: Retrieve details of a specific charge
      operationId: getCharge
      parameters:
        - $ref: '#/components/parameters/ChargeId'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Charge'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
      security:
        - OAuth2:
            - billing:read
    patch:
      tags:
        - Charges
      summary: Update Charge
      description: >
        Update a charge (partial update).


        **Restrictions:** Only charges with status `PENDING` or `BILLED` can be
        updated.

        Once a charge is `INVOICED`, it becomes immutable (moved to
        SettledCharges).
      operationId: updateCharge
      parameters:
        - $ref: '#/components/parameters/ChargeId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateChargeRequest'
      responses:
        '200':
          description: Charge updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Charge'
        '400':
          $ref: '#/components/responses/UnprocessableEntity'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Charge is invoiced and cannot be modified
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                result:
                  status: ERROR
                error:
                  responseCode: '409'
                  responseMessage:
                    - 409 Error
      security:
        - OAuth2:
            - billing:write
    delete:
      tags:
        - Charges
      summary: Delete Charge
      description: >
        Delete a charge (hard delete for PENDING, soft delete/void for BILLED).


        **Restrictions:** Cannot delete charges with status `INVOICED` or
        `PAID`.

        Use ledger adjustments for corrections to invoiced charges.
      operationId: deleteCharge
      parameters:
        - $ref: '#/components/parameters/ChargeId'
      responses:
        '204':
          description: Charge deleted successfully
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Charge is invoiced and cannot be deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                result:
                  status: ERROR
                error:
                  responseCode: '409'
                  responseMessage:
                    - 409 Error
      security:
        - OAuth2:
            - billing:write
  /charges/{chargeId}/void:
    post:
      tags:
        - Charges
      summary: Void Charge
      description: >
        Void a charge (sets status to VOID).


        **Use Case:** When a charge was created in error but you want to keep
        audit trail.
      operationId: voidCharge
      parameters:
        - $ref: '#/components/parameters/ChargeId'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                reason:
                  type: string
                  description: Reason for voiding the charge
      responses:
        '200':
          description: Charge voided successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Charge'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Charge is already voided or invoiced
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                result:
                  status: ERROR
                error:
                  responseCode: '409'
                  responseMessage:
                    - 409 Error
      security:
        - OAuth2:
            - billing:write
  /charges/bulk:
    post:
      tags:
        - Charges
      summary: Create Multiple Charges
      description: >
        Create multiple charges in a single request.


        **Use Case:** Creating charges for multiple billable entities at once
        (e.g., batch from recurring billing).


        **Atomicity:** All charges are created in a single transaction. If any
        fails, all are rolled back.
      operationId: createChargesBulk
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - charges
              properties:
                charges:
                  type: array
                  items:
                    $ref: '#/components/schemas/CreateChargeRequest'
                  maxItems: 100
                  description: Array of charges to create (max 100)
      responses:
        '201':
          description: All charges created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Charge'
                  created:
                    type: integer
                    description: Number of charges created
        '400':
          description: One or more charges failed validation (none created)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkChargeError'
        '401':
          $ref: '#/components/responses/Unauthorized'
      security:
        - OAuth2:
            - billing:write
  /settledCharges:
    get:
      tags:
        - SettledCharges
      summary: List Settled Charges
      description: >
        List settled charges (immutable post-invoice/payment state).


        Settled charges are created when:

        - A charge is invoiced (`settlementType: INVOICED`)

        - A charge is paid directly without invoice (`settlementType:
        DIRECT_PAYMENT`)


        **Note:** Settled charges are IMMUTABLE. Corrections must be made via
        ledger adjustments.
      operationId: listSettledCharges
      parameters:
        - $ref: '#/components/parameters/SettledChargeStatusFilter'
        - $ref: '#/components/parameters/SettlementTypeFilter'
        - $ref: '#/components/parameters/BillableEntityIdFilter'
        - $ref: '#/components/parameters/AccountIdFilter'
        - $ref: '#/components/parameters/InvoiceIdFilter'
        - $ref: '#/components/parameters/SettledAtFromFilter'
        - $ref: '#/components/parameters/SettledAtToFilter'
        - $ref: '#/components/parameters/PageParam'
        - $ref: '#/components/parameters/PageSizeParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SettledChargeListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
      security:
        - OAuth2:
            - billing:read
  /settledCharges/{settledChargeId}:
    get:
      tags:
        - SettledCharges
      summary: Get Settled Charge Details
      description: >
        Retrieve details of a specific settled charge.


        Includes frozen snapshots of:

        - `resolvedRate` - Rate details at time of settlement

        - `resolvedAllocation` - Allocation details at time of settlement

        - `consolidatedTags` - Tags from all sources (profile, rate,
        subscription, charge)
      operationId: getSettledCharge
      parameters:
        - $ref: '#/components/parameters/SettledChargeId'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SettledCharge'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
      security:
        - OAuth2:
            - billing:read
  /refunds:
    post:
      tags:
        - Refunds
      summary: Create Refund
      description: >
        Initiate a refund for one or more invoices, charges, or as a standalone
        refund.


        **Refund Types:**

        - **Invoice Refund**: Refund one or more invoices

        - **Charge Refund**: Refund one or more specific charges

        - **Standalone Refund**: Refund without reference to an invoice/charge


        **Process:**

        1. Refund request is validated

        2. Billing Service calls Payment API to process refund

        3. On success, journal entries are created (DR Refund Expense, CR Bank)

        4. Optionally, receipt is sent via Receipt Service
      operationId: createRefund
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RefundRequest'
      responses:
        '201':
          description: Refund created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Refund'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
      security:
        - OAuth2:
            - billing:write
    get:
      tags:
        - Refunds
      summary: List Refunds
      description: List all refunds with pagination and filtering
      operationId: listRefunds
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum:
              - PENDING
              - PROCESSING
              - COMPLETED
              - FAILED
              - CANCELLED
        - name: refund_type
          in: query
          schema:
            type: string
            enum:
              - INVOICE
              - CHARGE
              - STANDALONE
        - $ref: '#/components/parameters/PageParam'
        - $ref: '#/components/parameters/PageSizeParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RefundListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
      security:
        - OAuth2:
            - billing:read
  /refunds/{refundId}:
    get:
      tags:
        - Refunds
      summary: Get Refund
      description: Retrieve details of a specific refund
      operationId: getRefund
      parameters:
        - name: refundId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Refund'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
      security:
        - OAuth2:
            - billing:read
  /refunds/{refundId}/status:
    get:
      tags:
        - Refunds
      summary: Get Refund Status
      description: Get the processing status of a refund
      operationId: getRefundStatus
      parameters:
        - name: refundId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RefundStatus'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
      security:
        - OAuth2:
            - billing:read
  /adjustments:
    post:
      tags:
        - Adjustments
      summary: Create Manual Adjustment
      description: >
        Create a manual adjustment to correct ledger entries.


        **Adjustment Types:**

        - `DISCOUNT_CORRECTION` - Fix discount calculation error

        - `FEE_CORRECTION` - Adjust fee amount

        - `CREDIT_MEMO` - Issue credit to account

        - `DEBIT_MEMO` - Charge additional amount

        - `WRITE_OFF` - Bad debt write-off


        **Authorization:** Requires special permission level for manual
        adjustments.

        All adjustments include a full audit trail.
      operationId: createAdjustment
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AdjustmentRequest'
      responses:
        '201':
          description: Adjustment created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Adjustment'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
      security:
        - OAuth2:
            - billing:write
            - billing:adjustments
    get:
      tags:
        - Adjustments
      summary: List Adjustments
      description: List all manual adjustments with pagination and filtering
      operationId: listAdjustments
      parameters:
        - name: adjustment_type
          in: query
          schema:
            type: string
            enum:
              - DISCOUNT_CORRECTION
              - FEE_CORRECTION
              - CREDIT_MEMO
              - DEBIT_MEMO
              - WRITE_OFF
        - name: admin_user_id
          in: query
          schema:
            type: string
        - $ref: '#/components/parameters/PageParam'
        - $ref: '#/components/parameters/PageSizeParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AdjustmentListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
      security:
        - OAuth2:
            - billing:read
  /adjustments/{adjustmentId}:
    get:
      tags:
        - Adjustments
      summary: Get Adjustment
      description: Retrieve details of a specific adjustment
      operationId: getAdjustment
      parameters:
        - name: adjustmentId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Adjustment'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
      security:
        - OAuth2:
            - billing:read
  /ledger/journalEntries:
    get:
      tags:
        - Ledger
      summary: List Journal Entries
      description: |
        List journal entries (double-entry accounting records).

        **Sources:**
        - `INVOICE` - Created when invoice is posted (DR: A/R, CR: Revenue)
        - `PAYMENT` - Created when payment is received (DR: Bank, CR: A/R)
        - `REFUND` - Created when refund is processed
        - `ADJUSTMENT` - Manual corrections
        - `REMITTANCE` - Payment processor fee tracking
      operationId: listJournalEntries
      parameters:
        - $ref: '#/components/parameters/JournalSourceFilter'
        - $ref: '#/components/parameters/EntryDateFromFilter'
        - $ref: '#/components/parameters/EntryDateToFilter'
        - $ref: '#/components/parameters/SourceReferenceFilter'
        - $ref: '#/components/parameters/PageParam'
        - $ref: '#/components/parameters/PageSizeParam'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JournalEntryListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
      security:
        - OAuth2:
            - billing:read
    post:
      tags:
        - Ledger
      summary: Create Journal Entry
      description: |
        Create a new journal entry with lines.

        **Double-Entry Rule:** Sum of debits MUST equal sum of credits.
        The entry will be rejected if not balanced.

        **Immutability:** Journal entries are immutable once created.
        Use adjustment entries for corrections.

        **Account Codes:**
        - `REVENUE` - Revenue accounts
        - `AR` - Accounts Receivable
        - `CONTRA_REVENUE` - Contra-revenue (discounts)
        - `CLEARING` - Clearing accounts
        - `BANK` - Bank accounts
        - `FEE_EXPENSE` - Processing fee expenses
        - `REFUND_EXPENSE` - Refund expenses
      operationId: createJournalEntry
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateJournalEntryRequest'
            example:
              entryDate: '2026-01-20T10:30:00Z'
              currency: USD
              source: ADJUSTMENT
              sourceReference: ADJ-2026-001
              adjustmentType: DISCOUNT_CORRECTION
              reason: Applied missing sibling discount
              lines:
                - accountCode: CONTRA_REVENUE
                  direction: DEBIT
                  amount: 1000
                  description: Sibling discount correction
                  accountId: 550e8400-e29b-41d4-a716-446655440031
                - accountCode: AR
                  direction: CREDIT
                  amount: 1000
                  description: Reduce A/R for discount
                  accountId: 550e8400-e29b-41d4-a716-446655440031
      responses:
        '201':
          description: Journal entry created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JournalEntry'
        '400':
          description: |
            Bad request. Common causes:
            - Entry is not balanced (debits ≠ credits)
            - Missing required fields
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                result:
                  status: ERROR
                error:
                  responseCode: '400'
                  responseMessage:
                    - 400 Error
        '401':
          $ref: '#/components/responses/Unauthorized'
      security:
        - OAuth2:
            - billing:write
  /ledger/journalEntries/{journalEntryId}:
    get:
      tags:
        - Ledger
      summary: Get Journal Entry Details
      description: Retrieve details of a specific journal entry including all lines
      operationId: getJournalEntry
      parameters:
        - $ref: '#/components/parameters/JournalEntryId'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JournalEntry'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
      security:
        - OAuth2:
            - billing:read
  /ledger/accountBalances:
    get:
      tags:
        - Ledger
      summary: Get Account Balances
      description: |
        Get current balances for all ledger account codes.

        Useful for reconciliation and financial reporting.
      operationId: getAccountBalances
      parameters:
        - name: as_of_date
          in: query
          description: Get balances as of this date (defaults to current)
          schema:
            type: string
            format: date-time
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccountBalancesResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
      security:
        - OAuth2:
            - billing:read
  /ledger/trialBalance:
    get:
      tags:
        - Ledger
      summary: Get Trial Balance
      description: >
        Get trial balance report showing all accounts with their debit and
        credit totals.


        **Validation:** Debit total should equal credit total.
      operationId: getTrialBalance
      parameters:
        - name: from_date
          in: query
          description: Start date for the period
          schema:
            type: string
            format: date
        - name: to_date
          in: query
          description: End date for the period
          schema:
            type: string
            format: date
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TrialBalanceResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
      security:
        - OAuth2:
            - billing:read
  /exports/charges:
    get:
      tags:
        - Exports
      summary: Export Charges to CSV
      description: |
        Export charges to CSV format.

        Includes both mutable charges and settled charges based on filters.
      operationId: exportCharges
      parameters:
        - $ref: '#/components/parameters/EventDateFromFilter'
        - $ref: '#/components/parameters/EventDateToFilter'
        - $ref: '#/components/parameters/ChargeStatusFilter'
        - name: include_settled
          in: query
          description: Include settled charges in export
          schema:
            type: boolean
            default: true
      responses:
        '200':
          description: CSV file
          content:
            text/csv:
              schema:
                type: string
          headers:
            Content-Disposition:
              schema:
                type: string
              example: attachment; filename="charges-2026-01-20.csv"
        '401':
          $ref: '#/components/responses/Unauthorized'
      security:
        - OAuth2:
            - billing:read
  /exports/ledgerEntries:
    get:
      tags:
        - Exports
      summary: Export Ledger Entries to CSV
      description: |
        Export journal entries and lines to CSV format for GL integration.

        **Format:** Mirrors webhook event structure for consistency.
        Can be directly imported into QuickBooks, Xero, Sage Intacct, etc.
      operationId: exportLedgerEntries
      parameters:
        - $ref: '#/components/parameters/EntryDateFromFilter'
        - $ref: '#/components/parameters/EntryDateToFilter'
        - $ref: '#/components/parameters/JournalSourceFilter'
        - name: account_code
          in: query
          description: Filter by specific account code
          schema:
            $ref: '#/components/schemas/AccountCode'
      responses:
        '200':
          description: CSV file
          content:
            text/csv:
              schema:
                type: string
          headers:
            Content-Disposition:
              schema:
                type: string
              example: attachment; filename="ledger-entries-2026-01-20.csv"
        '401':
          $ref: '#/components/responses/Unauthorized'
      security:
        - OAuth2:
            - billing:read
components:
  securitySchemes:
    OAuth2:
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: https://auth.nps.com/oauth/authorize
          tokenUrl: https://auth.nps.com/oauth/token
          scopes:
            billing:read: Read billing data (rates, charges, ledger, refunds, adjustments)
            billing:write: >-
              Write billing data (create/update charges, post journal entries,
              create refunds)
            billing:adjustments: Create manual adjustments (requires elevated permissions)
  parameters:
    PageParam:
      name: page
      in: query
      description: Page number (1-indexed)
      schema:
        type: integer
        minimum: 1
        default: 1
      example: 1
    PageSizeParam:
      name: page_size
      in: query
      description: Number of items per page
      schema:
        type: integer
        minimum: 1
        maximum: 200
        default: 50
      example: 50
    RateId:
      name: rateId
      in: path
      required: true
      description: Unique identifier for the rate
      schema:
        type: string
        format: uuid
    RateCardId:
      name: rateCardId
      in: path
      required: true
      description: Unique identifier for the rate card
      schema:
        type: string
        format: uuid
    SubscriptionId:
      name: subscriptionId
      in: path
      required: true
      description: Unique identifier for the subscription
      schema:
        type: string
        format: uuid
    AllocationConfigId:
      name: allocationConfigId
      in: path
      required: true
      description: Unique identifier for the allocation configuration
      schema:
        type: string
        format: uuid
    ChargeId:
      name: chargeId
      in: path
      required: true
      description: Unique identifier for the charge
      schema:
        type: string
        format: uuid
    SettledChargeId:
      name: settledChargeId
      in: path
      required: true
      description: Unique identifier for the settled charge
      schema:
        type: string
        format: uuid
    JournalEntryId:
      name: journalEntryId
      in: path
      required: true
      description: Unique identifier for the journal entry
      schema:
        type: string
        format: uuid
    RateTypeFilter:
      name: type
      in: query
      description: Filter by rate type
      schema:
        $ref: '#/components/schemas/RateType'
    TagFilter:
      name: tags
      in: query
      description: Filter by tags using dot.notation (key:value pairs, comma-separated)
      schema:
        type: string
      example: billing.program:PRE_K,billing.taxable:false
    RateCardScopeFilter:
      name: scope
      in: query
      description: Filter by rate card scope
      schema:
        $ref: '#/components/schemas/RateCardScope'
    BillableEntityIdFilter:
      name: billable_entity_id
      in: query
      description: Filter by billable entity ID
      schema:
        type: string
        format: uuid
    AccountIdFilter:
      name: account_id
      in: query
      description: Filter by account ID
      schema:
        type: string
        format: uuid
    InvoiceIdFilter:
      name: invoice_id
      in: query
      description: Filter by invoice ID
      schema:
        type: string
        format: uuid
    SubscriptionStatusFilter:
      name: status
      in: query
      description: Filter by subscription status
      schema:
        $ref: '#/components/schemas/SubscriptionStatus'
    ChargeStatusFilter:
      name: status
      in: query
      description: Filter by charge status
      schema:
        $ref: '#/components/schemas/ChargeStatus'
    SettledChargeStatusFilter:
      name: status
      in: query
      description: Filter by settled charge status
      schema:
        $ref: '#/components/schemas/SettledChargeStatus'
    SettlementTypeFilter:
      name: settlement_type
      in: query
      description: Filter by settlement type
      schema:
        $ref: '#/components/schemas/SettlementType'
    JournalSourceFilter:
      name: source
      in: query
      description: Filter by journal entry source
      schema:
        $ref: '#/components/schemas/JournalSource'
    SourceReferenceFilter:
      name: source_reference
      in: query
      description: Filter by source reference (e.g., invoice ID)
      schema:
        type: string
    EventDateFromFilter:
      name: event_date_from
      in: query
      description: Filter charges with event date on or after this date
      schema:
        type: string
        format: date
    EventDateToFilter:
      name: event_date_to
      in: query
      description: Filter charges with event date on or before this date
      schema:
        type: string
        format: date
    SettledAtFromFilter:
      name: settled_at_from
      in: query
      description: Filter settled charges settled on or after this timestamp
      schema:
        type: string
        format: date-time
    SettledAtToFilter:
      name: settled_at_to
      in: query
      description: Filter settled charges settled on or before this timestamp
      schema:
        type: string
        format: date-time
    EntryDateFromFilter:
      name: entry_date_from
      in: query
      description: Filter journal entries on or after this date
      schema:
        type: string
        format: date-time
    EntryDateToFilter:
      name: entry_date_to
      in: query
      description: Filter journal entries on or before this date
      schema:
        type: string
        format: date-time
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      description: Unique key to ensure idempotent operations (UUID recommended)
      schema:
        type: string
        format: uuid
  schemas:
    Pagination:
      type: object
      required:
        - totalRecords
        - currentPage
        - totalPages
      properties:
        totalRecords:
          type: integer
          description: Total number of records across all pages
          example: 100
        currentPage:
          type: integer
          description: Current page number (1-indexed)
          example: 1
        totalPages:
          type: integer
          description: Total number of pages
          example: 10
        nextPage:
          type:
            - integer
            - 'null'
          description: Next page number, null if on last page
          example: 2
        prevPage:
          type:
            - integer
            - 'null'
          description: Previous page number, null if on first page
          example: null
    RateType:
      type: string
      enum:
        - service fee
        - LATE_FEE
        - REGISTRATION
        - DISCOUNT
        - OTHER
      description: Type of rate
    RateCardScope:
      type: string
      enum:
        - MERCHANT
        - ACCOUNT
      description: Scope of rate card availability
    BillingFrequency:
      type: string
      enum:
        - DAILY
        - WEEKLY
        - BIWEEKLY
        - MONTHLY
      description: Billing frequency for subscriptions
    SubscriptionStatus:
      type: string
      enum:
        - ACTIVE
        - PAUSED
        - CANCELLED
      description: Status of subscription
    AllocationRuleType:
      type: string
      enum:
        - RESPONSIBLE_PARTY
        - COVERAGE_TRANSFER
        - BILLING_CAP
      description: Type of allocation rule
    CapPeriod:
      type: string
      enum:
        - DAILY
        - WEEKLY
        - MONTHLY
      description: Period for billing cap calculation
    RemainderHandling:
      type: string
      enum:
        - DISTRIBUTE_PROPORTIONALLY
        - ASSIGN_TO_PRIMARY
        - LEAVE_UNALLOCATED
      description: How to handle remainder amounts after cap
    ChargeStatus:
      type: string
      enum:
        - PENDING
        - BILLED
        - VOID
      description: Status of mutable charge
    SettledChargeStatus:
      type: string
      enum:
        - INVOICED
        - PARTIALLY_PAID
        - PAID
        - REFUNDED
        - VOID
      description: Status of settled (immutable) charge
    SettlementType:
      type: string
      enum:
        - INVOICED
        - DIRECT_PAYMENT
      description: How the charge was settled
    JournalSource:
      type: string
      enum:
        - INVOICE
        - PAYMENT
        - REFUND
        - ADJUSTMENT
        - REMITTANCE
      description: Source of journal entry
    AdjustmentType:
      type: string
      enum:
        - STANDARD
        - DISCOUNT_CORRECTION
        - FEE_CORRECTION
        - CREDIT_MEMO
        - DEBIT_MEMO
        - WRITE_OFF
      description: Type of adjustment for manual entries
    AccountCode:
      type: string
      enum:
        - REVENUE
        - AR
        - CONTRA_REVENUE
        - CLEARING
        - BANK
        - FEE_EXPENSE
        - REFUND_EXPENSE
      description: Chart of accounts code
    JournalDirection:
      type: string
      enum:
        - DEBIT
        - CREDIT
      description: Direction of journal line
    Rate:
      type: object
      required:
        - id
        - entityId
        - description
        - type
        - createdAt
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier
        entityId:
          type: string
          format: uuid
          description: Merchant identifier (from auth context)
        description:
          type: string
          description: Human-readable description of the rate
        type:
          $ref: '#/components/schemas/RateType'
        pricePerUnit:
          type: integer
          description: >-
            Price per unit in cents (e.g., 5000 = $50.00). Required unless type
            is DISCOUNT.
        discountPercentage:
          type: number
          format: double
          minimum: 0
          maximum: 100
          description: Discount percentage (only for type DISCOUNT)
        tags:
          type: object
          additionalProperties:
            type: string
          description: Tags for categorization and reporting
        version:
          type: integer
          description: >-
            Business version number for audit history and deterministic billing
            (auto-incremented on updates)
          readOnly: true
        optimisticLockVersion:
          type: integer
          format: int64
          description: >-
            Optimistic locking version (managed by Hibernate @Version). Prevents
            concurrent update conflicts.
          readOnly: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    CreateRateRequest:
      type: object
      required:
        - description
        - type
      properties:
        description:
          type: string
          maxLength: 255
        type:
          $ref: '#/components/schemas/RateType'
        pricePerUnit:
          type: integer
          minimum: 0
          description: Required unless type is DISCOUNT
        discountPercentage:
          type: number
          format: double
          minimum: 0
          maximum: 100
          description: Required if type is DISCOUNT
        tags:
          type: object
          additionalProperties:
            type: string
    UpdateRateRequest:
      type: object
      properties:
        description:
          type: string
          maxLength: 255
        pricePerUnit:
          type: integer
          minimum: 0
        discountPercentage:
          type: number
          format: double
          minimum: 0
          maximum: 100
        tags:
          type: object
          additionalProperties:
            type: string
    RateListResponse:
      type: object
      required:
        - results
        - pagination
      properties:
        results:
          type: array
          items:
            $ref: '#/components/schemas/Rate'
        pagination:
          $ref: '#/components/schemas/Pagination'
    RateCard:
      type: object
      required:
        - id
        - entityId
        - name
        - scope
        - createdAt
      properties:
        id:
          type: string
          format: uuid
        entityId:
          type: string
          format: uuid
        name:
          type: string
        scope:
          $ref: '#/components/schemas/RateCardScope'
        tags:
          type: object
          additionalProperties:
            type: string
        optimisticLockVersion:
          type: integer
          format: int64
          description: >-
            Optimistic locking version (managed by Hibernate @Version). Prevents
            concurrent update conflicts.
          readOnly: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    RateCardWithRates:
      allOf:
        - $ref: '#/components/schemas/RateCard'
        - type: object
          properties:
            rates:
              type: array
              items:
                $ref: '#/components/schemas/RateCardRateEntry'
    RateCardRateEntry:
      type: object
      properties:
        rate:
          $ref: '#/components/schemas/Rate'
        displayOrder:
          type: integer
        addedAt:
          type: string
          format: date-time
    RateCardRate:
      type: object
      required:
        - rateCardId
        - rateId
        - createdAt
      properties:
        rateCardId:
          type: string
          format: uuid
        rateId:
          type: string
          format: uuid
        displayOrder:
          type: integer
        createdAt:
          type: string
          format: date-time
    CreateRateCardRequest:
      type: object
      required:
        - name
        - scope
      properties:
        name:
          type: string
          maxLength: 255
        scope:
          $ref: '#/components/schemas/RateCardScope'
        tags:
          type: object
          additionalProperties:
            type: string
    UpdateRateCardRequest:
      type: object
      properties:
        name:
          type: string
          maxLength: 255
        scope:
          $ref: '#/components/schemas/RateCardScope'
        tags:
          type: object
          additionalProperties:
            type: string
    RateCardListResponse:
      type: object
      required:
        - results
        - pagination
      properties:
        results:
          type: array
          items:
            $ref: '#/components/schemas/RateCard'
        pagination:
          $ref: '#/components/schemas/Pagination'
    Subscription:
      type: object
      required:
        - id
        - entityId
        - billableEntityId
        - rateId
        - allocationConfigId
        - billingFrequency
        - billingAnchorDay
        - billingTimezone
        - startDate
        - status
        - version
        - createdAt
      properties:
        id:
          type: string
          format: uuid
        entityId:
          type: string
          format: uuid
        billableEntityId:
          type: string
          format: uuid
          description: Reference to BillableEntity in Profile Service
        rateId:
          type: string
          format: uuid
        allocationConfigId:
          type: string
          format: uuid
        discountRateIds:
          type: array
          items:
            type: string
            format: uuid
          description: Discount rates to apply
        billingFrequency:
          $ref: '#/components/schemas/BillingFrequency'
        billingAnchorDay:
          type: integer
          minimum: 1
          maximum: 31
          description: >-
            Day of week (1-7) for WEEKLY/BIWEEKLY, day of month (1-31) for
            MONTHLY
        billingTimezone:
          type: string
          description: IANA timezone for billing calculations
          example: America/New_York
        startDate:
          type: string
          format: date
        endDate:
          type:
            - string
            - 'null'
          format: date
        status:
          $ref: '#/components/schemas/SubscriptionStatus'
        version:
          type: integer
          description: >-
            Business version number for change tracking (subscription
            configuration history)
        optimisticLockVersion:
          type: integer
          format: int64
          description: >-
            Optimistic locking version (managed by Hibernate @Version). Prevents
            concurrent update conflicts.
          readOnly: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    CreateSubscriptionRequest:
      type: object
      required:
        - billableEntityId
        - rateId
        - allocationConfigId
        - billingFrequency
        - billingAnchorDay
        - billingTimezone
        - startDate
      properties:
        billableEntityId:
          type: string
          format: uuid
        rateId:
          type: string
          format: uuid
        allocationConfigId:
          type: string
          format: uuid
        discountRateIds:
          type: array
          items:
            type: string
            format: uuid
        billingFrequency:
          $ref: '#/components/schemas/BillingFrequency'
        billingAnchorDay:
          type: integer
          minimum: 1
          maximum: 31
        billingTimezone:
          type: string
        startDate:
          type: string
          format: date
        endDate:
          type: string
          format: date
    UpdateSubscriptionRequest:
      type: object
      properties:
        rateId:
          type: string
          format: uuid
        allocationConfigId:
          type: string
          format: uuid
        discountRateIds:
          type: array
          items:
            type: string
            format: uuid
        billingFrequency:
          $ref: '#/components/schemas/BillingFrequency'
        billingAnchorDay:
          type: integer
          minimum: 1
          maximum: 31
        billingTimezone:
          type: string
        endDate:
          type: string
          format: date
    SubscriptionListResponse:
      type: object
      required:
        - results
        - pagination
      properties:
        results:
          type: array
          items:
            $ref: '#/components/schemas/Subscription'
        pagination:
          $ref: '#/components/schemas/Pagination'
    AllocationConfiguration:
      type: object
      required:
        - id
        - entityId
        - name
        - version
        - rules
        - createdAt
      properties:
        id:
          type: string
          format: uuid
        entityId:
          type: string
          format: uuid
        name:
          type: string
        description:
          type: string
        version:
          type: integer
          description: Business version number for allocation configuration history
        effectiveFrom:
          type: string
          format: date-time
        rules:
          type: array
          items:
            $ref: '#/components/schemas/AllocationRule'
        optimisticLockVersion:
          type: integer
          format: int64
          description: >-
            Optimistic locking version (managed by Hibernate @Version). Prevents
            concurrent update conflicts.
          readOnly: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    AllocationRule:
      type: object
      required:
        - id
        - priority
        - ruleType
        - accountId
      properties:
        id:
          type: string
          format: uuid
        priority:
          type: integer
          minimum: 1
          description: Processing order (1 = first)
        ruleType:
          $ref: '#/components/schemas/AllocationRuleType'
        accountId:
          type: string
          format: uuid
          description: Reference to Account in Profile Service
        percentage:
          type: number
          format: double
          minimum: 0
          maximum: 100
          description: For RESPONSIBLE_PARTY rules
        fixedAmount:
          type: integer
          minimum: 0
          description: For COVERAGE_TRANSFER rules (in cents)
        capAmount:
          type: integer
          minimum: 0
          description: For BILLING_CAP rules (in cents)
        capPeriod:
          $ref: '#/components/schemas/CapPeriod'
        remainderHandling:
          $ref: '#/components/schemas/RemainderHandling'
    CreateAllocationConfigurationRequest:
      type: object
      required:
        - name
        - rules
      properties:
        name:
          type: string
          maxLength: 255
        description:
          type: string
          maxLength: 1000
        rules:
          type: array
          items:
            $ref: '#/components/schemas/CreateAllocationRuleRequest'
          minItems: 1
    CreateAllocationRuleRequest:
      type: object
      required:
        - priority
        - ruleType
        - accountId
      properties:
        priority:
          type: integer
          minimum: 1
        ruleType:
          $ref: '#/components/schemas/AllocationRuleType'
        accountId:
          type: string
          format: uuid
        percentage:
          type: number
          format: double
          minimum: 0
          maximum: 100
        fixedAmount:
          type: integer
          minimum: 0
        capAmount:
          type: integer
          minimum: 0
        capPeriod:
          $ref: '#/components/schemas/CapPeriod'
        remainderHandling:
          $ref: '#/components/schemas/RemainderHandling'
    UpdateAllocationConfigurationRequest:
      type: object
      properties:
        name:
          type: string
          maxLength: 255
        description:
          type: string
          maxLength: 1000
        rules:
          type: array
          items:
            $ref: '#/components/schemas/CreateAllocationRuleRequest'
          minItems: 1
    AllocationConfigurationListResponse:
      type: object
      required:
        - results
        - pagination
      properties:
        results:
          type: array
          items:
            $ref: '#/components/schemas/AllocationConfiguration'
        pagination:
          $ref: '#/components/schemas/Pagination'
    AllocationValidationResult:
      type: object
      required:
        - valid
      properties:
        valid:
          type: boolean
          description: >-
            Whether the allocation configuration is valid for the billable
            entity
        totalPercentage:
          type: number
          format: double
          description: Total RESPONSIBLE_PARTY percentage coverage
        errors:
          type: array
          items:
            type: object
            properties:
              code:
                type: string
              message:
                type: string
              accountId:
                type: string
                format: uuid
        warnings:
          type: array
          items:
            type: object
            properties:
              code:
                type: string
              message:
                type: string
    Charge:
      type: object
      required:
        - id
        - entityId
        - billableEntityId
        - rateId
        - quantity
        - amount
        - netAmount
        - allocationConfigId
        - status
        - eventDate
        - createdAt
      properties:
        id:
          type: string
          format: uuid
        entityId:
          type: string
          format: uuid
        billableEntityId:
          type: string
          format: uuid
        accountId:
          type: string
          format: uuid
          description: Primary account for this charge (from allocation)
        subscriptionId:
          type:
            - string
            - 'null'
          format: uuid
          description: Subscription that generated this charge (if any)
        rateId:
          type: string
          format: uuid
        quantity:
          type: number
          format: double
        amount:
          type: integer
          description: Amount in cents BEFORE discounts
        prorationFactor:
          type: number
          format: double
          default: 1
          description: Proration factor (e.g., 0.5 for half month)
        proratedAmount:
          type: integer
          description: Amount after proration (in cents)
        netAmount:
          type: integer
          description: Final amount after discounts (in cents)
        discountRateIds:
          type: array
          items:
            type: string
            format: uuid
        discountAmounts:
          type: array
          items:
            type: integer
          description: Discount amounts in cents (parallel to discountRateIds)
        allocationConfigId:
          type: string
          format: uuid
        overrideAllocation:
          type: object
          additionalProperties: true
          description: Manual allocation override (optional)
        status:
          $ref: '#/components/schemas/ChargeStatus'
        eventDate:
          type: string
          format: date
          description: Date of the event that generated this charge
        rateVersion:
          type: integer
          description: Version of rate when charge was created
        subscriptionVersion:
          type: integer
          description: Version of subscription when charge was created
        allocationVersion:
          type: integer
          description: Version of allocation config when charge was created
        discountRateVersions:
          type: array
          items:
            type: integer
          description: >-
            Versions of discount rates when charge was created (parallel to
            discountRateIds)
        tags:
          type: object
          additionalProperties:
            type: string
        optimisticLockVersion:
          type: integer
          format: int64
          description: >-
            Optimistic locking version (managed by Hibernate @Version). Prevents
            concurrent update conflicts.
          readOnly: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    CreateChargeRequest:
      type: object
      required:
        - billableEntityId
        - rateId
        - quantity
        - allocationConfigId
        - eventDate
      properties:
        billableEntityId:
          type: string
          format: uuid
        subscriptionId:
          type: string
          format: uuid
        rateId:
          type: string
          format: uuid
        quantity:
          type: number
          format: double
          minimum: 0
        prorationFactor:
          type: number
          format: double
          minimum: 0
          maximum: 1
          default: 1
        allocationConfigId:
          type: string
          format: uuid
        discountRateIds:
          type: array
          items:
            type: string
            format: uuid
        overrideAllocation:
          type: object
          additionalProperties: true
        eventDate:
          type: string
          format: date
        tags:
          type: object
          additionalProperties:
            type: string
    UpdateChargeRequest:
      type: object
      properties:
        quantity:
          type: number
          format: double
          minimum: 0
        prorationFactor:
          type: number
          format: double
          minimum: 0
          maximum: 1
        discountRateIds:
          type: array
          items:
            type: string
            format: uuid
        overrideAllocation:
          type: object
          additionalProperties: true
        eventDate:
          type: string
          format: date
        tags:
          type: object
          additionalProperties:
            type: string
    ChargeListResponse:
      type: object
      required:
        - results
        - pagination
      properties:
        results:
          type: array
          items:
            $ref: '#/components/schemas/Charge'
        pagination:
          $ref: '#/components/schemas/Pagination'
    BulkChargeError:
      type: object
      properties:
        type:
          type: string
        title:
          type: string
        status:
          type: integer
        detail:
          type: string
        errors:
          type: array
          items:
            type: object
            properties:
              index:
                type: integer
                description: Index of failed charge in request array
              code:
                type: string
              message:
                type: string
    SettledCharge:
      type: object
      required:
        - id
        - originalChargeId
        - entityId
        - billableEntityId
        - accountId
        - settlementType
        - status
        - originalAmount
        - resolvedAmount
        - amountPaid
        - amountOutstanding
        - settledAt
        - createdAt
      properties:
        id:
          type: string
          format: uuid
        originalChargeId:
          type: string
          format: uuid
        entityId:
          type: string
          format: uuid
        billableEntityId:
          type: string
          format: uuid
        accountId:
          type: string
          format: uuid
        invoiceId:
          type:
            - string
            - 'null'
          format: uuid
        rateId:
          type: string
          format: uuid
          description: Rate used for this charge
        quantity:
          type: number
          format: double
          description: Quantity billed
        amount:
          type: integer
          description: Full charge amount before proration (cents)
        prorationFactor:
          type: number
          format: double
          description: Proration factor (0.0-1.0)
        proratedAmount:
          type: integer
          description: Amount after proration (cents)
        netAmount:
          type: integer
          description: Final amount after discounts (cents)
        discountRateIds:
          type: array
          items:
            type: string
            format: uuid
          description: Discount rates applied
        discountAmounts:
          type: array
          items:
            type: integer
          description: Discount amounts in cents (parallel to discountRateIds)
        allocationConfigId:
          type:
            - string
            - 'null'
          format: uuid
          description: Allocation configuration used
        rateVersion:
          type: integer
          description: Version of rate at settlement time
        subscriptionVersion:
          type:
            - integer
            - 'null'
          description: Version of subscription at settlement time
        allocationVersion:
          type:
            - integer
            - 'null'
          description: Version of allocation config at settlement time
        discountRateVersions:
          type: array
          items:
            type: integer
          description: Versions of discount rates at settlement time
        settlementType:
          $ref: '#/components/schemas/SettlementType'
        status:
          $ref: '#/components/schemas/SettledChargeStatus'
        originalAmount:
          type: integer
          description: Original charge amount in cents
        resolvedAmount:
          type: integer
          description: Amount after allocation resolution in cents
        amountPaid:
          type: integer
          description: Amount paid so far in cents
        amountOutstanding:
          type: integer
          description: Remaining amount in cents
        resolvedAllocation:
          type: object
          additionalProperties: true
          description: Frozen snapshot of allocation at settlement time
        resolvedRate:
          type: object
          additionalProperties: true
          description: Frozen snapshot of rate at settlement time
        consolidatedTags:
          type: object
          additionalProperties:
            type: string
          description: |
            Tags consolidated from all sources with prefixes:
            - profile.billableEntity.*
            - profile.account.*
            - billing.rate.*
            - billing.subscription.*
            - billing.charge.*
        settledAt:
          type: string
          format: date-time
        optimisticLockVersion:
          type: integer
          format: int64
          description: >-
            Optimistic locking version (managed by Hibernate @Version). Prevents
            concurrent update conflicts.
          readOnly: true
        createdAt:
          type: string
          format: date-time
    SettledChargeListResponse:
      type: object
      required:
        - results
        - pagination
      properties:
        results:
          type: array
          items:
            $ref: '#/components/schemas/SettledCharge'
        pagination:
          $ref: '#/components/schemas/Pagination'
    SettledChargePayment:
      type: object
      required:
        - id
        - settledChargeId
        - paymentId
        - amountApplied
        - appliedAt
        - applicationStatus
        - createdAt
      properties:
        id:
          type: string
          format: uuid
        settledChargeId:
          type: string
          format: uuid
        paymentId:
          type: string
          format: uuid
          description: Payment reference from OSI Payment Service
        paymentTransactionId:
          type:
            - string
            - 'null'
          description: Gateway transaction ID
        amountApplied:
          type: integer
          description: Amount in cents applied to this charge
        appliedAt:
          type: string
          format: date-time
          description: When payment was applied
        applicationStatus:
          type: string
          enum:
            - APPLIED
            - REVERSED
            - ADJUSTED
          description: Status of the payment application
        reversalReason:
          type:
            - string
            - 'null'
          description: Reason if payment was reversed
        reversalPaymentId:
          type:
            - string
            - 'null'
          format: uuid
          description: Reference to reversal payment
        createdAt:
          type: string
          format: date-time
    SettledChargePaymentListResponse:
      type: object
      required:
        - results
        - pagination
      properties:
        results:
          type: array
          items:
            $ref: '#/components/schemas/SettledChargePayment'
        pagination:
          $ref: '#/components/schemas/Pagination'
    JournalEntry:
      type: object
      required:
        - id
        - entityId
        - entryDate
        - currency
        - source
        - totalAmount
        - isBalanced
        - lines
        - createdAt
      properties:
        id:
          type: string
          format: uuid
        entityId:
          type: string
          format: uuid
        entryDate:
          type: string
          format: date-time
        currency:
          type: string
          description: ISO 4217 currency code
          default: USD
        source:
          $ref: '#/components/schemas/JournalSource'
        sourceReference:
          type: string
          description: Reference to source document (e.g., invoice ID)
        references:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
              format: uuid
          description: |
            Nested map of invoice IDs to charge IDs for tracing.
            Structure: { "invoiceId": ["chargeId1", "chargeId2"], ... }
            Enables tracking when a payment applies to multiple invoices.
        adjustmentType:
          $ref: '#/components/schemas/AdjustmentType'
        reason:
          type: string
          description: Reason for adjustment (if applicable)
        adminUserId:
          type: string
          description: Admin user who created the entry (for adjustments)
        relatedSourceRef:
          type: string
          description: Reference to related entries
        totalAmount:
          type: integer
          description: Total amount of the entry in cents
        isBalanced:
          type: boolean
          description: Whether debits equal credits
        lines:
          type: array
          items:
            $ref: '#/components/schemas/JournalLine'
        optimisticLockVersion:
          type: integer
          format: int64
          description: >-
            Optimistic locking version (managed by Hibernate @Version). Prevents
            concurrent update conflicts.
          readOnly: true
        createdAt:
          type: string
          format: date-time
    JournalLine:
      type: object
      required:
        - id
        - accountCode
        - direction
        - amount
      properties:
        id:
          type: string
          format: uuid
        accountCode:
          $ref: '#/components/schemas/AccountCode'
        externalAccountCode:
          type: string
          description: External GL account code for export
        direction:
          $ref: '#/components/schemas/JournalDirection'
        amount:
          type: integer
          description: Amount in cents (always positive)
        description:
          type: string
        billableEntityId:
          type: string
          format: uuid
        accountId:
          type: string
          format: uuid
        invoiceId:
          type: string
          format: uuid
        dimensions:
          type: object
          additionalProperties:
            type: string
          description: Reporting dimensions
        tags:
          type: object
          additionalProperties:
            type: string
    CreateJournalEntryRequest:
      type: object
      required:
        - entryDate
        - source
        - lines
      properties:
        entryDate:
          type: string
          format: date-time
        currency:
          type: string
          default: USD
        source:
          $ref: '#/components/schemas/JournalSource'
        sourceReference:
          type: string
        adjustmentType:
          $ref: '#/components/schemas/AdjustmentType'
        reason:
          type: string
          maxLength: 1000
        relatedSourceRef:
          type: string
        lines:
          type: array
          items:
            $ref: '#/components/schemas/CreateJournalLineRequest'
          minItems: 2
    CreateJournalLineRequest:
      type: object
      required:
        - accountCode
        - direction
        - amount
      properties:
        accountCode:
          $ref: '#/components/schemas/AccountCode'
        externalAccountCode:
          type: string
        direction:
          $ref: '#/components/schemas/JournalDirection'
        amount:
          type: integer
          minimum: 1
        description:
          type: string
        billableEntityId:
          type: string
          format: uuid
        accountId:
          type: string
          format: uuid
        invoiceId:
          type: string
          format: uuid
        dimensions:
          type: object
          additionalProperties:
            type: string
        tags:
          type: object
          additionalProperties:
            type: string
    JournalEntryListResponse:
      type: object
      required:
        - results
        - pagination
      properties:
        results:
          type: array
          items:
            $ref: '#/components/schemas/JournalEntry'
        pagination:
          $ref: '#/components/schemas/Pagination'
    AccountBalancesResponse:
      type: object
      properties:
        asOfDate:
          type: string
          format: date-time
        balances:
          type: array
          items:
            type: object
            properties:
              accountCode:
                $ref: '#/components/schemas/AccountCode'
              debitBalance:
                type: integer
              creditBalance:
                type: integer
              netBalance:
                type: integer
                description: Debit - Credit
    TrialBalanceResponse:
      type: object
      properties:
        periodStart:
          type: string
          format: date
        periodEnd:
          type: string
          format: date
        accounts:
          type: array
          items:
            type: object
            properties:
              accountCode:
                $ref: '#/components/schemas/AccountCode'
              openingBalance:
                type: integer
              debits:
                type: integer
              credits:
                type: integer
              closingBalance:
                type: integer
        totalDebits:
          type: integer
        totalCredits:
          type: integer
        isBalanced:
          type: boolean
    RefundRequest:
      type: object
      required:
        - refundType
        - amount
      properties:
        refundType:
          type: string
          enum:
            - INVOICE
            - CHARGE
            - STANDALONE
          description: Type of refund to process
        invoiceIds:
          type: array
          items:
            type: string
            format: uuid
          description: Invoice IDs to refund (required if refundType is INVOICE)
        chargeIds:
          type: array
          items:
            type: string
            format: uuid
          description: Charge IDs to refund (required if refundType is CHARGE)
        accountId:
          type: string
          format: uuid
          description: Account to refund to (required for STANDALONE)
        amount:
          type: integer
          minimum: 1
          description: Refund amount in cents
        reason:
          type: string
          description: Reason for the refund
          maxLength: 500
        sendReceipt:
          type: boolean
          default: false
          description: Whether to send a receipt via Receipt Service
        metadata:
          type: object
          additionalProperties:
            type: string
          description: Additional metadata for the refund
    Refund:
      type: object
      required:
        - id
        - entityId
        - refundType
        - amount
        - status
        - createdAt
      properties:
        id:
          type: string
          format: uuid
        entityId:
          type: string
          format: uuid
        refundType:
          type: string
          enum:
            - INVOICE
            - CHARGE
            - STANDALONE
        invoiceIds:
          type: array
          items:
            type: string
            format: uuid
        chargeIds:
          type: array
          items:
            type: string
            format: uuid
        accountId:
          type: string
          format: uuid
        amount:
          type: integer
        status:
          type: string
          enum:
            - PENDING
            - PROCESSING
            - COMPLETED
            - FAILED
            - CANCELLED
        reason:
          type: string
        paymentTransactionId:
          type: string
          description: Payment gateway transaction ID
        journalEntryId:
          type: string
          format: uuid
          description: Linked journal entry
        failureReason:
          type: string
          description: Reason for failure (if status is FAILED)
        metadata:
          type: object
          additionalProperties:
            type: string
        optimisticLockVersion:
          type: integer
          format: int64
          description: >-
            Optimistic locking version (managed by Hibernate @Version). Prevents
            concurrent update conflicts.
          readOnly: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        completedAt:
          type: string
          format: date-time
    RefundListResponse:
      type: object
      required:
        - results
        - pagination
      properties:
        results:
          type: array
          items:
            $ref: '#/components/schemas/Refund'
        pagination:
          $ref: '#/components/schemas/Pagination'
    RefundStatus:
      type: object
      required:
        - refundId
        - status
      properties:
        refundId:
          type: string
          format: uuid
        status:
          type: string
          enum:
            - PENDING
            - PROCESSING
            - COMPLETED
            - FAILED
            - CANCELLED
        paymentGatewayStatus:
          type: string
          description: Status from payment gateway
        lastCheckedAt:
          type: string
          format: date-time
    AdjustmentRequest:
      type: object
      required:
        - adjustmentType
        - amount
        - reason
      properties:
        adjustmentType:
          type: string
          enum:
            - DISCOUNT_CORRECTION
            - FEE_CORRECTION
            - CREDIT_MEMO
            - DEBIT_MEMO
            - WRITE_OFF
          description: Type of adjustment
        accountId:
          type: string
          format: uuid
          description: Account to adjust
        chargeId:
          type: string
          format: uuid
          description: Related charge (optional)
        invoiceId:
          type: string
          format: uuid
          description: Related invoice (optional)
        amount:
          type: integer
          minimum: 1
          description: Adjustment amount in cents
        reason:
          type: string
          description: Detailed reason for adjustment
          maxLength: 1000
        debitAccountCode:
          type: string
          description: Account code to debit
        creditAccountCode:
          type: string
          description: Account code to credit
        metadata:
          type: object
          additionalProperties:
            type: string
          description: Additional metadata for audit
    Adjustment:
      type: object
      required:
        - id
        - entityId
        - adjustmentType
        - amount
        - reason
        - adminUserId
        - status
        - createdAt
      properties:
        id:
          type: string
          format: uuid
        entityId:
          type: string
          format: uuid
        adjustmentType:
          type: string
          enum:
            - DISCOUNT_CORRECTION
            - FEE_CORRECTION
            - CREDIT_MEMO
            - DEBIT_MEMO
            - WRITE_OFF
        accountId:
          type: string
          format: uuid
        chargeId:
          type: string
          format: uuid
        invoiceId:
          type: string
          format: uuid
        amount:
          type: integer
        reason:
          type: string
        adminUserId:
          type: string
          description: ID of admin who created the adjustment
        adminUserEmail:
          type: string
          description: Email of admin who created the adjustment
        status:
          type: string
          enum:
            - PENDING
            - APPROVED
            - APPLIED
            - REJECTED
        journalEntryId:
          type: string
          format: uuid
          description: Linked journal entry (if applied)
        debitAccountCode:
          type: string
        creditAccountCode:
          type: string
        approvedBy:
          type: string
          description: ID of admin who approved (if required)
        approvedAt:
          type: string
          format: date-time
        appliedAt:
          type: string
          format: date-time
        metadata:
          type: object
          additionalProperties:
            type: string
        optimisticLockVersion:
          type: integer
          format: int64
          description: >-
            Optimistic locking version (managed by Hibernate @Version). Prevents
            concurrent update conflicts.
          readOnly: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    AdjustmentListResponse:
      type: object
      required:
        - results
        - pagination
      properties:
        results:
          type: array
          items:
            $ref: '#/components/schemas/Adjustment'
        pagination:
          $ref: '#/components/schemas/Pagination'
    ErrorResponse:
      type: object
      required:
        - result
        - error
      properties:
        result:
          type: object
          required:
            - status
          properties:
            status:
              type: string
              enum:
                - ERROR
              description: Response status (always ERROR for error responses)
        error:
          type: object
          required:
            - responseCode
            - responseMessage
          properties:
            responseCode:
              type: string
              description: >-
                Response code (numeric or contains numbers, e.g., "404", "409",
                "500", "ERR001")
              example: '404'
            responseMessage:
              type: array
              items:
                type: string
              description: Array of error message strings for multiple error details
              example:
                - Charge not found
                - The requested charge ID does not exist in the system
  responses:
    BadRequest:
      description: Bad Request - Invalid request format or parameters
      headers:
        X-RateLimit-Limit:
          description: Maximum requests allowed per window
          schema:
            type: integer
        X-RateLimit-Remaining:
          description: Requests remaining in current window
          schema:
            type: integer
        X-RateLimit-Reset:
          description: Unix timestamp when the rate limit resets
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            result:
              status: ERROR
            error:
              responseCode: '400'
              responseMessage:
                - Request body contains invalid JSON
    Unauthorized:
      description: Unauthorized - Authentication required or failed
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            result:
              status: ERROR
            error:
              responseCode: '401'
              responseMessage:
                - Invalid or expired access token
    Forbidden:
      description: Forbidden - Insufficient permissions or cross-merchant access
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            result:
              status: ERROR
            error:
              responseCode: '403'
              responseMessage:
                - You do not have permission to access this resource
    NotFound:
      description: Not Found - Resource does not exist
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            result:
              status: ERROR
            error:
              responseCode: '404'
              responseMessage:
                - The requested resource was not found
    Conflict:
      description: Conflict - Resource state conflict
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            result:
              status: ERROR
            error:
              responseCode: '409'
              responseMessage:
                - Cannot delete rate that is referenced by active subscriptions
    UnprocessableEntity:
      description: Bad Request - Business validation failed
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            result:
              status: ERROR
            error:
              responseCode: '422'
              responseMessage:
                - One or more validation errors occurred
    TooManyRequests:
      description: Rate limit exceeded
      headers:
        X-RateLimit-Limit:
          description: Maximum requests allowed per window
          schema:
            type: integer
        X-RateLimit-Remaining:
          description: Requests remaining in current window
          schema:
            type: integer
        X-RateLimit-Reset:
          description: Unix timestamp when the rate limit resets
          schema:
            type: integer
        Retry-After:
          description: Seconds to wait before retrying
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            result:
              status: ERROR
            error:
              responseCode: '429'
              responseMessage:
                - Rate limit exceeded. Please wait before retrying.
webhooks:
  ledgerJournalEntryCreated:
    post:
      summary: Journal Entry Created
      description: |
        Fired when a new journal entry is created.

        **Use Cases:**
        - Real-time GL integration
        - Audit trail synchronization
        - External accounting system updates

        **Security:** Payload is signed with HMAC-SHA256. 
        Verify using the `X-NPS-Signature` header.
      operationId: webhookJournalEntryCreated
      tags:
        - Ledger
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                id:
                  type: string
                  format: uuid
                  description: Unique event ID (use for idempotency)
                type:
                  type: string
                  enum:
                    - ledger.journal_entry.created
                occurredAt:
                  type: string
                  format: date-time
                data:
                  $ref: '#/components/schemas/JournalEntry'
      responses:
        '200':
          description: Webhook processed successfully
