openapi: 3.0.3
info:
  title: PUP - Ledger API v1
  description: >
    PUP is the universal player profile for padel. This API provides access to ledger accounts and transaction history.
  version: 1.0.0

servers:
  - url: https://api.gopup.app
    description: Development server

paths:
  /ledger/v1/accounts:
    get:
      tags:
        - Ledger
      operationId: getAccounts
      summary: List/search ledger accounts
      description: Returns a paginated list of all ledger accounts matching the filters. Restricted to platform administrators.
      security:
        - OAuth2:
            - urn:pup:platform.finances:read
        - PAT: []
      parameters:
        - name: account_type
          in: query
          required: false
          description: Comma-separated list of account types (e.g. platform,treasury)
          schema:
            type: string
            example: "platform,treasury"
        - name: currency
          in: query
          required: false
          schema:
            type: string
            example: "PUP"
        - name: status
          in: query
          required: false
          schema:
            type: string
            enum: [open, closed]
            example: "open"
        - name: owner_id
          in: query
          required: false
          schema:
            type: string
            example: "system"
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            format: int32
            default: 20
        - name: offset
          in: query
          required: false
          schema:
            type: integer
            format: int32
            default: 0
      responses:
        '200':
          description: Accounts retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccountsResponse'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        default:
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /ledger/v1/accounts/{account_id}:
    get:
      tags:
        - Ledger
      operationId: getAccountDetails
      summary: Get ledger account details
      description: Returns balance and metadata for a specific ledger account by ID.
      security:
        - OAuth2:
            - urn:pup:user.wallet:read
        - OAuth2:
            - urn:pup:club.wallet:read
        - OAuth2:
            - urn:pup:seller.wallet:read
        - OAuth2:
            - urn:pup:platform.finances:read
        - PAT: []
      parameters:
        - name: account_id
          in: path
          required: true
          schema:
            type: string
            example: "platform:tax_revenue"
      responses:
        '200':
          description: Account details retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccountResponse'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        default:
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /ledger/v1/accounts/{account_id}/transactions:
    get:
      tags:
        - Ledger
      operationId: getAccountTransactions
      summary: Search transactions for a ledger account
      description: Returns paginated transactions for a specific ledger account.
      security:
        - OAuth2:
            - urn:pup:user.wallet:read
        - OAuth2:
            - urn:pup:club.wallet:read
        - OAuth2:
            - urn:pup:seller.wallet:read
        - OAuth2:
            - urn:pup:platform.finances:read
        - PAT: []
      parameters:
        - name: account_id
          in: path
          required: true
          schema:
            type: string
            example: "platform:tax_revenue"
        - name: transaction_type
          in: query
          required: false
          schema:
            type: string
            example: "platform_fee"
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            format: int32
            default: 20
        - name: date_range
          in: query
          required: false
          schema:
            $ref: '#/components/schemas/WalletTransactionDateRange'
        - name: year_month
          in: query
          required: false
          schema:
            type: string
            pattern: '^\d{4}-\d{2}$'
            example: "2026-06"
        - name: after_id
          in: query
          required: false
          schema:
            type: string
            example: "018f23b2-6a18-7c6e-b5f2-7a22b5f7a001"
        - name: before_id
          in: query
          required: false
          schema:
            type: string
            example: "018f23b2-6a18-7c6e-b5f2-7a22b5f7a001"
      responses:
        '200':
          description: Account transactions retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransactionsResponse'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        default:
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

