openapi: 3.0.3
info:
  title: PUP - User API v1
  description: >
    PUP is the universal player profile for padel. Players can use PUP to track their performance, playing style, and 
    statistics – no matter where they play.
  version: 1.0.0

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

x-tagGroups:
  - name: User
    tags:
      - identity
      - profile
      - wallet
      - orders

tags:
  - name: identity
    x-displayName: "Identity"
    description: Operations related to user identity
  - name: profile
    x-displayName: "Profile"
    description: Operations related to user profile
  - name: wallet
    x-displayName: "Wallet"
    description: Operations related to user wallet and transactions
  - name: orders
    x-displayName: "Orders"
    description: Operations related to user's reward purchases

paths:
  /user/v1/me:
    get:
      tags:
        - identity
      summary: Get the current authenticated user
      description: Returns the user information for the JWT-authenticated session.
      operationId: getCurrentUser
      security:
        - OAuth2:
            - urn:pup:user.profile:manage
        - PAT: []
      responses:
        '200':
          description: The authenticated user's information
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '404':
          description: User not found
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: "User not found"
        '401':
          description: Unauthorized - invalid or missing JWT
        default:
          description: Generic error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /user/v1/me/features:
    get:
      tags:
        - identity
      summary: Get feature flags for the current user
      description: >
        Returns structured feature flags derived from the user's roles and club memberships.
        Replaces the legacy permissions list with a predictable, well-typed contract.
      operationId: getCurrentUserFeatures
      security:
        - OAuth2:
            - urn:pup:user.profile:manage
        - PAT: []
      responses:
        '200':
          description: The user's feature flags
          content:
            application/json:
              schema:
                type: object
                required:
                  - features
                properties:
                  features:
                    $ref: '#/components/schemas/UserFeatures'
        '401':
          description: Unauthorized - invalid or missing JWT
        default:
          description: Generic error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /user/v1/me/profile:
    get:
      tags:
        - profile
      summary: Show the profile of the current authenticated user
      description: Returns the current user's profile data
      operationId: getCurrentUserProfile
      security:
        - OAuth2:
            - urn:pup:user.profile:manage
        - PAT: []
      responses:
        '200':
          description: The authenticated user's information
          content:
            application/json:
              schema:
                type: object
                required:
                  - profile
                properties:
                  profile:
                    $ref: '#/components/schemas/Profile'
        '404':
          description: User not found
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: "User not found"
        '401':
          description: Unauthorized - invalid or missing JWT
        default:
          description: Generic error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

    patch:
      tags:
        - profile
      summary: Update profile details
      description: >
        Update profile details such as personal information, bio and other properties such as KNLTB profile URL.
      operationId: updateProfile
      security:
        - OAuth2:
            - urn:pup:user.profile:manage
        - PAT: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateProfile'
      responses:
        '422':
          description: Invalid request input
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized
        '204':
          description: Profile successfully updated
        default:
          description: Generic error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /user/v1/me/profile/skills:
    put:
      tags:
        - profile
      summary: Update skills
      description: >
        Update skills by allocating points. See <a href="#tag/profile/operation/getCurrentUserProfile">Get profile</a> 
        on getting the allocatable points.
      operationId: updateSkills
      security:
        - OAuth2:
            - urn:pup:user.profile:manage
        - PAT: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Skills'
      responses:
        '422':
          description: Invalid request input (including wrong calculation)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized
        '204':
          description: Skill points successfully allocated
        default:
          description: Generic error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /user/v1/profile/onboarding:
    post:
      tags:
        - profile
      summary: Complete onboarding
      description: Marks the current authenticated user's onboarding as complete.
      operationId: completeOnboarding
      security:
        - OAuth2:
            - urn:pup:user.profile:manage
        - PAT: []
      responses:
        '204':
          description: Onboarding successfully marked as complete
        '401':
          description: Unauthorized
        '409':
          description: Onboarding already completed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        default:
          description: Generic error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /user/v1/me/profile/picture:
    put:
      tags:
        - profile
      summary: Upload new profile picture
      operationId: updateProfilePicture
      security:
        - OAuth2:
            - urn:pup:user.profile:manage
        - PAT: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                picture:
                  type: string
                  format: binary
                  description: The picture file to process (JPG, PNG, WebP)
              required:
                - picture
      responses:
        '204':
          description: Profile picture uploaded successfully
        '401':
          description: Unauthorized
        '400':
          description: Invalid input or file too large
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Updating profile picture failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        default:
          description: Generic error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /user/v1/wallet:
    get:
      tags:
        - wallet
      summary: Get current user's wallet
      description: >
        Returns the wallet balance and status for the authenticated user.
        The wallet contains the user's credit balance in PUP currency.
      operationId: getWallet
      security:
        - OAuth2:
            - urn:pup:user.profile:manage
        - PAT: []
      responses:
        '200':
          description: Wallet details retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WalletResponse'
        '401':
          description: Unauthorized - invalid or missing JWT
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Wallet not found - account does not exist
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        default:
          description: Generic error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /user/v1/wallet/transactions:
    get:
      tags:
        - wallet
      summary: Get wallet transaction history
      description: >
        Returns paginated transaction history for the authenticated user's wallet.
        Transactions show credits earned, spent, and transferred.
      operationId: getWalletTransactions
      security:
        - OAuth2:
            - urn:pup:user.profile:manage
        - PAT: []
      parameters:
        - name: offset
          in: query
          description: Pagination offset (number of records to skip)
          schema:
            type: integer
            default: 0
            minimum: 0
          example: 0
        - name: limit
          in: query
          description: Maximum number of records to return
          schema:
            type: integer
            default: 20
            minimum: 1
            maximum: 100
          example: 20
        - name: transaction_type
          in: query
          description: Filter by transaction type
          schema:
            type: string
            enum: [challenge_reward, spend_credits, purchase_credits, reversal, platform_fee, refund, transfer_between_players, escrow_funding]
          example: spend_credits
      responses:
        '200':
          description: Transaction history retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransactionsResponse'
        '401':
          description: Unauthorized - invalid or missing JWT
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        default:
          description: Generic error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /user/v1/orders:
    get:
      tags:
        - orders
      summary: Get user's orders
      description: >
        Returns paginated list of orders for the authenticated user.
        Orders represent rewards purchased using PUP coins.
      operationId: getOrders
      parameters:
        - in: query
          name: status
          description: Filter by order status
          schema:
            type: string
            enum: [pending_payment, paid, payment_failed, cancelled, redeemed]
          example: paid
        - in: query
          name: search_term
          description: Search by reward title
          schema:
            type: string
          example: Premium Membership
        - in: query
          name: offset
          description: Results offset (for pagination)
          schema:
            type: integer
            minimum: 0
            default: 0
          example: 0
        - in: query
          name: limit
          description: Results limit (for pagination)
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
          example: 20
      security:
        - OAuth2:
            - urn:pup:user.profile:manage
        - PAT: []
      responses:
        '200':
          description: Orders retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrdersResponse'
        '401':
          description: Unauthorized - invalid or missing JWT
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        default:
          description: Generic error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /user/v1/orders/{order_id}:
    get:
      tags:
        - orders
      summary: Get order by ID
      description: >
        Retrieves details of a specific order belonging to the authenticated user.
      operationId: getOrder
      parameters:
        - in: path
          name: order_id
          description: Order identifier
          required: true
          schema:
            type: string
          example: 7c9e6679-7425-40de-944b-e07fc1f90ae7
      security:
        - OAuth2:
            - urn:pup:user.profile:manage
        - PAT: []
      responses:
        '200':
          description: Order details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderResponse'
        '401':
          description: Unauthorized - invalid or missing JWT
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Order not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        default:
          description: Generic error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

