openapi: 3.0.3
info:
  title: PUP - Club API v1
  description: >
    PUP is the universal player profile for padel. This API provides club management functionality.
  version: 1.0.0

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

x-tagGroups:
  - name: Club
    tags:
      - clubs
      - team-members
      - integrations
      - wallet

tags:
  - name: clubs
    x-displayName: "Clubs"
    description: Operations related to club management
  - name: team-members
    x-displayName: "Team Members"
    description: Operations related to club team member management
  - name: integrations
    x-displayName: "Integrations"
    description: Operations related to 3rd party integration management
  - name: wallet
    x-displayName: "Wallet"
    description: Operations related to club wallet and transactions

paths:
  /club/v1/new:
    post:
      tags:
        - clubs
      summary: Create a new club
      description: Creates a new club with the provided information
      operationId: createClub
      security:
        - OAuth2:
            - urn:pup:club:manage
        - PAT: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateClubRequest'
      responses:
        '201':
          description: Club created successfully
          content:
            application/json:
              schema:
                type: object
                required:
                  - id
                properties:
                  id:
                    type: string
                    example: "550e8400-e29b-41d4-a716-446655440000"
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        default:
          description: Generic error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /club/v1/search:
    get:
      tags:
        - clubs
      summary: Search clubs
      description: Search for clubs with optional filters
      operationId: searchClubs
      parameters:
        - in: query
          name: term
          example: Home padel
          schema:
            type: string
        - in: query
          name: city
          example: Amsterdam
          schema:
            type: string
        - in: query
          name: country
          example: NL
          description: ISO 3166-1 alpha-2 country code
          schema:
            type: string
            minLength: 2
            maxLength: 2
        - in: query
          name: geoloc_lat
          example: 52.3534646
          description: User geolocation latitude for searching nearby clubs
          schema:
            type: number
            format: float
            minimum: -90
            maximum: 90
        - in: query
          name: geoloc_lng
          example: 4.9037305
          description: User geolocation longitude for searching nearby clubs
          schema:
            type: number
            format: float
            minimum: -180
            maximum: 180
        - in: query
          name: geoloc_radius
          example: 20
          description: >
            Radius (in km) for searching nearby clubs. Only works when geoloc_lat and geoloc_lng are also set
          schema:
            type: integer
            minimum: 1
        - in: query
          name: open_at
          example: "14:30"
          description: >
            Filter clubs open at the specified time. Use the player's current local time in HH:MM format (24-hour).
            Example: "14:30"
          schema:
            type: string
            pattern: '^([01]?[0-9]|2[0-3]):[0-5][0-9]$'
        - in: query
          name: offset
          example: 0
          description: Results offset (useful for pagination)
          schema:
            type: integer
            minimum: 0
            default: 0
        - in: query
          name: limit
          example: 20
          description: Results limit (useful for pagination)
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
      security:
        - OAuth2:
            - urn:pup:club:read
        - OAuth2:
            - urn:pup:club:manage
        - PAT: []
      responses:
        '200':
          description: The search results
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SearchClubsResponse'
        '401':
          description: Unauthorized
        default:
          description: Generic error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /club/v1/club/{id}:
    get:
      tags:
        - clubs
      summary: Get club by ID
      description: Returns the club information for the specified ID
      operationId: getClubById
      parameters:
        - in: path
          name: id
          required: true
          description: Club identifier
          schema:
            type: string
      security:
        - OAuth2:
            - urn:pup:club:read
        - OAuth2:
            - urn:pup:club:manage
        - PAT: []
      responses:
        '200':
          description: The club information
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ClubResponse'
        '404':
          description: Club not found
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: "Club not found"
        '401':
          description: Unauthorized
        default:
          description: Generic error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

    patch:
      tags:
        - clubs
      summary: Update club
      description: Updates club information
      operationId: updateClub
      parameters:
        - in: path
          name: id
          required: true
          description: Club identifier
          schema:
            type: string
      security:
        - OAuth2:
            - urn:pup:club:manage
        - PAT: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateClubRequest'
      responses:
        '204':
          description: Club updated successfully
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden - user is not a manager of this club
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Club not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        default:
          description: Generic error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /club/v1/logo:
    put:
      tags:
        - clubs
      summary: Upload club logo
      description: Upload a new logo for the club
      operationId: uploadClubLogo
      security:
        - OAuth2:
            - urn:pup:club:manage
        - PAT: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                club_id:
                  type: string
                  description: The club ID
                logo:
                  type: string
                  format: binary
                  description: The logo file to upload (JPG, PNG, WebP)
              required:
                - club_id
                - logo
      responses:
        '204':
          description: Logo uploaded successfully
        '400':
          description: Invalid input or file too large
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden - user is not a manager of this club
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Club not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Uploading logo failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        default:
          description: Generic error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /club/v1/clubs/{club_id}/integrations:
    get:
      tags:
        - integrations
      summary: List club integrations
      description: Returns all integrations for the specified club
      operationId: listClubIntegrations
      parameters:
        - name: club_id
          in: path
          required: true
          description: Club identifier
          schema:
            type: string
      security:
        - OAuth2:
            - urn:pup:club:manage
        - PAT: []
      responses:
        '200':
          description: List of integrations
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListClubIntegrationsResponse'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden - user is not a manager of this club
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        default:
          description: Generic error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /club/v1/clubs/{club_id}/integrations/{integration_type}/validate:
    post:
      tags:
        - integrations
      summary: Validate integration credentials without persisting (dry run)
      description: Tests integration credentials without creating an integration
      operationId: validateIntegrationCredentials
      parameters:
        - name: club_id
          in: path
          required: true
          description: Club identifier
          schema:
            type: string
        - name: integration_type
          in: path
          required: true
          schema:
            type: string
            minLength: 1
      security:
        - OAuth2:
            - urn:pup:club:manage
        - PAT: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ValidateIntegrationCredentialsRequest'
      responses:
        '200':
          description: Validation result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidateIntegrationCredentialsResponse'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden - user is not a manager of this club
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Club not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        default:
          description: Generic error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /club/v1/clubs/{club_id}/integrations/{integration_type}:
    get:
      tags:
        - integrations
      summary: Get integration
      description: Returns the integration of the specified type for the specified club
      operationId: getIntegration
      parameters:
        - name: club_id
          in: path
          required: true
          description: Club identifier
          schema:
            type: string
        - name: integration_type
          in: path
          required: true
          schema:
            type: string
            minLength: 1
      security:
        - OAuth2:
            - urn:pup:club:manage
        - PAT: []
      responses:
        '200':
          description: The integration
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ClubIntegrationResponse'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden - user is not a manager of this club
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Integration not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        default:
          description: Generic error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    post:
      tags:
        - integrations
      summary: Connect integration
      description: Creates a new integration of the specified type for the club
      operationId: connectIntegration
      parameters:
        - name: club_id
          in: path
          required: true
          description: Club identifier
          schema:
            type: string
        - name: integration_type
          in: path
          required: true
          schema:
            type: string
            minLength: 1
      security:
        - OAuth2:
            - urn:pup:club:manage
        - PAT: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ConnectIntegrationRequest'
      responses:
        '201':
          description: Integration connected successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectIntegrationResponse'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden - user is not a manager of this club
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Club not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: Integration of this type is already connected for the club
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        default:
          description: Generic error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

    put:
      tags:
        - integrations
      summary: Update integration credentials
      description: Updates the integration credentials
      operationId: updateIntegration
      parameters:
        - name: club_id
          in: path
          required: true
          description: Club identifier
          schema:
            type: string
        - name: integration_type
          in: path
          required: true
          schema:
            type: string
            minLength: 1
      security:
        - OAuth2:
            - urn:pup:club:manage
        - PAT: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateIntegrationRequest'
      responses:
        '200':
          description: Integration credentials updated
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden - user is not a manager of this club
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Club or integration not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        default:
          description: Generic error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

    delete:
      tags:
        - integrations
      summary: Disconnect integration
      description: Disconnects and revokes the integration
      operationId: disconnectIntegration
      parameters:
        - name: club_id
          in: path
          required: true
          description: Club identifier
          schema:
            type: string
        - name: integration_type
          in: path
          required: true
          schema:
            type: string
            minLength: 1
      security:
        - OAuth2:
            - urn:pup:club:manage
        - PAT: []
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DisconnectIntegrationRequest'
      responses:
        '204':
          description: Integration disconnected successfully
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden - user is not a manager of this club
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Club or integration not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        default:
          description: Generic error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /club/v1/clubs/{club_id}/wallet:
    get:
      tags:
        - wallet
      summary: Get club wallet
      description: Returns the club's wallet balance and details. Requires user to be a club manager.
      operationId: getClubWallet
      security:
        - OAuth2:
            - urn:pup:club:manage
        - PAT: []
      parameters:
        - name: club_id
          in: path
          required: true
          description: Club ID
          schema:
            type: string
            minLength: 1
            maxLength: 64
          example: "550e8400-e29b-41d4-a716-446655440000"
      responses:
        '200':
          description: Wallet details retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WalletResponse'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden - User is not a manager of this club
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Club wallet not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        default:
          description: Generic error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /club/v1/clubs/{club_id}/wallet/transactions:
    get:
      tags:
        - wallet
      summary: Get club wallet transactions
      description: Returns the club's wallet transaction history with pagination and filtering. Requires user to be a club manager.
      operationId: getClubWalletTransactions
      security:
        - OAuth2:
            - urn:pup:club:manage
        - PAT: []
      parameters:
        - name: club_id
          in: path
          required: true
          description: Club ID
          schema:
            type: string
            minLength: 1
            maxLength: 64
          example: "550e8400-e29b-41d4-a716-446655440000"
        - name: transaction_type
          in: query
          required: false
          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"
        - name: offset
          in: query
          required: false
          description: Offset for pagination (default 0)
          schema:
            type: integer
            minimum: 0
            default: 0
          example: 0
        - name: limit
          in: query
          required: false
          description: Limit for pagination (default 20, max 100)
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
          example: 20
      responses:
        '200':
          description: Transactions retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransactionsResponse'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden - User is not a manager of this club
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        default:
          description: Generic error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /club/v1/{club_id}/team-members:
    get:
      tags:
        - team-members
      summary: List club team members
      description: Returns the list of team members for a club. Any team member can view the list.
      operationId: listClubTeamMembers
      parameters:
        - in: path
          name: club_id
          required: true
          schema:
            type: string
          description: The club ID
      security:
        - OAuth2:
            - urn:pup:club:manage
        - PAT: []
      responses:
        '200':
          description: Team members retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TeamMemberListResponse'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden - user is not a team member of this club
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        default:
          description: Generic error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    post:
      tags:
        - team-members
      summary: Add a team member
      description: >
        Add a new team member to the club by providing their username or email address.
        Only the club owner can add team members.
      operationId: addClubTeamMember
      parameters:
        - in: path
          name: club_id
          required: true
          schema:
            type: string
          description: The club ID
      security:
        - OAuth2:
            - urn:pup:club:manage
        - PAT: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AddTeamMemberRequest'
      responses:
        '204':
          description: Team member added successfully
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden - only the club owner can manage team members
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: User not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        default:
          description: Generic error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /club/v1/{club_id}/team-members/{user_id}/role:
    patch:
      tags:
        - team-members
      summary: Change a team member's role
      description: Change the role of an existing team member. Only the club owner can change roles.
      operationId: changeClubTeamMemberRole
      parameters:
        - in: path
          name: club_id
          required: true
          schema:
            type: string
          description: The club ID
        - in: path
          name: user_id
          required: true
          schema:
            type: string
          description: The user ID of the team member
      security:
        - OAuth2:
            - urn:pup:club:manage
        - PAT: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChangeTeamMemberRoleRequest'
      responses:
        '204':
          description: Team member role changed successfully
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden - only the club owner can manage team members
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        default:
          description: Generic error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /club/v1/{club_id}/team-members/{user_id}:
    delete:
      tags:
        - team-members
      summary: Remove a team member
      description: Remove a team member from the club. Only the club owner can remove members. The owner cannot be removed.
      operationId: removeClubTeamMember
      parameters:
        - in: path
          name: club_id
          required: true
          schema:
            type: string
          description: The club ID
        - in: path
          name: user_id
          required: true
          schema:
            type: string
          description: The user ID of the team member to remove
      security:
        - OAuth2:
            - urn:pup:club:manage
        - PAT: []
      responses:
        '204':
          description: Team member removed successfully
        '401':
          description: Unauthorized
        '403':
          description: Forbidden - only the club owner can manage team members
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Unprocessable entity
          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

    Address:
      type: object
      required:
        - street
        - city
        - postal_code
        - country
      properties:
        street:
          type: string
          minLength: 3
          maxLength: 200
          example: "Main Street 123"
        city:
          type: string
          minLength: 2
          maxLength: 100
          example: "Amsterdam"
        postal_code:
          type: string
          minLength: 2
          maxLength: 20
          example: "1012 AB"
        country:
          type: string
          description: ISO 3166-1 alpha-2 country code
          example: "NL"
          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 ]
        geolocation:
          type: object
          nullable: true
          description: Optional geolocation coordinates
          required:
            - latitude
            - longitude
          properties:
            latitude:
              type: number
              format: float
              minimum: -90
              maximum: 90
              description: Latitude coordinate
              example: 52.3534646
            longitude:
              type: number
              format: float
              minimum: -180
              maximum: 180
              description: Longitude coordinate
              example: 4.9037305

    Contact:
      type: object
      required:
        - name
        - email
        - phone
      properties:
        name:
          type: string
          minLength: 3
          maxLength: 100
          example: "John Doe"
        email:
          type: string
          format: email
          example: "contact@example.com"
        phone:
          type: string
          minLength: 7
          maxLength: 20
          pattern: '^[0-9\s\-()+]+$'
          example: "+31 20 1234567"
        website:
          type: string
          pattern: '^$|^([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$|^https?:\/\/([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(:[0-9]{1,5})?(\/[^\s?#]*)?(\?[^\s#]*)?(#[^\s]*)?$'
          nullable: true
          example: "https://www.example.com/clubs/home?ref=app"

    DaySchedule:
      type: object
      required:
        - opening_time
        - closing_time
      properties:
        opening_time:
          type: string
          pattern: '^([01]?[0-9]|2[0-3]):[0-5][0-9]$'
          example: "08:00"
          description: "Opening time in HH:MM format (24-hour)"
        closing_time:
          type: string
          pattern: '^([01]?[0-9]|2[0-3]):[0-5][0-9]$'
          example: "22:00"
          description: "Closing time in HH:MM format (24-hour)"

    WeeklySchedule:
      type: object
      description: "Weekly opening hours schedule. Days not specified are considered closed."
      properties:
        monday:
          $ref: '#/components/schemas/DaySchedule'
        tuesday:
          $ref: '#/components/schemas/DaySchedule'
        wednesday:
          $ref: '#/components/schemas/DaySchedule'
        thursday:
          $ref: '#/components/schemas/DaySchedule'
        friday:
          $ref: '#/components/schemas/DaySchedule'
        saturday:
          $ref: '#/components/schemas/DaySchedule'
        sunday:
          $ref: '#/components/schemas/DaySchedule'
      example:
        monday:
          opening_time: "08:00"
          closing_time: "22:00"
        tuesday:
          opening_time: "08:00"
          closing_time: "22:00"
        friday:
          opening_time: "08:00"
          closing_time: "23:00"

    CreateClubRequest:
      type: object
      required:
        - name
        - address
        - contact
      properties:
        name:
          type: string
          minLength: 3
          maxLength: 100
          example: "Home Padel Club"
        description:
          type: string
          nullable: true
          example: "The best padel club in the city"
        address:
          $ref: '#/components/schemas/Address'
        contact:
          $ref: '#/components/schemas/Contact'
        number_of_courts:
          type: integer
          minimum: 0
          maximum: 100
          nullable: true
          example: 4
          description: "Number of courts available"
        weekly_schedule:
          $ref: '#/components/schemas/WeeklySchedule'

    UpdateClubRequest:
      type: object
      properties:
        name:
          type: string
          minLength: 3
          maxLength: 100
          example: "Home Padel Club"
        description:
          type: string
          nullable: true
          example: "The best padel club in the city"
        address:
          $ref: '#/components/schemas/Address'
        contact:
          $ref: '#/components/schemas/Contact'
        number_of_courts:
          type: integer
          minimum: 0
          maximum: 100
          nullable: true
          example: 4
          description: "Number of courts available"
        weekly_schedule:
          $ref: '#/components/schemas/WeeklySchedule'

    Club:
      type: object
      required:
        - id
        - name
        - address
        - contact
      properties:
        id:
          type: string
          example: "550e8400-e29b-41d4-a716-446655440000"
        name:
          type: string
          example: "Home Padel Club"
        description:
          type: string
          nullable: true
          example: "The best padel club in the city"
        logo_url:
          type: string
          format: uri
          nullable: true
          example: "https://cdn.example.com/club/550e8400-e29b-41d4-a716-446655440000.png"
        address:
          $ref: '#/components/schemas/Address'
        contact:
          $ref: '#/components/schemas/Contact'
        number_of_courts:
          type: integer
          minimum: 0
          maximum: 100
          nullable: true
          example: 4
          description: "Number of courts available"
        weekly_schedule:
          $ref: '#/components/schemas/WeeklySchedule'

    ResponseMetaMany:
      type: object
      required:
        - pagination
      properties:
        pagination:
          type: object
          required:
            - total
            - returned
            - offset
            - limit
          properties:
            total:
              type: integer
              description: Total number of matching records
              example: 145
            returned:
              type: integer
              description: Number of records actually returned in this response
              example: 20
            offset:
              type: integer
              description: Offset used in the request
              example: 0
            limit:
              type: integer
              description: Limit used in the request
              example: 20
            next:
              type: string
              description: Relative path to next page of results
              example: /club/v1/search?term=something&offset=20&limit=20
            previous:
              type: string
              description: Relative path to previous page of results
              example: /club/v1/search?term=something&offset=0&limit=20

    ClubResponse:
      type: object
      required:
        - club
      properties:
        club:
          $ref: '#/components/schemas/Club'

    ClubIntegration:
      type: object
      required:
        - integration_id
        - type
        - status
        - config
        - created_at
        - updated_at
      properties:
        integration_id:
          type: string
          description: Integration identifier
        type:
          type: string
          description: Integration type (e.g. "playtomic")
        status:
          type: string
          description: Integration status (connected, disconnected)
        config:
          type: object
          additionalProperties:
            type: string
          description: Integration configuration (e.g. venue_id)
        created_at:
          type: string
          format: date-time
          description: When the integration was created
        updated_at:
          type: string
          format: date-time
          description: When the integration was last updated

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

    ClubIntegrationResponse:
      type: object
      required:
        - integration
      properties:
        integration:
          $ref: '#/components/schemas/ClubIntegration'

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

    ValidateIntegrationCredentialsRequest:
      type: object
      required:
        - client_id
        - secret
        - venue_id
      properties:
        client_id:
          type: string
          description: Playtomic client ID
        secret:
          type: string
          description: Playtomic secret
        venue_id:
          type: string
          description: Playtomic venue ID

    ValidateIntegrationCredentialsResponse:
      type: object
      required:
        - valid
      properties:
        valid:
          type: boolean
          description: Whether credentials are valid
        venue_name:
          type: string
          nullable: true
          description: Venue name from Playtomic (if valid)
        venue_id:
          type: string
          nullable: true
          description: Venue ID from Playtomic (if valid)
        error_message:
          type: string
          nullable: true
          description: Error message (if invalid)

    ConnectIntegrationRequest:
      type: object
      required:
        - client_id
        - secret
        - venue_id
      properties:
        client_id:
          type: string
          description: Playtomic client ID
        secret:
          type: string
          description: Playtomic secret
        venue_id:
          type: string
          description: Playtomic venue ID

    ConnectIntegrationResponse:
      type: object
      required:
        - integration_id
      properties:
        integration_id:
          type: string
          description: ID of the created integration (format club:integration:{type}:{club_id})

    UpdateIntegrationRequest:
      type: object
      required:
        - client_id
        - secret
      properties:
        client_id:
          type: string
          description: New Playtomic client ID
        secret:
          type: string
          description: New Playtomic secret
        venue_id:
          type: string
          nullable: true
          description: Optional - update venue ID

    DisconnectIntegrationRequest:
      type: object
      properties: {}

    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: "club: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
          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
          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: "user:550e8400-e29b-41d4-a716-446655440000"
        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: "Credits added via voucher SPRING-2026"
        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/VoucherFundingTransactionContext'
        - $ref: '#/components/schemas/EscrowFundingTransactionContext'
      discriminator:
        propertyName: kind
        mapping:
          voucher_funding: '#/components/schemas/VoucherFundingTransactionContext'
          escrow_funding: '#/components/schemas/EscrowFundingTransactionContext'

    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"

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

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

    TeamMemberListResponse:
      type: object
      required:
        - members
      properties:
        members:
          type: array
          items:
            $ref: '#/components/schemas/TeamMember'

    TeamMember:
      type: object
      required:
        - user_id
        - username
        - first_name
        - last_name
        - role
      properties:
        user_id:
          type: string
          example: "550e8400-e29b-41d4-a716-446655440000"
        username:
          type: string
          example: "johndoe"
        first_name:
          type: string
          example: "John"
        last_name:
          type: string
          example: "Doe"
        role:
          type: string
          enum: [owner, manager, staff]
          example: "manager"

    AddTeamMemberRequest:
      type: object
      required:
        - role
      properties:
        username:
          type: string
          description: Username of the user to add (provide username or email)
          example: "johndoe"
        email:
          type: string
          format: email
          description: Email of the user to add (provide username or email)
          example: "john@example.com"
        role:
          type: string
          enum: [manager, staff]
          description: Role to assign to the new team member
          example: "manager"

    ChangeTeamMemberRoleRequest:
      type: object
      required:
        - role
      properties:
        role:
          type: string
          enum: [manager, staff]
          description: New role for the team member
          example: "staff"

  securitySchemes:
    OAuth2:
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: "https://oauth2.gopup.app/oauth2/auth"
          tokenUrl: "https://oauth2.gopup.app/oauth2/token"
          scopes:
            urn:pup:club:read: "Read club information"
            urn:pup:club:manage: "Manage clubs"
      description: OAuth2

    PAT:
      type: http
      scheme: bearer
      bearerFormat: PAT
      description: Personal Access Token