components:
  securitySchemes:
    OAuth2:
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: https://oauth2.gopup.app/oauth2/auth
          tokenUrl: https://oauth2.gopup.app/oauth2/token
          scopes:
            urn:pup:platform.finances:read: Read platform accounts and transaction history
            urn:pup:user.wallet:read: Read user wallet details and transactions
            urn:pup:club.wallet:read: Read club wallet details and transactions
            urn:pup:seller.wallet:read: Read seller wallet details and transactions
    PAT:
      type: http
      scheme: bearer
      bearerFormat: PAT
      description: Personal Access Token

  schemas:
    AccountsResponse:
      type: object
      required:
        - hits
        - meta
      properties:
        hits:
          type: array
          items:
            $ref: '#/components/schemas/Account'
        meta:
          $ref: '#/components/schemas/AccountsResponseMeta'

    AccountsResponseMeta:
      type: object
      required:
        - pagination
      properties:
        pagination:
          $ref: '#/components/schemas/OffsetPagination'

    OffsetPagination:
      type: object
      required:
        - total
        - returned
        - offset
        - limit
      properties:
        total:
          $ref: '#/components/schemas/CollectionTotal'
        returned:
          type: integer
          format: int32
          description: Number of items in this response
        offset:
          type: integer
          format: int32
          description: Offset used in the request
        limit:
          type: integer
          format: int32
          description: Limit used in the request

    AccountResponse:
      type: object
      required:
        - account
      properties:
        account:
          $ref: '#/components/schemas/Account'

    Account:
      type: object
      required:
        - account_id
        - account_type
        - owner_id
        - currency
        - status
        - balance
        - opened_at
      properties:
        account_id:
          type: string
          example: "platform:tax_revenue"
        account_type:
          type: string
          example: "platform"
        owner_id:
          type: string
          example: "system"
        currency:
          type: string
          example: "PUP"
        status:
          type: string
          example: "open"
        balance:
          type: integer
          format: int64
          example: 500000
        opened_at:
          type: string
          format: date-time
          example: "2026-06-18T10:00:00Z"
        closed_at:
          type: string
          format: date-time
          nullable: true
          example: null

    TransactionsResponse:
      type: object
      required:
        - hits
        - meta
      properties:
        hits:
          type: array
          items:
            $ref: '#/components/schemas/Transaction'
        meta:
          $ref: '#/components/schemas/TransactionsResponseMeta'

    TransactionsResponseMeta:
      type: object
      required:
        - pagination
      properties:
        pagination:
          $ref: '#/components/schemas/TransactionPagination'

    Transaction:
      type: object
      required:
        - transaction_id
        - type
        - amount
        - counterparty_account_id
        - description
        - created_at
      properties:
        transaction_id:
          type: string
          example: "tx_018f23b2-6a18-7c6e-b5f2-7a22b5f7a001"
        type:
          type: string
          example: "platform_fee"
        amount:
          type: integer
          format: int64
          example: 250
        counterparty_account_id:
          type: string
          example: "club:club-123"
        metadata:
          type: object
          additionalProperties:
            type: string
          nullable: true
          description: Optional metadata key-value pairs associated with the transaction
        context:
          allOf:
            - $ref: '#/components/schemas/TransactionContext'
          nullable: true
          description: Immutable structured context for client-side transaction rendering
        description:
          type: string
          example: "Platform fee on booking"
        created_at:
          type: string
          format: date-time
          example: "2026-06-18T10:30:00Z"

    TransactionPagination:
      type: object
      required:
        - limit
      properties:
        limit:
          type: integer
          format: int32
          description: Limit used in the request
        next:
          type: string
          nullable: true
          description: Relative path to next page
        previous:
          type: string
          nullable: true
          description: Relative path to previous page

    WalletTransactionDateRange:
      type: string
      enum:
        - today
        - yesterday
        - this_week
        - last_week
        - last_month
        - last_30_days
        - last_3_months
      example: last_30_days

    CollectionTotal:
      type: object
      required:
        - value
        - relation
      properties:
        value:
          type: integer
          format: int64
          description: Exact total count or lower-bound count for the full result set
          example: 145
        relation:
          type: string
          description: Whether value is exact or a lower bound
          enum:
            - exact
            - gte

    Error:
      type: object
      required:
        - error
        - code
      properties:
        error:
          type: string
          example: "An error message"
        code:
          type: string
          enum:
            - MALFORMED_REQUEST
            - INVALID_INPUT
            - UNAUTHORIZED
            - INSUFFICIENT_SCOPE
            - ACCESS_DENIED
            - RESOURCE_NOT_FOUND
            - RESOURCE_EXISTS
            - CONFLICT
            - ACTION_BLOCKED
            - INTERNAL_ERROR
        details:
          type: string
          nullable: true
          example: "Optional context or validation details"
      description: Generic error response

    TransactionContext:
      oneOf:
        - $ref: '#/components/schemas/OrderPurchaseTransactionContext'
        - $ref: '#/components/schemas/OrderRefundTransactionContext'
        - $ref: '#/components/schemas/OrderTaxTransactionContext'
        - $ref: '#/components/schemas/OrderTaxRefundTransactionContext'
        - $ref: '#/components/schemas/ChallengeRewardTransactionContext'
        - $ref: '#/components/schemas/VoucherFundingTransactionContext'
        - $ref: '#/components/schemas/EscrowFundingTransactionContext'
        - $ref: '#/components/schemas/POSEarningTransactionContext'
      discriminator:
        propertyName: kind
        mapping:
          order_purchase: '#/components/schemas/OrderPurchaseTransactionContext'
          order_refund: '#/components/schemas/OrderRefundTransactionContext'
          order_tax: '#/components/schemas/OrderTaxTransactionContext'
          order_tax_refund: '#/components/schemas/OrderTaxRefundTransactionContext'
          challenge_reward: '#/components/schemas/ChallengeRewardTransactionContext'
          voucher_funding: '#/components/schemas/VoucherFundingTransactionContext'
          escrow_funding: '#/components/schemas/EscrowFundingTransactionContext'
          pos_earning: '#/components/schemas/POSEarningTransactionContext'

    OrderPurchaseTransactionContext:
      type: object
      required:
        - kind
        - source
        - reference
        - details
      properties:
        kind:
          type: string
          enum: [order_purchase]
          example: "order_purchase"
        source:
          type: string
          enum: [order]
          example: "order"
        reference:
          $ref: '#/components/schemas/OrderTransactionContextReference'
        details:
          $ref: '#/components/schemas/OrderPurchaseTransactionContextDetails'

    OrderPurchaseTransactionContextDetails:
      type: object
      required:
        - order_id
        - reward_id
        - reward_title
        - price_amount
      properties:
        order_id:
          type: string
          example: "order-123"
        reward_id:
          type: string
          example: "reward-456"
        reward_title:
          type: string
          example: "Free Court Booking"
        price_amount:
          type: integer
          example: 100

    OrderRefundTransactionContext:
      type: object
      required:
        - kind
        - source
        - reference
        - details
      properties:
        kind:
          type: string
          enum: [order_refund]
          example: "order_refund"
        source:
          type: string
          enum: [order]
          example: "order"
        reference:
          $ref: '#/components/schemas/OrderTransactionContextReference'
        details:
          $ref: '#/components/schemas/OrderRefundTransactionContextDetails'

    OrderRefundTransactionContextDetails:
      type: object
      required:
        - order_id
        - reward_id
        - reward_title
        - price_amount
      properties:
        order_id:
          type: string
          example: "order-123"
        reward_id:
          type: string
          example: "reward-456"
        reward_title:
          type: string
          example: "Free Court Booking"
        price_amount:
          type: integer
          example: 100
        cancellation_reason:
          type: string
          nullable: true
          example: "User cancellation"

    OrderTaxTransactionContext:
      type: object
      required:
        - kind
        - source
        - reference
        - details
      properties:
        kind:
          type: string
          enum: [order_tax]
          example: "order_tax"
        source:
          type: string
          enum: [order]
          example: "order"
        reference:
          $ref: '#/components/schemas/OrderTransactionContextReference'
        details:
          $ref: '#/components/schemas/OrderTaxTransactionContextDetails'

    OrderTaxTransactionContextDetails:
      type: object
      required:
        - order_id
        - reward_id
        - reward_title
        - price_amount
        - tax_amount
        - tax_rate_percent
        - user_id
      properties:
        order_id:
          type: string
          example: "order-123"
        reward_id:
          type: string
          example: "reward-456"
        reward_title:
          type: string
          example: "Free Court Booking"
        price_amount:
          type: integer
          example: 100
        tax_amount:
          type: integer
          example: 10
        tax_rate_percent:
          type: number
          format: double
          example: 10.0
        user_id:
          type: string
          example: "user-789"

    OrderTaxRefundTransactionContext:
      type: object
      required:
        - kind
        - source
        - reference
        - details
      properties:
        kind:
          type: string
          enum: [order_tax_refund]
          example: "order_tax_refund"
        source:
          type: string
          enum: [order]
          example: "order"
        reference:
          $ref: '#/components/schemas/OrderTransactionContextReference'
        details:
          $ref: '#/components/schemas/OrderTaxRefundTransactionContextDetails'

    OrderTaxRefundTransactionContextDetails:
      type: object
      required:
        - order_id
        - reward_id
        - reward_title
        - price_amount
        - tax_amount
        - tax_rate_percent
        - user_id
      properties:
        order_id:
          type: string
          example: "order-123"
        reward_id:
          type: string
          example: "reward-456"
        reward_title:
          type: string
          example: "Free Court Booking"
        price_amount:
          type: integer
          example: 100
        tax_amount:
          type: integer
          example: 10
        tax_rate_percent:
          type: number
          format: double
          example: 10.0
        user_id:
          type: string
          example: "user-789"
        cancellation_reason:
          type: string
          nullable: true
          example: "User cancellation"
        original_transaction_id:
          type: string
          nullable: true
          example: "tx-xyz"

    ChallengeRewardTransactionContext:
      type: object
      required:
        - kind
        - source
        - reference
        - details
      properties:
        kind:
          type: string
          enum: [challenge_reward]
          example: "challenge_reward"
        source:
          type: string
          enum: [challenge, engagement]
          example: "challenge"
        reference:
          $ref: '#/components/schemas/ParticipationTransactionContextReference'
        details:
          $ref: '#/components/schemas/ChallengeRewardTransactionContextDetails'

    ChallengeRewardTransactionContextDetails:
      type: object
      required:
        - challenge_id
        - challenge_title
        - club_id
      properties:
        challenge_id:
          type: string
          example: "challenge-123"
        challenge_title:
          type: string
          example: "King of the Court - April"
        club_id:
          type: string
          example: "club-9"

    VoucherFundingTransactionContext:
      type: object
      required:
        - kind
        - source
        - reference
        - details
      properties:
        kind:
          type: string
          enum: [voucher_funding]
          example: "voucher_funding"
        source:
          type: string
          enum: [voucher]
          example: "voucher"
        reference:
          $ref: '#/components/schemas/VoucherTransactionContextReference'
        details:
          $ref: '#/components/schemas/VoucherFundingTransactionContextDetails'

    VoucherFundingTransactionContextDetails:
      type: object
      required:
        - voucher_code
        - club_id
        - redeemed_by
      properties:
        voucher_code:
          type: string
          example: "SPRING-2026"
        club_id:
          type: string
          example: "club-1"
        redeemed_by:
          type: string
          example: "user-77"

    EscrowFundingTransactionContext:
      type: object
      required:
        - kind
        - source
        - reference
        - details
      properties:
        kind:
          type: string
          enum: [escrow_funding]
          example: "escrow_funding"
        source:
          type: string
          enum: [challenge]
          example: "challenge"
        reference:
          $ref: '#/components/schemas/ChallengeTransactionContextReference'
        details:
          $ref: '#/components/schemas/EscrowFundingTransactionContextDetails'

    EscrowFundingTransactionContextDetails:
      type: object
      required:
        - escrow_account_id
        - source_account_id
        - operation
      properties:
        challenge_title:
          type: string
          example: "Summer Ladder Finals"
        escrow_account_id:
          type: string
          example: "challenge:challenge-123"
        source_account_id:
          type: string
          example: "club:club-9"
        operation:
          type: string
          enum: [escrow_funding]
          example: "escrow_funding"

    OrderTransactionContextReference:
      type: object
      required:
        - type
        - id
      properties:
        type:
          type: string
          enum: [order, order_refund, order_tax, order_tax_refund]
          example: "order"
        id:
          type: string
          example: "order-123"

    VoucherTransactionContextReference:
      type: object
      required:
        - type
        - id
      properties:
        type:
          type: string
          enum: [voucher]
          example: "voucher"
        id:
          type: string
          example: "SPRING-2026"

    ParticipationTransactionContextReference:
      type: object
      required:
        - type
        - id
      properties:
        type:
          type: string
          enum: [attempt, participation]
          example: "attempt"
        id:
          type: string
          example: "attempt-456"

    ChallengeTransactionContextReference:
      type: object
      required:
        - type
        - id
      properties:
        type:
          type: string
          enum: [challenge]
          example: "challenge"
        id:
          type: string
          example: "challenge-123"

    POSEarningTransactionContext:
      type: object
      required:
        - kind
        - source
        - reference
        - details
      properties:
        kind:
          type: string
          enum: [pos_earning]
          example: "pos_earning"
        source:
          type: string
          enum: [order]
          example: "order"
        reference:
          $ref: '#/components/schemas/POSOrderTransactionContextReference'
        details:
          $ref: '#/components/schemas/POSEarningTransactionContextDetails'

    POSEarningTransactionContextDetails:
      type: object
      required:
        - order_id
      properties:
        order_id:
          type: string
          example: "ord-12345"

    POSOrderTransactionContextReference:
      type: object
      required:
        - type
        - id
      properties:
        type:
          type: string
          enum: [pos_order]
          example: "pos_order"
        id:
          type: string
          example: "ord-12345"