components:
  schemas:
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: string
          example: "An error message"
      description: Generic error response

    BaseProfile:
      type: object
      properties:
        first_name:
          type: string
          description: User’s first name
          example: "Alice"
        last_name:
          type: string
          description: User’s last name
          example: "Johnson"
        country_code:
          type: string
          description: Country code (ISO 3166-1 alpha-2 code)
          enum: [ AF, AX, AL, DZ, AS, AD, AO, AI, AQ, AG, AR, AM, AW, AU, AT, AZ, BS, BH, BD, BB, BY, BE, BZ, BJ, BM, BT, BO, BQ, BA, BW, BV, BR, IO, BN, BG, BF, BI, CV, KH, CM, CA, KY, CF, TD, CL, CN, CX, CC, CO, KM, CG, CD, CK, CR, CI, HR, CU, CW, CY, CZ, DK, DJ, DM, DO, EC, EG, SV, GQ, ER, EE, SZ, ET, FK, FO, FJ, FI, FR, GF, PF, TF, GA, GM, GE, DE, GH, GI, GR, GL, GD, GP, GU, GT, GG, GN, GW, GY, HT, HM, VA, HN, HK, HU, IS, IN, ID, IR, IQ, IE, IM, IL, IT, JM, JP, JE, JO, KZ, KE, KI, KP, KR, KW, KG, LA, LV, LB, LS, LR, LY, LI, LT, LU, MO, MG, MW, MY, MV, ML, MT, MH, MQ, MR, MU, YT, MX, FM, MD, MC, MN, ME, MS, MA, MZ, MM, NA, NR, NP, NL, NC, NZ, NI, NE, NG, NU, NF, MK, MP, "NO", OM, PK, PW, PS, PA, PG, PY, PE, PH, PN, PL, PT, PR, QA, RE, RO, RU, RW, BL, SH, KN, LC, MF, PM, VC, WS, SM, ST, SA, SN, RS, SC, SL, SG, SX, SK, SI, SB, SO, ZA, GS, SS, ES, LK, SD, SR, SJ, SE, CH, SY, TW, TJ, TZ, TH, TL, TG, TK, TO, TT, TN, TR, TM, TC, TV, UG, UA, AE, GB, US, UM, UY, UZ, VU, VE, VN, VG, VI, WF, EH, YE, ZM, ZW ]
        bio:
          type: string
          nullable: true
          description: Short biography of the player
          example: "Passionate padel player with 2 years of experience"
        club:
          type: object
          nullable: true
          description: >
            The player's club. If club_id is set and valid, club_name is ignored and loaded from the database.
            At least one of club_id or club_name must be set if the club object is present.
          properties:
            id:
              type: string
              nullable: true
              description: The club's unique identifier. If set and valid, the club name will be loaded from the database.
              example: "c1234567-89ab-cdef-0123-456789abcdef"
            name:
              type: string
              nullable: true
              description: The club's name. Ignored if a valid club_id is provided.
              example: "Home padel"
        knltb_profile_url:
          type: string
          nullable: true
          format: uri
          description: URL to the official KNLTB profile
          example: "https://mijnknltb.toernooi.nl/player-profile/e52a3bc3-9ccc-4ed1-be66-de7510a61f86"
        best_hand:
          type: string
          enum: ["left", "right", "both"]
        position_on_court:
          type: string
          enum: ["left", "right", "both"]
        gender:
          type: string
          nullable: true
          enum: ["male", "female"]
          description: Gender of the player
        date_of_birth:
          type: string
          nullable: true
          format: date
          description: Date of birth of the player
          example: "1990-05-15"
        profile_visibility:
          type: string
          enum: ["private", "in_app", "public"]
          description: >
            Controls who can see this profile.
            "private" — not visible to anyone.
            "in_app" — visible to authenticated users in the app.
            "public" — visible to anyone, including the public API.
          example: "private"

    Skill:
      type: integer
      example: 67
      minimum: 0
      maximum: 99
      description: Points allocated to a skill

    Skills:
      type: object
      additionalProperties: false
      required:
        - physical
        - forehand
        - back
        - volley
        - bajada
        - vibora
        - bandeja
        - smash
        - defense
      properties:
        physical:
          $ref: "#/components/schemas/Skill"
        forehand:
          $ref: "#/components/schemas/Skill"
        back:
          $ref: "#/components/schemas/Skill"
        volley:
          $ref: "#/components/schemas/Skill"
        bajada:
          $ref: "#/components/schemas/Skill"
        vibora:
          $ref: "#/components/schemas/Skill"
        bandeja:
          $ref: "#/components/schemas/Skill"
        smash:
          $ref: "#/components/schemas/Skill"
        defense:
          $ref: "#/components/schemas/Skill"

    Rating:
      type: object
      required:
        - provider
        - rating
      properties:
        provider:
          type: string
          example: "KNLTB"
          description: "Name of the rating provider"
        rating:
          type: string
          example: "5.12345"
          pattern: '^\d+(\.\d+)?$'

    Profile:
      allOf:
        - $ref: "#/components/schemas/BaseProfile"
        - type: object
          required:
            - first_name
            - last_name
            - pup_score
            - onboarding_completed
            - profile_visibility
          properties:
            picture_url:
              type: string
              format: uri
              description: "URL to profile picture"
              example: "https://cdn.example.com/profile/b6d563ab-d1bf-493f-bde2-2d9474c6e596.png"
            card_image_url:
              type: string
              format: uri
              nullable: true
              description: "URL to pre-rendered player card image"
            onboarding_completed:
              type: boolean
              description: "Whether the user has completed the onboarding flow"
              example: false
            skills:
              type: object
              properties:
                points:
                  type: object
                  required:
                    - granted
                    - remaining
                    - allocated
                  properties:
                    granted:
                      type: integer
                      example: 600
                      description: The total amount of points that can be used by the profile
                    remaining:
                      type: integer
                      description: The amount of points remaining
                      example: 22
                    allocated:
                      $ref: "#/components/schemas/Skills"
            ratings:
              type: array
              items:
                $ref: "#/components/schemas/Rating"
            pup_score:
              type: integer
              minimum: 0
              example: 67
              description: "The PUP score is based on unified overall rating from all sources (providers such as KNLTB, Playtomic)"

    UpdateProfile:
      $ref: "#/components/schemas/BaseProfile"

    User:
      type: object
      required:
        - user_id
        - email
        - username
        - first_name
        - last_name
        - roles
      properties:
        user_id:
          type: string
          description: Unique identifier for the user
          example: "abc123"
        email:
          type: string
          format: email
          description: User email address
          example: "user@example.com"
        username:
          type: string
          description: Unique username
          example: "padelfan99"
        first_name:
          type: string
          description: User’s first name
          example: "Alice"
        last_name:
          type: string
          description: User’s last name
          example: "Johnson"
        roles:
          type: array
          items:
            type: string
          example: ["user"]
          description: User roles

    UserFeatures:
      type: object
      required:
        - can_create_club
        - can_verify_challenges
        - can_create_challenges
        - can_create_rewards
        - can_redeem_orders
        - can_issue_funding_vouchers
        - club_team_memberships
        - is_root
      properties:
        can_create_club:
          type: boolean
          description: Whether the user can create a new club
          example: true
        can_verify_challenges:
          type: boolean
          description: Whether the user can verify challenge participations (any club role)
          example: true
        can_create_challenges:
          type: boolean
          description: Whether the user can create challenges (owner or manager of any club)
          example: false
        can_create_rewards:
          type: boolean
          description: Whether the user can create rewards (root only)
          example: false
        can_redeem_orders:
          type: boolean
          description: Whether the user can authorize order redemptions (root only)
          example: false
        can_issue_funding_vouchers:
          type: boolean
          description: Whether the user can issue funding vouchers (root only)
          example: false
        club_team_memberships:
          type: array
          items:
            $ref: '#/components/schemas/ClubTeam'
          description: Clubs whose team the user is part of, with their role
        is_root:
          type: boolean
          description: Whether the user has root (superadmin) privileges
          example: false

    ClubTeam:
      type: object
      required:
        - club_id
        - club_name
        - role
      properties:
        club_id:
          type: string
          description: The club's unique identifier
          example: "c1234567-89ab-cdef-0123-456789abcdef"
        club_name:
          type: string
          description: The club's display name
          example: "Tennis Club Amsterdam"
        role:
          type: string
          description: The user's role in this club
          enum: [owner, manager, staff]
          example: "owner"

    WalletResponse:
      type: object
      required:
        - wallet
      properties:
        wallet:
          $ref: '#/components/schemas/Wallet'

    Wallet:
      type: object
      required:
        - account_id
        - balance
        - currency
        - status
        - opened_at
      properties:
        account_id:
          type: string
          description: Unique identifier for the wallet account
          example: "user:550e8400-e29b-41d4-a716-446655440000"
        balance:
          type: integer
          description: Current balance in credits
          example: 5000
        currency:
          type: string
          description: Currency code
          example: "PUP"
        status:
          type: string
          description: Account status (e.g., open, closed)
          example: "open"
        opened_at:
          type: string
          format: date-time
          description: Timestamp when the wallet was opened
          example: "2024-01-01T00:00:00Z"
        closed_at:
          type: string
          format: date-time
          nullable: true
          description: Timestamp when the wallet was closed (if applicable)
          example: null

    TransactionsResponse:
      type: object
      required:
        - hits
        - meta
      properties:
        hits:
          type: array
          items:
            $ref: '#/components/schemas/Transaction'
          description: Array of transactions
        meta:
          $ref: '#/components/schemas/ResponseMetaMany'

    Transaction:
      type: object
      required:
        - transaction_id
        - type
        - amount
        - counterparty_account_id
        - description
        - created_at
      properties:
        transaction_id:
          type: string
          description: Unique transaction identifier
          example: "tx-abc123"
        type:
          type: string
          description: Type of transaction (spend_credits, purchase_credits, refund, transfer)
          example: "spend_credits"
        amount:
          type: integer
          description: Transaction amount (positive for credit, negative for debit)
          example: -100
        counterparty_account_id:
          type: string
          description: The account ID of the other party in the transaction (where funds came from or went to)
          example: "acc-treasury-xyz"
        context:
          allOf:
            - $ref: '#/components/schemas/TransactionContext'
          nullable: true
          description: Immutable structured context for client-side transaction rendering
        description:
          type: string
          description: Human-readable fallback description. Use `type` and `context` for machine-readable UX.
          example: "Purchased Free Court Booking"
        created_at:
          type: string
          format: date-time
          description: Timestamp when the transaction was recorded
          example: "2024-01-15T10:30:00Z"

    TransactionContext:
      oneOf:
        - $ref: '#/components/schemas/OrderPurchaseTransactionContext'
        - $ref: '#/components/schemas/ChallengeRewardTransactionContext'
      discriminator:
        propertyName: kind
        mapping:
          order_purchase: '#/components/schemas/OrderPurchaseTransactionContext'
          challenge_reward: '#/components/schemas/ChallengeRewardTransactionContext'

    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-42"
        reward_title:
          type: string
          example: "Free Court Booking"
        price_amount:
          type: integer
          example: 300

    ChallengeRewardTransactionContext:
      type: object
      required:
        - kind
        - source
        - reference
        - details
      properties:
        kind:
          type: string
          enum: [challenge_reward]
          example: "challenge_reward"
        source:
          type: string
          enum: [engagement]
          example: "engagement"
        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"

    OrderTransactionContextReference:
      type: object
      required:
        - type
        - id
      properties:
        type:
          type: string
          enum: [order]
          description: Business reference type
          example: "order"
        id:
          type: string
          description: Business reference identifier
          example: "order-123"

    ParticipationTransactionContextReference:
      type: object
      required:
        - type
        - id
      properties:
        type:
          type: string
          enum: [participation]
          description: Business reference type
          example: "participation"
        id:
          type: string
          description: Business reference identifier
          example: "participation-456"

    ResponseMetaMany:
      type: object
      required:
        - pagination
      properties:
        pagination:
          $ref: '#/components/schemas/Pagination'

    Pagination:
      type: object
      required:
        - total
        - returned
        - offset
        - limit
      properties:
        total:
          type: integer
          description: Total number of records available
          example: 145
        returned:
          type: integer
          description: Number of records returned in this response
          example: 20
        offset:
          type: integer
          description: Offset used for this request
          example: 0
        limit:
          type: integer
          description: Limit used for this request
          example: 20
        next:
          type: string
          nullable: true
          description: Relative URL to the next page (null if no next page)
          example: "/user/v1/wallet/transactions?offset=20&limit=20"
        previous:
          type: string
          nullable: true
          description: Relative URL to the previous page (null if no previous page)
          example: null

    Order:
      type: object
      required:
        - order_id
        - user_id
        - reward_id
        - reward_title
        - price_amount
        - status
        - created_at
      properties:
        order_id:
          type: string
          description: Order identifier
          example: 7c9e6679-7425-40de-944b-e07fc1f90ae7
        user_id:
          type: string
          description: User identifier who placed the order
          example: 123e4567-e89b-12d3-a456-426614174000
        reward_id:
          type: string
          description: Reward identifier
          example: 550e8400-e29b-41d4-a716-446655440000
        reward_title:
          type: string
          description: Title of the purchased reward
          example: Premium Membership
        price_amount:
          type: integer
          description: Price in credits
          example: 1000
        status:
          type: string
          description: Current order status
          enum: [pending_payment, paid, payment_failed, cancelled, redeemed]
          example: paid
        transaction_id:
          type: string
          nullable: true
          description: Transaction ID (only present for paid orders)
          example: txn_abc123
        redemption_code:
          type: string
          nullable: true
          description: Redemption code (only present for paid orders)
          example: REDEMPTION-xyz789
        failure_reason:
          type: string
          nullable: true
          description: Reason for payment failure (only present for failed orders)
          example: Insufficient credits
        created_at:
          type: string
          format: date-time
          description: Order creation timestamp
          example: "2026-02-08T15:00:00Z"
        completed_at:
          type: string
          format: date-time
          nullable: true
          description: Timestamp when order was completed (paid)
          example: "2026-02-08T15:01:00Z"
        redeemed_at:
          type: string
          format: date-time
          nullable: true
          description: Timestamp when order was redeemed
          example: "2026-02-08T16:00:00Z"
        claim_instructions:
          type: string
          nullable: true
          description: >
            Instructions for how to claim the reward. Only present for paid orders
            or orders with a redemption code.
          example: "Show this code at the front desk: CODE-123"

    OrdersResponse:
      type: object
      required:
        - hits
        - meta
      properties:
        hits:
          type: array
          items:
            $ref: '#/components/schemas/Order'
        meta:
          $ref: '#/components/schemas/ResponseMetaMany'

    OrderResponse:
      type: object
      required:
        - order
      properties:
        order:
          $ref: '#/components/schemas/Order'

  securitySchemes:
    OAuth2:
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: "https://oauth2.gopup.app/oauth2/auth"
          tokenUrl: "https://oauth2.gopup.app/oauth2/token"
          scopes:
            urn:pup:user.profile:manage: "Manage PUP profile"
      description: OAuth2
    PAT:
      type: http
      scheme: bearer
      bearerFormat: PAT
      description: Personal Access Token
