openapi: 3.1.0
info:
  title: Rellavo API
  version: 1.1.0
  summary: The machine-readable surface of the Rellavo platform.
  description: |
    Rellavo is a multi-tenant gym & community-center platform. This API
    exposes member, schedule, billing, messaging, and workflow primitives
    behind tenant-scoped personal access tokens (PATs).

    Each token is bound to exactly one tenant (`gym_id`); the tenant is
    implicit on every request and cannot be overridden except via
    `X-Active-Gym-Id` with a platform-admin token.

    JSON keys are snake_case. All `*_at` fields are ISO 8601 timestamps
    with explicit timezone. Money fields are decimal strings (preserving
    DB `numeric` precision). Errors follow RFC 9457 (problem+json).
  contact:
    name: Rellavo Engineering
    url: https://docs.rellavo.com
    email: api@rellavo.com
  license:
    name: Proprietary
    url: https://www.rellavo.com/legal/terms
servers:
- url: https://{tenant}.rellavo.com/v1
  description: Production tenant
  variables:
    tenant:
      default: demo
      description: Tenant subdomain (display only — auth is by token, not subdomain)
- url: https://docs.rellavo.com/v1
  description: Demo (read-only)
tags:
- name: Platform
  description: Health, token introspection, OpenAPI surface
- name: Members
  description: Member profiles (user_type=member)
- name: Leads
  description: Lead profiles (user_type=lead)
- name: Schedule
  description: Group classes, appointments, and courts
- name: Billing
  description: Plans, subscriptions, invoices, and payments
- name: Messaging
  description: Send messages, templates, communications, and campaigns
- name: Workflows
  description: Automation graphs, runs, and jobs
- name: Tasks
  description: Tasks
- name: Notes
  description: Notes on members
- name: Documents
  description: Documents and user-document signing
- name: Programs
  description: Programs (community / JCC)
- name: Reports
  description: Aggregate analytics
- name: Webhooks
  description: Subscriptions and deliveries
- name: Tenant
  description: Gym metadata
- name: Public
  description: Unauthenticated endpoints
- name: Admin
  description: Admin-scope endpoints
- name: Support
  description: Support tickets
- name: Workouts
  description: Workouts, sessions, and personal records (gym tenants)
- name: Facility
  description: Facility check-ins, areas, and locations
security:
- bearerAuth: []
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: rk_(live|test)_<gym_slug>_<28chars>
  parameters:
    cursor:
      name: cursor
      in: query
      schema:
        type: string
      description: Opaque pagination cursor returned by the previous page.
    limit:
      name: limit
      in: query
      schema:
        type: integer
        default: 50
        minimum: 1
        maximum: 200
    sort:
      name: sort
      in: query
      schema:
        type: string
      description: Comma list of fields; prefix `-` for desc (`-created_at,name`).
    include_total:
      name: include_total
      in: query
      schema:
        type: boolean
        default: false
      description: When true, the first page response includes an exact `total`.
    idempotencyKey:
      name: Idempotency-Key
      in: header
      schema:
        type: string
        maxLength: 255
      description: Opaque client-supplied key. Replays within 24h return the cached response.
    ifMatch:
      name: If-Match
      in: header
      schema:
        type: string
      description: ETag from a prior read. Returns 412 if the resource changed.
    activeGymId:
      name: X-Active-Gym-Id
      in: header
      schema:
        type: string
        format: uuid
      description: Platform-admin only. Impersonate another tenant.
    requestId:
      name: X-Request-Id
      in: header
      schema:
        type: string
      description: Echoed back; generated server-side if absent.
  responses:
    Unauthorized:
      description: Missing or invalid bearer token.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    Forbidden:
      description: Token lacks required scope or cross-tenant access denied.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    NotFound:
      description: Resource not found within the token's tenant.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    Conflict:
      description: State conflict (incl. duplicate Idempotency-Key with different body).
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    Validation:
      description: Request validation failure (RFC 9457 problem with `errors[]`).
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    PreconditionFailed:
      description: If-Match mismatch.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    RateLimited:
      description: Per-token or per-tenant rate limit exceeded.
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds until the next request will be accepted.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    FeatureNotEnabled:
      description: Endpoint exists, but the tenant has not enabled this feature.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    PayloadTooLarge:
      description: CSV export exceeds the 50k-row sync threshold.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    NoContent:
      description: Success, no body.
  schemas:
    Problem:
      type: object
      required:
      - type
      - title
      - status
      properties:
        type:
          type: string
          format: uri
        title:
          type: string
        status:
          type: integer
        detail:
          type: string
        instance:
          type: string
        request_id:
          type: string
        errors:
          type: array
          items:
            type: object
            properties:
              path:
                type: string
              code:
                type: string
              message:
                type: string
    Cursor:
      type: object
      properties:
        next_cursor:
          type:
          - string
          - 'null'
        count_estimate:
          type: integer
        total:
          type:
          - integer
          - 'null'
    Money:
      type: string
      description: Decimal string in the gym's currency (precision preserved from DB numeric).
      example: '29.99'
    Gym:
      type: object
      required:
      - id
      - slug
      - name
      - display_name
      - tenant_type
      properties:
        id:
          type: string
          format: uuid
        slug:
          type: string
        name:
          type: string
        display_name:
          type: string
        short_name:
          type:
          - string
          - 'null'
        tagline:
          type:
          - string
          - 'null'
        logo_url:
          type:
          - string
          - 'null'
        primary_color:
          type:
          - string
          - 'null'
        accent_color:
          type:
          - string
          - 'null'
        tenant_type:
          type: string
          enum:
          - gym
          - community
        timezone:
          type: string
          default: America/Chicago
        features:
          type: array
          items:
            type: string
        custom_values:
          type: object
          additionalProperties: true
        outbound_email_from_name:
          type:
          - string
          - 'null'
        is_active:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    Me:
      type: object
      required:
      - token
      - gym
      properties:
        token:
          type: object
          required:
          - id
          - name
          - scopes
          properties:
            id:
              type: string
              format: uuid
            name:
              type: string
            prefix:
              type: string
              example: rk_live_demo_a8Z1
            scopes:
              type: array
              items:
                type: string
            rate_limit:
              type: integer
            created_at:
              type: string
              format: date-time
            last_used_at:
              type:
              - string
              - 'null'
              format: date-time
            expires_at:
              type:
              - string
              - 'null'
              format: date-time
        gym:
          $ref: '#/components/schemas/Gym'
    Member:
      type: object
      required:
      - id
      - user_type
      - first_name
      - account_status
      properties:
        id:
          type: string
          format: uuid
        user_type:
          type: string
          enum:
          - member
          - lead
        first_name:
          type: string
        last_name:
          type:
          - string
          - 'null'
        middle_name:
          type:
          - string
          - 'null'
        nickname:
          type:
          - string
          - 'null'
        preferred_name:
          type:
          - string
          - 'null'
        email:
          type:
          - string
          - 'null'
          format: email
        phone:
          type:
          - string
          - 'null'
        birthday:
          type:
          - string
          - 'null'
          format: date
        gender:
          type:
          - string
          - 'null'
        address_line1:
          type:
          - string
          - 'null'
        address_line2:
          type:
          - string
          - 'null'
        city:
          type:
          - string
          - 'null'
        state:
          type:
          - string
          - 'null'
        country:
          type:
          - string
          - 'null'
        zip_code:
          type:
          - string
          - 'null'
        emergency_name:
          type:
          - string
          - 'null'
        emergency_relationship:
          type:
          - string
          - 'null'
        emergency_phone:
          type:
          - string
          - 'null'
        member_since:
          type:
          - string
          - 'null'
          format: date
        first_checkin:
          type:
          - string
          - 'null'
          format: date
        last_checkin:
          type:
          - string
          - 'null'
          format: date
        last_check_in_at:
          type:
          - string
          - 'null'
          format: date-time
        custom_id:
          type:
          - string
          - 'null'
        referred_by:
          type:
          - string
          - 'null'
        assigned_to:
          type:
          - string
          - 'null'
          format: uuid
        account_status:
          type: string
        lead_status:
          type:
          - string
          - 'null'
        lead_score:
          type: integer
        stripe_customer_id:
          type:
          - string
          - 'null'
          description: Visible only with billing:read scope
        tags:
          type: array
          items:
            type: string
        membership_id:
          type:
          - string
          - 'null'
          format: uuid
        member_sequence:
          type:
          - integer
          - 'null'
        family_group_id:
          type:
          - string
          - 'null'
          format: uuid
        family_category:
          type:
          - string
          - 'null'
        avatar_url:
          type:
          - string
          - 'null'
        barcode:
          type:
          - string
          - 'null'
        is_volunteer:
          type: boolean
        is_donor:
          type: boolean
        is_employee:
          type: boolean
        calendar_sync_enabled:
          type: boolean
        profile_data:
          type: object
          additionalProperties: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    MemberCreate:
      type: object
      required:
      - first_name
      properties:
        first_name:
          type: string
        last_name:
          type: string
        middle_name:
          type: string
        nickname:
          type: string
        email:
          type: string
          format: email
        phone:
          type: string
        birthday:
          type: string
          format: date
        gender:
          type: string
        address_line1:
          type: string
        address_line2:
          type: string
        city:
          type: string
        state:
          type: string
        country:
          type: string
        zip_code:
          type: string
        emergency_name:
          type: string
        emergency_relationship:
          type: string
        emergency_phone:
          type: string
        member_since:
          type: string
          format: date
        custom_id:
          type: string
        assigned_to:
          type: string
          format: uuid
        tags:
          type: array
          items:
            type: string
    MemberPatch:
      allOf:
      - $ref: '#/components/schemas/MemberCreate'
      - type: object
        properties:
          account_status:
            type: string
          lead_status:
            type: string
    Lead:
      allOf:
      - $ref: '#/components/schemas/Member'
      - type: object
        properties:
          user_type:
            type: string
            enum:
            - lead
    LeadStageEvent:
      type: object
      required:
      - id
      - profile_id
      - to_status
      - changed_at
      properties:
        id:
          type: string
          format: uuid
        profile_id:
          type: string
          format: uuid
        from_status:
          type:
          - string
          - 'null'
        to_status:
          type: string
        changed_by:
          type:
          - string
          - 'null'
          format: uuid
        note:
          type:
          - string
          - 'null'
        changed_at:
          type: string
          format: date-time
    MemberAlert:
      type: object
      required:
      - id
      - priority
      - message
      - active_from
      properties:
        id:
          type: string
          format: uuid
        profile_id:
          type:
          - string
          - 'null'
          format: uuid
        membership_id:
          type:
          - string
          - 'null'
          format: uuid
        priority:
          type: string
          enum:
          - high
          - medium
          - low
        active_from:
          type: string
          format: date
        active_until:
          type:
          - string
          - 'null'
          format: date
        message:
          type: string
        remove_after_first_checkin:
          type: boolean
        show_on_checkin:
          type: boolean
        show_on_membership:
          type: boolean
        resolved_at:
          type:
          - string
          - 'null'
          format: date-time
        created_at:
          type: string
          format: date-time
    Note:
      type: object
      required:
      - id
      - profile_id
      - note
      - created_at
      properties:
        id:
          type: string
          format: uuid
        profile_id:
          type: string
          format: uuid
        note:
          type: string
        category:
          type: string
          default: general
        remind_on:
          type:
          - string
          - 'null'
          format: date
        remind_via:
          type:
          - string
          - 'null'
        is_pinned:
          type: boolean
        created_by:
          type:
          - string
          - 'null'
          format: uuid
        created_at:
          type: string
          format: date-time
    NoteCreate:
      type: object
      required:
      - note
      properties:
        note:
          type: string
        category:
          type: string
        remind_on:
          type: string
          format: date
        remind_via:
          type: string
        is_pinned:
          type: boolean
    Membership:
      type: object
      required:
      - id
      - membership_number
      - membership_type
      - status
      properties:
        id:
          type: string
          format: uuid
        membership_number:
          type: string
        membership_type:
          type: string
          enum:
          - family
          - individual
          - senior
          - youth
        status:
          type: string
          enum:
          - active
          - inactive
          - on_hold
          - prospect
        monthly_charge:
          $ref: '#/components/schemas/Money'
        payment_method:
          type:
          - string
          - 'null'
        next_process_date:
          type:
          - string
          - 'null'
          format: date
        balance_due:
          $ref: '#/components/schemas/Money'
        primary_member_id:
          type:
          - string
          - 'null'
          format: uuid
        pricing_slug:
          type:
          - string
          - 'null'
        notes:
          type:
          - string
          - 'null'
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    PlanCategory:
      type: object
      required:
      - id
      - name
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        sort_order:
          type: integer
        created_at:
          type: string
          format: date-time
    Plan:
      type: object
      required:
      - id
      - name
      - plan_type
      - client_status
      - price
      properties:
        id:
          type: string
          format: uuid
        category_id:
          type:
          - string
          - 'null'
          format: uuid
        name:
          type: string
        plan_type:
          type: string
          enum:
          - recurring
          - non_recurring
          - punchcard
        client_status:
          type: string
          enum:
          - member
          - non_member
          - lead
        price:
          $ref: '#/components/schemas/Money'
        price_interval:
          type:
          - string
          - 'null'
          enum:
          - week
          - month
          - year
          - null
        price_interval_count:
          type: integer
        setup_fee:
          $ref: '#/components/schemas/Money'
        tax_rate_percent:
          $ref: '#/components/schemas/Money'
        sessions_count:
          type:
          - integer
          - 'null'
        sessions_expire_count:
          type:
          - integer
          - 'null'
        sessions_expire_interval:
          type:
          - string
          - 'null'
        class_access:
          type: string
          default: all
        open_gym_access:
          type: boolean
        limit_quantity:
          type:
          - integer
          - 'null'
        limit_per_person:
          type:
          - integer
          - 'null'
        force_purchase_start_date:
          type: boolean
        priority_booking:
          type: boolean
        twenty_four_hour_access:
          type: boolean
        publish_purchase_page:
          type: boolean
        guest_checkout:
          type: boolean
        short_description:
          type: string
        detailed_description:
          type: string
        allow_class_booking_after_purchase:
          type: boolean
        purchase_confirmation_message:
          type: string
        show_on_website_plan_list:
          type: boolean
        show_on_kiosk:
          type: boolean
        show_on_members_app:
          type: boolean
        is_deleted:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    PlanSubscription:
      type: object
      required:
      - id
      - plan_id
      - profile_id
      - status
      properties:
        id:
          type: string
          format: uuid
        plan_id:
          type: string
          format: uuid
        profile_id:
          type: string
          format: uuid
        status:
          type: string
          enum:
          - active
          - cancelled
          - expired
          - paused
        sessions_remaining:
          type:
          - integer
          - 'null'
        started_at:
          type:
          - string
          - 'null'
          format: date-time
        expires_at:
          type:
          - string
          - 'null'
          format: date-time
        cancelled_at:
          type:
          - string
          - 'null'
          format: date-time
        subtotal:
          $ref: '#/components/schemas/Money'
        discount_amount:
          $ref: '#/components/schemas/Money'
        tax_amount:
          $ref: '#/components/schemas/Money'
        total:
          $ref: '#/components/schemas/Money'
        next_charge_date:
          type:
          - string
          - 'null'
          format: date
        last_charge_date:
          type:
          - string
          - 'null'
          format: date
        pause_date:
          type:
          - string
          - 'null'
          format: date
        resume_date:
          type:
          - string
          - 'null'
          format: date
        cancel_reason:
          type:
          - string
          - 'null'
        end_date:
          type:
          - string
          - 'null'
          format: date
        payment_failed_at:
          type:
          - string
          - 'null'
          format: date-time
        payment_retry_count:
          type: integer
        payment_failure_reason:
          type:
          - string
          - 'null'
        stripe_subscription_id:
          type:
          - string
          - 'null'
          description: billing:read only
        created_at:
          type: string
          format: date-time
    GroupClass:
      type: object
      required:
      - id
      - source
      - name
      - date
      - start_time
      - end_time
      properties:
        id:
          type: string
          format: uuid
        source:
          type: string
          enum:
          - group_class
          - class_instance
        name:
          type: string
        class_title_id:
          type:
          - string
          - 'null'
          format: uuid
        location_id:
          type:
          - string
          - 'null'
          format: uuid
        studio_id:
          type:
          - string
          - 'null'
          format: uuid
        studio:
          type:
          - string
          - 'null'
        trainer_id:
          type:
          - string
          - 'null'
          format: uuid
        trainer_name:
          type:
          - string
          - 'null'
        date:
          type: string
          format: date
        start_time:
          type: string
        end_time:
          type: string
        capacity:
          type:
          - integer
          - 'null'
        notes:
          type:
          - string
          - 'null'
        description:
          type:
          - string
          - 'null'
        is_virtual:
          type: boolean
        is_cancelled:
          type: boolean
        recurrence_kind:
          type:
          - string
          - 'null'
          enum:
          - weekly
          - biweekly
          - selected_days
          - null
        attachment_url:
          type:
          - string
          - 'null'
        google_event_id:
          type:
          - string
          - 'null'
        created_at:
          type: string
          format: date-time
    GroupClassRegistration:
      type: object
      required:
      - id
      - source
      - class_id
      - class_date
      - status
      - profile_id
      properties:
        id:
          type: string
          format: uuid
        source:
          type: string
          enum:
          - group_class_registration
          - class_registration
        class_id:
          type: string
          format: uuid
        class_date:
          type: string
          format: date
        profile_id:
          type: string
          format: uuid
        booked_for_profile_id:
          type:
          - string
          - 'null'
          format: uuid
        status:
          type: string
          enum:
          - reserved
          - checked_in
          - waitlist
          - no_show
          - late_cancel
          - cancelled
        waitlist_position:
          type:
          - integer
          - 'null'
        registered_at:
          type: string
          format: date-time
        checked_in_at:
          type:
          - string
          - 'null'
          format: date-time
        checked_in_by:
          type:
          - string
          - 'null'
          format: uuid
        marked_no_show_at:
          type:
          - string
          - 'null'
          format: date-time
        promoted_from_waitlist_at:
          type:
          - string
          - 'null'
          format: date-time
    GroupClassRoster:
      type: object
      required:
      - class_id
      - class_date
      - registrations
      properties:
        class_id:
          type: string
          format: uuid
        class_date:
          type: string
          format: date
        capacity:
          type:
          - integer
          - 'null'
        reserved:
          type: integer
        checked_in:
          type: integer
        waitlist:
          type: integer
        registrations:
          type: array
          items:
            $ref: '#/components/schemas/GroupClassRegistration'
    GroupClassCancellation:
      type: object
      required:
      - id
      - group_class_id
      - class_date
      - sub_status
      properties:
        id:
          type: string
          format: uuid
        group_class_id:
          type: string
          format: uuid
        class_date:
          type: string
          format: date
        cancelled_at:
          type: string
          format: date-time
        cancelled_by:
          type:
          - string
          - 'null'
          format: uuid
        reason:
          type:
          - string
          - 'null'
        member_reason:
          type:
          - string
          - 'null'
        sub_status:
          type: string
          enum:
          - none
          - pending
          - approved
          - unclaimed
        reservations_at_cancel:
          type: integer
    SubRequest:
      type: object
      required:
      - id
      - group_class_id
      - class_date
      - status
      - requested_by
      properties:
        id:
          type: string
          format: uuid
        group_class_id:
          type: string
          format: uuid
        class_date:
          type: string
          format: date
        requested_by:
          type: string
          format: uuid
        status:
          type: string
          enum:
          - open
          - filled
          - cancelled
          - unclaimed
        filled_by:
          type:
          - string
          - 'null'
          format: uuid
        notes:
          type:
          - string
          - 'null'
        created_at:
          type: string
          format: date-time
        filled_at:
          type:
          - string
          - 'null'
          format: date-time
    SubOffer:
      type: object
      required:
      - id
      - sub_request_id
      - trainer_id
      properties:
        id:
          type: string
          format: uuid
        sub_request_id:
          type: string
          format: uuid
        trainer_id:
          type: string
          format: uuid
        comment:
          type:
          - string
          - 'null'
        created_at:
          type: string
          format: date-time
    Appointment:
      type: object
      required:
      - id
      - appointment_type_id
      - trainer_id
      - athlete_id
      - date
      - start_time
      - end_time
      - status
      properties:
        id:
          type: string
          format: uuid
        appointment_type_id:
          type: string
          format: uuid
        trainer_id:
          type: string
          format: uuid
        athlete_id:
          type: string
          format: uuid
        date:
          type: string
          format: date
        start_time:
          type: string
        end_time:
          type: string
        status:
          type: string
          enum:
          - booked
          - checked_in
          - no_show
          - cancelled
        notes:
          type:
          - string
          - 'null'
        google_event_id:
          type:
          - string
          - 'null'
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    AppointmentType:
      type: object
      required:
      - id
      - name
      - category
      - duration_minutes
      - is_active
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        category:
          type: string
          enum:
          - personal_training
          - recovery
        duration_minutes:
          type: integer
        is_active:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    Court:
      type: object
      required:
      - id
      - court_number
      - active
      properties:
        id:
          type: string
          format: uuid
        court_number:
          type: integer
        display_name:
          type:
          - string
          - 'null'
        tennis_allowed:
          type: boolean
        pickleball_allowed:
          type: boolean
        racquetball_allowed:
          type: boolean
        active:
          type: boolean
        booking_duration_minutes:
          type: integer
        created_at:
          type: string
          format: date-time
    CourtBooking:
      type: object
      required:
      - id
      - court_id
      - profile_id
      - sport
      - start_at
      - end_at
      - status
      properties:
        id:
          type: string
          format: uuid
        court_id:
          type: string
          format: uuid
        profile_id:
          type: string
          format: uuid
        profile_ids:
          type: array
          items:
            type: string
            format: uuid
        sport:
          type: string
          enum:
          - tennis
          - pickleball
          - racquetball
        half_slot:
          type:
          - string
          - 'null'
        start_at:
          type: string
          format: date-time
        end_at:
          type: string
          format: date-time
        status:
          type: string
          enum:
          - booked
          - cancelled
          - completed
        notes:
          type:
          - string
          - 'null'
        checked_in_at:
          type:
          - string
          - 'null'
          format: date-time
        checked_in_via:
          type:
          - string
          - 'null'
        no_show_charged_at:
          type:
          - string
          - 'null'
          format: date-time
        created_at:
          type: string
          format: date-time
    NoShowFee:
      type: object
      required:
      - booking_id
      - sport
      - start_at
      - end_at
      properties:
        booking_id:
          type: string
          format: uuid
        booker_profile_id:
          type: string
          format: uuid
        sport:
          type: string
        half_slot:
          type:
          - string
          - 'null'
        start_at:
          type: string
          format: date-time
        end_at:
          type: string
          format: date-time
        court_number:
          type: integer
        court_display_name:
          type:
          - string
          - 'null'
        marker:
          type:
          - string
          - 'null'
    InvoiceItem:
      type: object
      required:
      - id
      - invoice_id
      - name
      - quantity
      - price
      properties:
        id:
          type: string
          format: uuid
        invoice_id:
          type: string
          format: uuid
        name:
          type: string
        description:
          type:
          - string
          - 'null'
        quantity:
          type: integer
        price:
          $ref: '#/components/schemas/Money'
        tax_rate:
          $ref: '#/components/schemas/Money'
        discount:
          $ref: '#/components/schemas/Money'
        product_id:
          type:
          - string
          - 'null'
          format: uuid
        variant_name:
          type:
          - string
          - 'null'
        created_at:
          type: string
          format: date-time
    InvoicePayment:
      type: object
      required:
      - id
      - invoice_id
      - amount
      - payment_method
      properties:
        id:
          type: string
          format: uuid
        invoice_id:
          type: string
          format: uuid
        amount:
          $ref: '#/components/schemas/Money'
        payment_method:
          type: string
        payment_method_label:
          type:
          - string
          - 'null'
        paid_at:
          type:
          - string
          - 'null'
          format: date-time
        refund_of_payment_id:
          type:
          - string
          - 'null'
          format: uuid
        reason:
          type:
          - string
          - 'null'
        created_at:
          type: string
          format: date-time
    Invoice:
      type: object
      required:
      - id
      - number
      - status
      - amount_due
      properties:
        id:
          type: string
          format: uuid
        number:
          type: string
          description: User-facing invoice id (DB column `invoice_id`).
        profile_id:
          type:
          - string
          - 'null'
          format: uuid
        guest_first_name:
          type:
          - string
          - 'null'
        guest_last_name:
          type:
          - string
          - 'null'
        guest_email:
          type:
          - string
          - 'null'
          format: email
        status:
          type: string
          enum:
          - open
          - overdue
          - paid
          - forgiven
          - failed
        payment_method_label:
          type:
          - string
          - 'null'
        date_opened:
          type:
          - string
          - 'null'
          format: date-time
        next_attempt_date:
          type:
          - string
          - 'null'
          format: date-time
        last_attempt_date:
          type:
          - string
          - 'null'
          format: date-time
        amount_due:
          $ref: '#/components/schemas/Money'
        amount_paid:
          $ref: '#/components/schemas/Money'
        refunds:
          $ref: '#/components/schemas/Money'
        auto_charge_date:
          type:
          - string
          - 'null'
          format: date-time
        items:
          type: array
          items:
            $ref: '#/components/schemas/InvoiceItem'
        payments:
          type: array
          items:
            $ref: '#/components/schemas/InvoicePayment'
        ship_to_name:
          type:
          - string
          - 'null'
        ship_to_address1:
          type:
          - string
          - 'null'
        ship_to_address2:
          type:
          - string
          - 'null'
        ship_to_city:
          type:
          - string
          - 'null'
        ship_to_state:
          type:
          - string
          - 'null'
        ship_to_zip:
          type:
          - string
          - 'null'
        ship_to_country:
          type:
          - string
          - 'null'
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    PaymentMethod:
      type: object
      required:
      - id
      - brand
      - last4
      properties:
        id:
          type: string
        brand:
          type: string
        last4:
          type: string
        exp_month:
          type: integer
        exp_year:
          type: integer
        is_default:
          type: boolean
        created:
          type: integer
          description: Stripe unix seconds.
    StripeCharge:
      type: object
      required:
      - id
      - amount
      - status
      properties:
        id:
          type: string
        amount:
          $ref: '#/components/schemas/Money'
        amount_refunded:
          $ref: '#/components/schemas/Money'
        status:
          type: string
        created:
          type: integer
        description:
          type:
          - string
          - 'null'
        paid:
          type: boolean
        receipt_url:
          type:
          - string
          - 'null'
          format: uri
        payment_intent:
          type:
          - string
          - 'null'
    BillingState:
      type: object
      description: Composite billing snapshot for a member (wraps assistant_get_billing_state).
      additionalProperties: true
    CalendarSync:
      type: object
      description: Calendar-sync diagnosis snapshot (wraps assistant_get_calendar_sync_diagnosis).
      additionalProperties: true
    EmailDelivery:
      type: object
      description: A recent message delivery event (wraps assistant_get_email_delivery).
      additionalProperties: true
    Communication:
      type: object
      required:
      - id
      - profile_id
      - channel
      - direction
      - sent_at
      properties:
        id:
          type: string
          format: uuid
        profile_id:
          type: string
          format: uuid
        channel:
          type: string
          enum:
          - email
          - sms
        direction:
          type: string
          enum:
          - outbound
          - inbound
          - internal
        subject:
          type:
          - string
          - 'null'
        body:
          type:
          - string
          - 'null'
        status:
          type:
          - string
          - 'null'
        sent_at:
          type: string
          format: date-time
        workflow_run_id:
          type:
          - string
          - 'null'
          format: uuid
    MessageEvent:
      type: object
      required:
      - id
      - provider
      - event_type
      - occurred_at
      properties:
        id:
          type: string
          format: uuid
        communication_id:
          type:
          - string
          - 'null'
          format: uuid
        profile_id:
          type:
          - string
          - 'null'
          format: uuid
        provider:
          type: string
        event_type:
          type: string
        provider_id:
          type:
          - string
          - 'null'
        occurred_at:
          type: string
          format: date-time
    MessageTemplate:
      type: object
      required:
      - id
      - name
      - channel
      - body
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        channel:
          type: string
          enum:
          - email
          - sms
        subject:
          type:
          - string
          - 'null'
        body:
          type: string
        category:
          type:
          - string
          - 'null'
        created_by:
          type:
          - string
          - 'null'
          format: uuid
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    SendMessageRequest:
      type: object
      required:
      - channel
      - profile_id
      - body
      properties:
        channel:
          type: string
          enum:
          - email
          - sms
        profile_id:
          type: string
          format: uuid
        subject:
          type: string
          description: Required for email.
        body:
          type: string
        template_id:
          type: string
          format: uuid
        merge_data:
          type: object
          additionalProperties: true
    Campaign:
      type: object
      required:
      - id
      - name
      - channel
      - body
      - status
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        channel:
          type: string
          enum:
          - email
          - sms
        subject:
          type:
          - string
          - 'null'
        body:
          type: string
        audience:
          type: object
          additionalProperties: true
        drip_config:
          type: object
          additionalProperties: true
        status:
          type: string
        scheduled_at:
          type:
          - string
          - 'null'
          format: date-time
        sends_summary:
          type: object
          properties:
            pending:
              type: integer
            sent:
              type: integer
            failed:
              type: integer
            skipped:
              type: integer
        created_at:
          type: string
          format: date-time
    WorkflowNode:
      type: object
      required:
      - id
      - workflow_id
      - kind
      properties:
        id:
          type: string
          format: uuid
        workflow_id:
          type: string
          format: uuid
        kind:
          type: string
        config:
          type: object
          additionalProperties: true
        position:
          type: object
          properties:
            x:
              type: number
            y:
              type: number
        description:
          type:
          - string
          - 'null'
    WorkflowEdge:
      type: object
      required:
      - id
      - workflow_id
      - from_node_id
      - to_node_id
      properties:
        id:
          type: string
          format: uuid
        workflow_id:
          type: string
          format: uuid
        from_node_id:
          type: string
          format: uuid
        to_node_id:
          type: string
          format: uuid
        condition:
          type:
          - object
          - 'null'
          additionalProperties: true
    Workflow:
      type: object
      required:
      - id
      - name
      - trigger_type
      - is_active
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        description:
          type:
          - string
          - 'null'
        trigger_type:
          type: string
        trigger_config:
          type: object
          additionalProperties: true
        is_active:
          type: boolean
        nodes:
          type: array
          items:
            $ref: '#/components/schemas/WorkflowNode'
        edges:
          type: array
          items:
            $ref: '#/components/schemas/WorkflowEdge'
        created_by:
          type:
          - string
          - 'null'
          format: uuid
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    WorkflowJob:
      type: object
      required:
      - id
      - run_id
      - node_id
      - status
      properties:
        id:
          type: string
          format: uuid
        run_id:
          type: string
          format: uuid
        node_id:
          type: string
          format: uuid
        run_at:
          type: string
          format: date-time
        status:
          type: string
        attempts:
          type: integer
        error:
          type:
          - string
          - 'null'
    WorkflowRun:
      type: object
      required:
      - id
      - workflow_id
      - profile_id
      - status
      - started_at
      properties:
        id:
          type: string
          format: uuid
        workflow_id:
          type: string
          format: uuid
        profile_id:
          type: string
          format: uuid
        status:
          type: string
          enum:
          - active
          - completed
          - errored
          - cancelled
        current_node_id:
          type:
          - string
          - 'null'
          format: uuid
        event_data:
          type: object
          additionalProperties: true
        started_at:
          type: string
          format: date-time
        completed_at:
          type:
          - string
          - 'null'
          format: date-time
        jobs:
          type: array
          items:
            $ref: '#/components/schemas/WorkflowJob'
    Task:
      type: object
      required:
      - id
      - title
      - status
      - priority
      properties:
        id:
          type: string
          format: uuid
        profile_id:
          type:
          - string
          - 'null'
          format: uuid
        assigned_to:
          type:
          - string
          - 'null'
          format: uuid
        title:
          type: string
        description:
          type:
          - string
          - 'null'
        priority:
          type: string
          enum:
          - low
          - medium
          - high
        status:
          type: string
          enum:
          - open
          - done
          - dismissed
        due_at:
          type:
          - string
          - 'null'
          format: date-time
        completed_at:
          type:
          - string
          - 'null'
          format: date-time
        created_by_workflow_run:
          type:
          - string
          - 'null'
          format: uuid
        created_at:
          type: string
          format: date-time
    TaskCreate:
      type: object
      required:
      - title
      properties:
        profile_id:
          type: string
          format: uuid
        assigned_to:
          type: string
          format: uuid
        title:
          type: string
        description:
          type: string
        priority:
          type: string
          enum:
          - low
          - medium
          - high
        due_at:
          type: string
          format: date-time
    Document:
      type: object
      required:
      - id
      - title
      - status
      - attach_to
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        body:
          type: string
        current_version:
          type: integer
        status:
          type: string
          enum:
          - draft
          - published
          - archived
        attach_to:
          type: string
        attach_target_id:
          type:
          - string
          - 'null'
          format: uuid
        attach_target_table:
          type:
          - string
          - 'null'
        published_at:
          type:
          - string
          - 'null'
          format: date-time
        created_by:
          type:
          - string
          - 'null'
          format: uuid
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    UserDocument:
      type: object
      required:
      - id
      - profile_id
      - name
      - status
      properties:
        id:
          type: string
          format: uuid
        document_id:
          type:
          - string
          - 'null'
          format: uuid
        profile_id:
          type: string
          format: uuid
        name:
          type: string
        version:
          type: integer
        status:
          type: string
          enum:
          - unsigned
          - signed
          - revoked
          - stale
        category:
          type: string
        signed_at:
          type:
          - string
          - 'null'
          format: date-time
        expires_at:
          type:
          - string
          - 'null'
          format: date
        signed_by_name:
          type:
          - string
          - 'null'
        signed_via:
          type: string
        program_id:
          type:
          - string
          - 'null'
        created_at:
          type: string
          format: date-time
    UserDocumentFile:
      type: object
      required:
      - url
      - expires_at
      properties:
        url:
          type: string
          format: uri
        expires_at:
          type: string
          format: date-time
    Program:
      type: object
      required:
      - id
      - name
      - status
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        department:
          type:
          - string
          - 'null'
        description:
          type:
          - string
          - 'null'
        instructor_id:
          type:
          - string
          - 'null'
          format: uuid
        max_capacity:
          type:
          - integer
          - 'null'
        session_count:
          type:
          - integer
          - 'null'
        start_date:
          type:
          - string
          - 'null'
          format: date
        end_date:
          type:
          - string
          - 'null'
          format: date
        schedule_description:
          type:
          - string
          - 'null'
        fee:
          $ref: '#/components/schemas/Money'
        status:
          type: string
          enum:
          - draft
          - active
          - completed
          - cancelled
        location_id:
          type:
          - string
          - 'null'
          format: uuid
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    ProgramSession:
      type: object
      required:
      - id
      - program_id
      - date
      properties:
        id:
          type: string
          format: uuid
        program_id:
          type: string
          format: uuid
        date:
          type: string
          format: date
        start_time:
          type:
          - string
          - 'null'
        end_time:
          type:
          - string
          - 'null'
        is_cancelled:
          type: boolean
        notes:
          type:
          - string
          - 'null'
    ProgramRegistration:
      type: object
      required:
      - id
      - program_id
      - profile_id
      - status
      properties:
        id:
          type: string
          format: uuid
        program_id:
          type: string
          format: uuid
        profile_id:
          type: string
          format: uuid
        status:
          type: string
          enum:
          - registered
          - waitlisted
          - withdrawn
          - completed
        registered_at:
          type: string
          format: date-time
    JccOffering:
      type: object
      required:
      - id
      - name
      - offering_kind
      properties:
        id:
          type: string
          description: Text id (legacy).
        offering_type:
          type:
          - string
          - 'null'
        name:
          type: string
        offering_kind:
          type: string
          enum:
          - standard
          - package
          - session
          - bundle
        parent_offering_id:
          type:
          - string
          - 'null'
        location:
          type:
          - string
          - 'null'
        start_date:
          type:
          - string
          - 'null'
          format: date
        end_date:
          type:
          - string
          - 'null'
          format: date
        days:
          type:
          - string
          - 'null'
        time:
          type:
          - string
          - 'null'
        registration_opens:
          type:
          - string
          - 'null'
          format: date
        notes:
          type:
          - string
          - 'null'
        price_min:
          $ref: '#/components/schemas/Money'
        price_max:
          $ref: '#/components/schemas/Money'
        member_price:
          $ref: '#/components/schemas/Money'
        non_member_price:
          $ref: '#/components/schemas/Money'
        registration_fee:
          $ref: '#/components/schemas/Money'
        deposit:
          $ref: '#/components/schemas/Money'
        tax_deductible:
          type: boolean
        pack_count:
          type:
          - integer
          - 'null'
        capacity:
          type:
          - integer
          - 'null'
        registered_count:
          type: integer
        age_min:
          type:
          - integer
          - 'null'
        age_max:
          type:
          - integer
          - 'null'
        registration_in_house_opens:
          type:
          - string
          - 'null'
          format: date-time
        registration_in_house_closes:
          type:
          - string
          - 'null'
          format: date-time
        registration_online_opens:
          type:
          - string
          - 'null'
          format: date-time
        registration_online_closes:
          type:
          - string
          - 'null'
          format: date-time
        agreements:
          type: array
          items:
            type: string
        due_date_label:
          type: string
        branch:
          type: string
    JccOfferingCategory:
      type: object
      required:
      - id
      - name
      properties:
        id:
          type: string
        name:
          type: string
    RevenueReport:
      type: object
      description: Aggregate revenue with per-bucket totals.
      properties:
        since:
          type: string
          format: date
        until:
          type: string
          format: date
        group_by:
          type: string
          enum:
          - day
          - week
          - month
        buckets:
          type: array
          items:
            type: object
            properties:
              bucket:
                type: string
              gross:
                $ref: '#/components/schemas/Money'
              refunds:
                $ref: '#/components/schemas/Money'
              net:
                $ref: '#/components/schemas/Money'
    EngagementReport:
      type: object
      properties:
        since:
          type: string
          format: date
        until:
          type: string
          format: date
        days:
          type: array
          items:
            type: object
            properties:
              d:
                type: string
                format: date
              classes:
                type: integer
              pt:
                type: integer
              recovery:
                type: integer
              events:
                type: integer
    AttendanceReport:
      type: object
      properties:
        since:
          type: string
          format: date
        until:
          type: string
          format: date
        by_day:
          type: array
          items:
            type: object
            properties:
              d:
                type: string
                format: date
              count:
                type: integer
        by_class_type:
          type: array
          items:
            type: object
            properties:
              class_type:
                type: string
              count:
                type: integer
    FunnelReport:
      type: object
      properties:
        since:
          type: string
          format: date
        stages:
          type: array
          items:
            type: object
            properties:
              stage:
                type: string
              count:
                type: integer
    Celebrations:
      type: object
      properties:
        date:
          type: string
          format: date
        birthdays:
          type: array
          items:
            $ref: '#/components/schemas/Member'
        anniversaries:
          type: array
          items:
            $ref: '#/components/schemas/Member'
    PersonalRecord:
      type: object
      required:
      - id
      - athlete_id
      - exercise_id
      - pr_type
      - value
      - achieved_at
      properties:
        id:
          type: string
          format: uuid
        athlete_id:
          type: string
          format: uuid
        exercise_id:
          type: string
          format: uuid
        session_id:
          type:
          - string
          - 'null'
          format: uuid
        pr_type:
          type: string
          enum:
          - weight
          - reps
          - volume
          - estimated_1rm
        value:
          type: number
        unit:
          type: string
        reps:
          type:
          - integer
          - 'null'
        previous_value:
          type:
          - number
          - 'null'
        achieved_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
    AttendanceRow:
      type: object
      properties:
        d:
          type: string
          format: date
        kind:
          type: string
          enum:
          - class
          - appointment
          - court
          - program
        ref_id:
          type: string
          format: uuid
        ref_name:
          type: string
        status:
          type: string
    TourBooking:
      type: object
      required:
      - id
      - prospect_profile_id
      - tour_date
      - status
      properties:
        id:
          type: string
          format: uuid
        prospect_profile_id:
          type: string
          format: uuid
        tour_date:
          type: string
          format: date-time
        tour_guide_id:
          type:
          - string
          - 'null'
          format: uuid
        status:
          type: string
          enum:
          - scheduled
          - completed
          - no_show
          - cancelled
        notes:
          type:
          - string
          - 'null'
        completed_at:
          type:
          - string
          - 'null'
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    SupportTicket:
      type: object
      required:
      - id
      - profile_id
      - category
      - subject
      - body
      - status
      - priority
      properties:
        id:
          type: string
          format: uuid
        profile_id:
          type: string
          format: uuid
        category:
          type: string
        subject:
          type: string
        body:
          type: string
        status:
          type: string
          enum:
          - open
          - in_progress
          - resolved
          - closed
        priority:
          type: string
          enum:
          - low
          - normal
          - high
        context:
          type:
          - object
          - 'null'
          additionalProperties: true
        assigned_to:
          type:
          - string
          - 'null'
          format: uuid
        last_message_at:
          type: string
          format: date-time
        resolved_at:
          type:
          - string
          - 'null'
          format: date-time
        created_at:
          type: string
          format: date-time
    SupportTicketMessage:
      type: object
      required:
      - id
      - ticket_id
      - body
      - from_staff
      - created_at
      properties:
        id:
          type: string
          format: uuid
        ticket_id:
          type: string
          format: uuid
        profile_id:
          type:
          - string
          - 'null'
          format: uuid
        trainer_id:
          type:
          - string
          - 'null'
          format: uuid
        body:
          type: string
        from_staff:
          type: boolean
        created_at:
          type: string
          format: date-time
    WebhookSubscription:
      type: object
      required:
      - id
      - url
      - events
      - active
      properties:
        id:
          type: string
          format: uuid
        url:
          type: string
          format: uri
        events:
          type: array
          items:
            type: string
        description:
          type:
          - string
          - 'null'
        active:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    WebhookSubscriptionCreated:
      allOf:
      - $ref: '#/components/schemas/WebhookSubscription'
      - type: object
        required:
        - signing_secret
        properties:
          signing_secret:
            type: string
            description: Returned exactly once. Store it; rotate via `:rotate_secret`.
    WebhookDelivery:
      type: object
      required:
      - id
      - subscription_id
      - event_id
      - event_type
      - status
      properties:
        id:
          type: string
          format: uuid
        subscription_id:
          type: string
          format: uuid
        event_id:
          type: string
          format: uuid
        event_type:
          type: string
        payload:
          type: object
          additionalProperties: true
        status:
          type: string
          enum:
          - pending
          - delivered
          - failed
          - exhausted
        attempts:
          type: integer
        next_attempt_at:
          type: string
          format: date-time
        last_response_status:
          type:
          - integer
          - 'null'
        last_response_body:
          type:
          - string
          - 'null'
        created_at:
          type: string
          format: date-time
    BlogStats:
      type: object
      description: Aggregated platform stats for the public blog (wraps /api/blog-stats).
      additionalProperties: true
    MarketingLeadCreate:
      type: object
      required:
      - email
      properties:
        name:
          type: string
        email:
          type: string
          format: email
        phone:
          type: string
        gym_name:
          type: string
        gym_location:
          type: string
        headcount:
          type: string
        looking_for:
          type: string
        source:
          type: string
    PublicScheduleRow:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        date:
          type: string
          format: date
        start_time:
          type: string
        end_time:
          type: string
        trainer:
          type:
          - string
          - 'null'
        capacity:
          type:
          - integer
          - 'null'
        spots_left:
          type:
          - integer
          - 'null'
    SqlSelectRequest:
      type: object
      required:
      - query
      properties:
        query:
          type: string
          description: SELECT or WITH only. Validated server-side; RLS applies.
    SqlSelectResponse:
      type: object
      required:
      - rows
      properties:
        rows:
          type: array
          items:
            type: object
            additionalProperties: true
    MemberList:
      type: object
      required:
      - data
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Member'
        next_cursor:
          type:
          - string
          - 'null'
        count_estimate:
          type: integer
        total:
          type:
          - integer
          - 'null'
    LeadList:
      type: object
      required:
      - data
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Lead'
        next_cursor:
          type:
          - string
          - 'null'
        count_estimate:
          type: integer
    GroupClassList:
      type: object
      required:
      - data
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/GroupClass'
        next_cursor:
          type:
          - string
          - 'null'
    AppointmentList:
      type: object
      required:
      - data
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Appointment'
        next_cursor:
          type:
          - string
          - 'null'
    CourtBookingList:
      type: object
      required:
      - data
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/CourtBooking'
        next_cursor:
          type:
          - string
          - 'null'
    PlanList:
      type: object
      required:
      - data
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Plan'
        next_cursor:
          type:
          - string
          - 'null'
    PlanSubscriptionList:
      type: object
      required:
      - data
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/PlanSubscription'
        next_cursor:
          type:
          - string
          - 'null'
    MembershipList:
      type: object
      required:
      - data
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Membership'
        next_cursor:
          type:
          - string
          - 'null'
    InvoiceList:
      type: object
      required:
      - data
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Invoice'
        next_cursor:
          type:
          - string
          - 'null'
    CommunicationList:
      type: object
      required:
      - data
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Communication'
        next_cursor:
          type:
          - string
          - 'null'
    MessageTemplateList:
      type: object
      required:
      - data
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/MessageTemplate'
        next_cursor:
          type:
          - string
          - 'null'
    CampaignList:
      type: object
      required:
      - data
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Campaign'
        next_cursor:
          type:
          - string
          - 'null'
    WorkflowList:
      type: object
      required:
      - data
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Workflow'
        next_cursor:
          type:
          - string
          - 'null'
    WorkflowRunList:
      type: object
      required:
      - data
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/WorkflowRun'
        next_cursor:
          type:
          - string
          - 'null'
    TaskList:
      type: object
      required:
      - data
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Task'
        next_cursor:
          type:
          - string
          - 'null'
    NoteList:
      type: object
      required:
      - data
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Note'
        next_cursor:
          type:
          - string
          - 'null'
    DocumentList:
      type: object
      required:
      - data
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Document'
        next_cursor:
          type:
          - string
          - 'null'
    UserDocumentList:
      type: object
      required:
      - data
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/UserDocument'
        next_cursor:
          type:
          - string
          - 'null'
    TourBookingList:
      type: object
      required:
      - data
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/TourBooking'
        next_cursor:
          type:
          - string
          - 'null'
    SupportTicketList:
      type: object
      required:
      - data
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/SupportTicket'
        next_cursor:
          type:
          - string
          - 'null'
    ProgramList:
      type: object
      required:
      - data
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Program'
        next_cursor:
          type:
          - string
          - 'null'
    WebhookSubscriptionList:
      type: object
      required:
      - data
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/WebhookSubscription'
    WebhookDeliveryList:
      type: object
      required:
      - data
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/WebhookDelivery'
        next_cursor:
          type:
          - string
          - 'null'
    WebhookEventCatalogEntry:
      type: object
      required: [event_type, resource, action, description]
      properties:
        event_type:
          type: string
          example: lead.stage_changed
        resource:
          type: string
          example: lead
        action:
          type: string
          example: stage_changed
        description:
          type: string
        example_payload:
          type: object
        added_at:
          type: string
          format: date-time
    SearchHit:
      type: object
      required: [type, id, label]
      properties:
        type:
          type: string
          enum: [members, leads, classes, invoices, tasks]
        id:
          type: string
        label:
          type: string
        sublabel:
          type: string
        href:
          type: string
    Workout:
      type: object
      required: [id, athlete_id, date]
      properties:
        id: { type: string, format: uuid }
        athlete_id: { type: string, format: uuid }
        programmed_by: { type: [string, 'null'], format: uuid }
        date: { type: string, format: date }
        status: { type: string }
        title: { type: [string, 'null'] }
        raw_text: { type: [string, 'null'] }
        created_at: { type: string, format: date-time }
    WorkoutDetail:
      allOf:
      - $ref: '#/components/schemas/Workout'
      - type: object
        properties:
          sections:
            type: array
            items:
              type: object
              properties:
                id: { type: string, format: uuid }
                name: { type: [string, 'null'] }
                order: { type: integer }
                exercises:
                  type: array
                  items:
                    type: object
                    properties:
                      id: { type: string, format: uuid }
                      exercise_id: { type: string, format: uuid }
                      order: { type: integer }
                      superset_group: { type: [integer, 'null'] }
                      sets: { type: [integer, 'null'] }
                      reps: { type: [integer, 'null'] }
                      rep_qualifier: { type: [string, 'null'] }
                      load_value: { type: [string, 'null'] }
                      load_unit: { type: [string, 'null'] }
                      equipment: { type: [string, 'null'] }
                      notes: { type: [string, 'null'] }
                      metadata: { type: object }
    JccOfferingList:
      type: object
      required:
      - data
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/JccOffering'
        next_cursor:
          type:
          - string
          - 'null'
paths:
  /health:
    get:
      tags:
      - Platform
      operationId: get_health
      summary: Liveness check
      security: []
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                required:
                - ok
                - version
                - time
                properties:
                  ok:
                    type: boolean
                    const: true
                  version:
                    type: string
                  time:
                    type: string
                    format: date-time
        '422':
          $ref: '#/components/responses/Validation'
  /me:
    get:
      tags:
      - Platform
      operationId: get_me
      summary: Describe the current token + tenant
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Me'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /openapi.yaml:
    get:
      tags:
      - Platform
      operationId: get_openapi_yaml
      summary: OpenAPI spec (YAML)
      security: []
      responses:
        '200':
          description: OK
          content:
            application/yaml:
              schema:
                type: string
        '422':
          $ref: '#/components/responses/Validation'
  /openapi.json:
    get:
      tags:
      - Platform
      operationId: get_openapi_json
      summary: OpenAPI spec (JSON)
      security: []
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '422':
          $ref: '#/components/responses/Validation'
  /gym:
    get:
      tags:
      - Tenant
      operationId: get_gym
      summary: Get the current tenant's gym record
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Gym'
        '401':
          $ref: '#/components/responses/Unauthorized'
    patch:
      tags:
      - Tenant
      - Admin
      operationId: update_gym
      summary: Update tenant settings (admin)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                display_name:
                  type: string
                tagline:
                  type: string
                primary_color:
                  type: string
                accent_color:
                  type: string
                outbound_email_from_name:
                  type: string
                custom_values:
                  type: object
                  additionalProperties: true
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Gym'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/Validation'
  /members:
    get:
      tags:
      - Members
      operationId: list_members
      summary: List members
      parameters:
      - $ref: '#/components/parameters/cursor'
      - $ref: '#/components/parameters/limit'
      - $ref: '#/components/parameters/sort'
      - $ref: '#/components/parameters/include_total'
      - name: filter[email]
        in: query
        schema:
          type: string
      - name: filter[phone]
        in: query
        schema:
          type: string
      - name: filter[lead_status]
        in: query
        schema:
          type: string
      - name: filter[account_status]
        in: query
        schema:
          type: string
      - name: filter[tag]
        in: query
        schema:
          type: string
      - name: filter[assigned_to]
        in: query
        schema:
          type: string
          format: uuid
      - name: filter[created_at_gte]
        in: query
        schema:
          type: string
          format: date-time
      - name: filter[created_at_lte]
        in: query
        schema:
          type: string
          format: date-time
      - name: filter[last_check_in_at_gte]
        in: query
        schema:
          type: string
          format: date-time
      - name: filter[last_check_in_at_lte]
        in: query
        schema:
          type: string
          format: date-time
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MemberList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
    post:
      tags:
      - Members
      operationId: create_member
      summary: Create a member
      parameters:
      - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MemberCreate'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Member'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Validation'
  /members/search:
    get:
      tags:
      - Members
      operationId: search_members
      summary: Free-text member search
      parameters:
      - name: q
        in: query
        required: true
        schema:
          type: string
          minLength: 2
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Member'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /members/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Members
      operationId: get_member
      summary: Get a member
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Member'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags:
      - Members
      operationId: update_member
      summary: Update a member
      parameters:
      - $ref: '#/components/parameters/ifMatch'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MemberPatch'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Member'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '412':
          $ref: '#/components/responses/PreconditionFailed'
        '422':
          $ref: '#/components/responses/Validation'
    delete:
      tags:
      - Members
      - Admin
      operationId: delete_member
      summary: Soft-delete a member (admin)
      responses:
        '202':
          description: Accepted — deletion enqueued.
          content:
            application/json:
              schema:
                type: object
                properties:
                  task_id:
                    type: string
                    format: uuid
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /members/{id}/billing_state:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Members
      - Billing
      operationId: get_member_billing_state
      summary: Composite billing snapshot for a member
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BillingState'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /members/{id}/calendar_sync:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Members
      operationId: get_member_calendar_sync
      summary: Calendar-sync diagnosis
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CalendarSync'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /members/{id}/email_delivery:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    - name: limit
      in: query
      schema:
        type: integer
        default: 20
        maximum: 100
    get:
      tags:
      - Members
      - Messaging
      operationId: get_member_email_delivery
      summary: Recent email delivery events for a member
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/EmailDelivery'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /members/{id}/notes:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Notes
      operationId: list_member_notes
      summary: List notes on a member
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NoteList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
    post:
      tags:
      - Notes
      operationId: create_member_note
      summary: Add a note to a member
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NoteCreate'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Note'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /members/{id}/communications:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    - name: filter[channel]
      in: query
      schema:
        type: string
        enum:
        - email
        - sms
    - name: filter[status]
      in: query
      schema:
        type: string
    - name: filter[since]
      in: query
      schema:
        type: string
        format: date-time
    get:
      tags:
      - Members
      - Messaging
      operationId: list_member_communications
      summary: List communications for a member
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CommunicationList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /members/{id}/charges:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    - name: limit
      in: query
      schema:
        type: integer
        default: 20
        maximum: 100
    get:
      tags:
      - Members
      - Billing
      operationId: list_member_charges
      summary: Recent Stripe charges for a member
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/StripeCharge'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /members/{id}/no_show_fees:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Members
      - Billing
      operationId: list_member_no_show_fees
      summary: Outstanding no-show fees for a member
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/NoShowFee'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /members/{id}/documents:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Members
      - Documents
      operationId: list_member_documents
      summary: Per-member user_documents
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserDocumentList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /members/{id}/tags:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Members
      operationId: add_member_tag
      summary: Add a tag to a member
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - tag
              properties:
                tag:
                  type: string
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /members/{id}/tags/{tag}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    - name: tag
      in: path
      required: true
      schema:
        type: string
    delete:
      tags:
      - Members
      operationId: remove_member_tag
      summary: Remove a tag from a member
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /members/{id}/memberships:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Members
      - Billing
      operationId: list_member_memberships
      summary: Memberships attached to a member
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MembershipList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /members/{id}/plan_subscriptions:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Members
      - Billing
      operationId: list_member_plan_subscriptions
      summary: A member's plan subscriptions
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlanSubscriptionList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /members/{id}/attendance:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    - name: since
      in: query
      schema:
        type: string
        format: date
    - name: until
      in: query
      schema:
        type: string
        format: date
    get:
      tags:
      - Members
      operationId: list_member_attendance
      summary: Member attendance across surfaces
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AttendanceRow'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /members/{id}/personal_records:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Members
      - Workouts
      operationId: list_member_prs
      summary: Personal records for a member
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PersonalRecord'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /members/{id}/lead_stage_history:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Members
      - Leads
      operationId: list_member_lead_history
      summary: Lead-stage transition history
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/LeadStageEvent'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /members/{id}/alerts:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Members
      operationId: list_member_alerts
      summary: Active alerts on a member's profile
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/MemberAlert'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /leads:
    get:
      tags:
      - Leads
      operationId: list_leads
      summary: List leads
      parameters:
      - $ref: '#/components/parameters/cursor'
      - $ref: '#/components/parameters/limit'
      - $ref: '#/components/parameters/sort'
      - name: filter[lead_status]
        in: query
        schema:
          type: string
      - name: filter[assigned_to]
        in: query
        schema:
          type: string
          format: uuid
      - name: filter[source]
        in: query
        schema:
          type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LeadList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
    post:
      tags:
      - Leads
      operationId: create_lead
      summary: Create a lead
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MemberCreate'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Lead'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /leads/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Leads
      operationId: get_lead
      summary: Get a lead
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Lead'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags:
      - Leads
      operationId: update_lead
      summary: Update a lead
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MemberPatch'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Lead'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /leads/{id}/set_stage:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Leads
      operationId: set_lead_stage
      summary: Move a lead to a new stage
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - to_status
              properties:
                to_status:
                  type: string
                note:
                  type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Lead'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /leads/{id}/score:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Leads
      operationId: score_lead
      summary: AI-score this lead now
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                required:
                - score
                properties:
                  score:
                    type: integer
                  reasoning:
                    type:
                    - string
                    - 'null'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /leads/{id}/convert:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Leads
      - Members
      operationId: convert_lead
      summary: Convert a lead into a member
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - membership_type
              - monthly_charge
              properties:
                membership_type:
                  type: string
                monthly_charge:
                  $ref: '#/components/schemas/Money'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Member'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /leads/{id}/assign:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Leads
      operationId: assign_lead
      summary: Assign a lead to a trainer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - trainer_id
              properties:
                trainer_id:
                  type: string
                  format: uuid
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Lead'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /group_classes:
    get:
      tags:
      - Schedule
      operationId: list_group_classes
      summary: List class occurrences in a date window
      parameters:
      - name: date
        in: query
        schema:
          type: string
          format: date
        description: Single date, or use date_range_end for a window.
      - name: date_range_end
        in: query
        schema:
          type: string
          format: date
      - name: filter[trainer_id]
        in: query
        schema:
          type: string
          format: uuid
      - name: filter[class_title_id]
        in: query
        schema:
          type: string
          format: uuid
      - name: filter[location_id]
        in: query
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GroupClassList'
        '403':
          $ref: '#/components/responses/FeatureNotEnabled'
  /group_classes/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    - name: date
      in: query
      schema:
        type: string
        format: date
    get:
      tags:
      - Schedule
      operationId: get_group_class
      summary: Get a class occurrence
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GroupClass'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /group_classes/{id}/roster:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    - name: date
      in: query
      required: true
      schema:
        type: string
        format: date
    get:
      tags:
      - Schedule
      operationId: get_class_roster
      summary: Full roster for one class occurrence
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GroupClassRoster'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /group_classes/{id}/registrations:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Schedule
      operationId: register_for_class
      summary: Reserve a spot (or waitlist) for a member
      parameters:
      - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - date
              - profile_id
              properties:
                date:
                  type: string
                  format: date
                profile_id:
                  type: string
                  format: uuid
                booked_for_profile_id:
                  type: string
                  format: uuid
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GroupClassRegistration'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /group_classes/{id}/registrations/bulk:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Schedule
      operationId: register_bulk_for_class
      summary: Bulk-register members into a class
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - date
              - member_ids
              properties:
                date:
                  type: string
                  format: date
                member_ids:
                  type: array
                  items:
                    type: string
                    format: uuid
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  created:
                    type: array
                    items:
                      type: string
                      format: uuid
                  failed:
                    type: array
                    items:
                      type: object
                      properties:
                        member_id:
                          type: string
                        reason:
                          type: string
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /group_class_registrations/check_in:
    post:
      tags:
      - Schedule
      operationId: check_in_class
      summary: Mark attended / no-show from a roster
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - attended_ids
              - no_show_ids
              properties:
                attended_ids:
                  type: array
                  items:
                    type: string
                    format: uuid
                no_show_ids:
                  type: array
                  items:
                    type: string
                    format: uuid
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /group_class_registrations/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    delete:
      tags:
      - Schedule
      operationId: cancel_class_registration
      summary: Cancel a single registration (promotes from waitlist if applicable)
      responses:
        '200':
          description: Cancelled. Includes any promoted member.
          content:
            application/json:
              schema:
                type: object
                properties:
                  promoted_profile_id:
                    type:
                    - string
                    - 'null'
                    format: uuid
                  promoted_registration_id:
                    type:
                    - string
                    - 'null'
                    format: uuid
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /group_classes/{id}/cancellations:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Schedule
      operationId: cancel_class_occurrence
      summary: Cancel a class occurrence on a specific date
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - date
              properties:
                date:
                  type: string
                  format: date
                reason:
                  type: string
                member_reason:
                  type: string
                post_sub_request:
                  type: boolean
                sub_request_notes:
                  type: string
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GroupClassCancellation'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /group_classes/{id}/cancellations/{date}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    - name: date
      in: path
      required: true
      schema:
        type: string
        format: date
    delete:
      tags:
      - Schedule
      operationId: uncancel_class_occurrence
      summary: Reverse a class cancellation
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /group_class_sub_requests:
    get:
      tags:
      - Schedule
      operationId: list_sub_requests
      summary: Open sub requests for the tenant
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/SubRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /group_class_sub_requests/{id}/offers:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Schedule
      operationId: create_sub_offer
      summary: Trainer offers to cover a sub request
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - trainer_id
              properties:
                trainer_id:
                  type: string
                  format: uuid
                notes:
                  type: string
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubOffer'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /appointments:
    get:
      tags:
      - Schedule
      operationId: list_appointments
      summary: List appointments
      parameters:
      - $ref: '#/components/parameters/cursor'
      - $ref: '#/components/parameters/limit'
      - name: date
        in: query
        schema:
          type: string
          format: date
      - name: date_range_end
        in: query
        schema:
          type: string
          format: date
      - name: filter[trainer_id]
        in: query
        schema:
          type: string
          format: uuid
      - name: filter[athlete_id]
        in: query
        schema:
          type: string
          format: uuid
      - name: filter[status]
        in: query
        schema:
          type: string
          enum:
          - booked
          - checked_in
          - no_show
          - cancelled
      - name: filter[appointment_type_id]
        in: query
        schema:
          type: string
          format: uuid
      - name: filter[category]
        in: query
        schema:
          type: string
          enum:
          - personal_training
          - recovery
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AppointmentList'
        '403':
          $ref: '#/components/responses/FeatureNotEnabled'
    post:
      tags:
      - Schedule
      operationId: create_appointment
      summary: Book an appointment
      parameters:
      - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - appointment_type_id
              - trainer_id
              - athlete_id
              - date
              - start_time
              - end_time
              properties:
                appointment_type_id:
                  type: string
                  format: uuid
                trainer_id:
                  type: string
                  format: uuid
                athlete_id:
                  type: string
                  format: uuid
                date:
                  type: string
                  format: date
                start_time:
                  type: string
                end_time:
                  type: string
                notes:
                  type: string
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Appointment'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /appointments/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Schedule
      operationId: get_appointment
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Appointment'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Get appointment
    patch:
      tags:
      - Schedule
      operationId: update_appointment
      summary: Update an appointment
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                date:
                  type: string
                  format: date
                start_time:
                  type: string
                end_time:
                  type: string
                trainer_id:
                  type: string
                  format: uuid
                appointment_type_id:
                  type: string
                  format: uuid
                status:
                  type: string
                  enum:
                  - booked
                  - checked_in
                  - no_show
                  - cancelled
                notes:
                  type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Appointment'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
    delete:
      tags:
      - Schedule
      operationId: cancel_appointment
      summary: Cancel an appointment (soft)
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /appointments/{id}/check_in:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Schedule
      operationId: check_in_appointment
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Appointment'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Check in appointment
  /appointment_types:
    get:
      tags:
      - Schedule
      operationId: list_appointment_types
      summary: List appointment types
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AppointmentType'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /courts:
    get:
      tags:
      - Schedule
      operationId: list_courts
      summary: List courts
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Court'
        '403':
          $ref: '#/components/responses/FeatureNotEnabled'
  /court_bookings:
    get:
      tags:
      - Schedule
      operationId: list_court_bookings
      summary: List court bookings
      parameters:
      - name: date
        in: query
        schema:
          type: string
          format: date
      - name: court_id
        in: query
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CourtBookingList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
    post:
      tags:
      - Schedule
      operationId: create_court_booking
      summary: Book a court (member or staff-on-behalf-of)
      parameters:
      - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - court_id
              - sport
              - start_at
              - minutes
              properties:
                court_id:
                  type: string
                  format: uuid
                sport:
                  type: string
                  enum:
                  - tennis
                  - pickleball
                  - racquetball
                half_slot:
                  type: string
                start_at:
                  type: string
                  format: date-time
                minutes:
                  type: integer
                notes:
                  type: string
                additional_profiles:
                  type: array
                  items:
                    type: string
                    format: uuid
                on_behalf_of:
                  type: string
                  format: uuid
                  description: Staff books for this member.
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CourtBooking'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /court_bookings/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    delete:
      tags:
      - Schedule
      operationId: cancel_court_booking
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Cancel court booking
  /court_bookings/{id}/check_in:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Schedule
      operationId: check_in_court_booking
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CourtBooking'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Check in court booking
  /no_show_fees/{booking_id}/collect:
    parameters:
    - name: booking_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Billing
      operationId: collect_no_show_fee
      summary: Record collection of an unpaid no-show fee
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - method
              properties:
                method:
                  type: string
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /no_show_fees/{booking_id}/waive:
    parameters:
    - name: booking_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Billing
      operationId: waive_no_show_fee
      summary: Waive an unpaid no-show fee
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - reason
              properties:
                reason:
                  type: string
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /plans:
    get:
      tags:
      - Billing
      operationId: list_plans
      summary: List plans
      parameters:
      - $ref: '#/components/parameters/cursor'
      - $ref: '#/components/parameters/limit'
      - name: filter[category_id]
        in: query
        schema:
          type: string
          format: uuid
      - name: filter[plan_type]
        in: query
        schema:
          type: string
          enum:
          - recurring
          - non_recurring
          - punchcard
      - name: filter[is_deleted]
        in: query
        schema:
          type: boolean
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlanList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
    post:
      tags:
      - Billing
      operationId: create_plan
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Plan'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Plan'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Create plan
  /plans/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Billing
      operationId: get_plan
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Plan'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Get plan
    patch:
      tags:
      - Billing
      operationId: update_plan
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Plan'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Plan'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Update plan
  /plan_subscriptions:
    get:
      tags:
      - Billing
      operationId: list_plan_subscriptions
      parameters:
      - $ref: '#/components/parameters/cursor'
      - $ref: '#/components/parameters/limit'
      - name: filter[profile_id]
        in: query
        schema:
          type: string
          format: uuid
      - name: filter[status]
        in: query
        schema:
          type: string
          enum:
          - active
          - cancelled
          - expired
          - paused
      - name: filter[plan_id]
        in: query
        schema:
          type: string
          format: uuid
      - name: filter[expires_at_lte]
        in: query
        schema:
          type: string
          format: date-time
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlanSubscriptionList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: List plan subscriptions
    post:
      tags:
      - Billing
      operationId: create_plan_subscription
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - profile_id
              - plan_id
              properties:
                profile_id:
                  type: string
                  format: uuid
                plan_id:
                  type: string
                  format: uuid
                started_at:
                  type: string
                  format: date-time
                payment_method_id:
                  type: string
                end_date:
                  type: string
                  format: date
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlanSubscription'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Create plan subscription
  /plan_subscriptions/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Billing
      operationId: get_plan_subscription
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlanSubscription'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Get plan subscription
  /plan_subscriptions/{id}/cancel:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Billing
      operationId: cancel_plan_subscription
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                end_date:
                  type: string
                  format: date
                reason:
                  type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlanSubscription'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Cancel plan subscription
  /plan_subscriptions/{id}/pause:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Billing
      operationId: pause_plan_subscription
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - pause_date
              - resume_date
              properties:
                pause_date:
                  type: string
                  format: date
                resume_date:
                  type: string
                  format: date
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlanSubscription'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Pause plan subscription
  /plan_subscriptions/{id}/resume:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Billing
      operationId: resume_plan_subscription
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlanSubscription'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Resume plan subscription
  /memberships:
    get:
      tags:
      - Billing
      operationId: list_memberships
      parameters:
      - $ref: '#/components/parameters/cursor'
      - $ref: '#/components/parameters/limit'
      - name: filter[status]
        in: query
        schema:
          type: string
          enum:
          - active
          - inactive
          - on_hold
          - prospect
      - name: filter[primary_profile_id]
        in: query
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MembershipList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: List memberships
  /memberships/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Billing
      operationId: get_membership
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Membership'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Get membership
  /memberships/{id}/transfer_primary:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Billing
      - Admin
      operationId: transfer_membership_primary
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - new_primary_id
              properties:
                new_primary_id:
                  type: string
                  format: uuid
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Membership'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Transfer membership primary
  /programs:
    get:
      tags:
      - Programs
      operationId: list_programs
      parameters:
      - $ref: '#/components/parameters/cursor'
      - name: filter[status]
        in: query
        schema:
          type: string
          enum:
          - draft
          - active
          - completed
          - cancelled
      - name: filter[starts_at_gte]
        in: query
        schema:
          type: string
          format: date
      - name: filter[tag]
        in: query
        schema:
          type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProgramList'
        '403':
          $ref: '#/components/responses/FeatureNotEnabled'
      summary: List programs
  /programs/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Programs
      operationId: get_program
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Program'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Get program
  /programs/{id}/sessions:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Programs
      operationId: list_program_sessions
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ProgramSession'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: List program sessions
  /programs/{id}/registrations:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Programs
      operationId: list_program_registrations
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ProgramRegistration'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: List program registrations
    post:
      tags:
      - Programs
      - Billing
      operationId: register_for_program
      summary: Register a member into a program (with payment path)
      parameters:
      - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - profile_id
              - payment_path
              properties:
                profile_id:
                  type: string
                  format: uuid
                payment_path:
                  type: string
                  enum:
                  - charge
                  - invoice
                  - comp
                  - external
                payment_method_id:
                  type: string
                external_method:
                  type: string
                amount_dollars:
                  $ref: '#/components/schemas/Money'
                notes:
                  type: string
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProgramRegistration'
        '409':
          description: Stripe charge succeeded but DB write failed — `payment_intent_id` is in the problem `extra`.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
  /program_registrations/{id}/attendance:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Programs
      operationId: record_program_attendance
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - session_id
              - status
              properties:
                session_id:
                  type: string
                  format: uuid
                status:
                  type: string
                  enum:
                  - present
                  - absent
                  - excused
                  - late
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Record program attendance
  /jcc_offerings:
    get:
      tags:
      - Programs
      operationId: list_jcc_offerings
      parameters:
      - $ref: '#/components/parameters/cursor'
      - name: filter[category_id]
        in: query
        schema:
          type: string
      - name: filter[is_active]
        in: query
        schema:
          type: boolean
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JccOfferingList'
        '403':
          $ref: '#/components/responses/FeatureNotEnabled'
      summary: List jcc offerings
  /jcc_offerings/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
    get:
      tags:
      - Programs
      operationId: get_jcc_offering
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JccOffering'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Get jcc offering
  /jcc_offering_categories:
    get:
      tags:
      - Programs
      operationId: list_jcc_offering_categories
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/JccOfferingCategory'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: List jcc offering categories
  /invoices:
    get:
      tags:
      - Billing
      operationId: list_invoices
      parameters:
      - $ref: '#/components/parameters/cursor'
      - $ref: '#/components/parameters/limit'
      - name: filter[status]
        in: query
        schema:
          type: string
          enum:
          - open
          - overdue
          - paid
          - forgiven
          - failed
      - name: filter[profile_id]
        in: query
        schema:
          type: string
          format: uuid
      - name: filter[date_opened_gte]
        in: query
        schema:
          type: string
          format: date-time
      - name: filter[date_opened_lte]
        in: query
        schema:
          type: string
          format: date-time
      - name: filter[auto_charge_date]
        in: query
        schema:
          type: string
          format: date-time
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InvoiceList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: List invoices
    post:
      tags:
      - Billing
      operationId: create_invoice
      parameters:
      - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - items
              properties:
                profile_id:
                  type: string
                  format: uuid
                guest_first_name:
                  type: string
                guest_last_name:
                  type: string
                guest_email:
                  type: string
                  format: email
                items:
                  type: array
                  items:
                    type: object
                    required:
                    - name
                    - quantity
                    - price
                    properties:
                      name:
                        type: string
                      description:
                        type: string
                      quantity:
                        type: integer
                      price:
                        $ref: '#/components/schemas/Money'
                      tax_rate:
                        $ref: '#/components/schemas/Money'
                      discount:
                        $ref: '#/components/schemas/Money'
                      product_id:
                        type: string
                        format: uuid
                auto_charge_date:
                  type: string
                  format: date-time
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Invoice'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Create invoice
  /invoices/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Billing
      operationId: get_invoice
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Invoice'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Get invoice
    patch:
      tags:
      - Billing
      operationId: update_invoice
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Invoice'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Invoice'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Update invoice
  /invoices/{id}/pay:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Billing
      operationId: pay_invoice
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - method
              properties:
                method:
                  type: string
                  enum:
                  - card
                  - ach
                  - cash
                  - check
                payment_method_id:
                  type: string
                amount:
                  $ref: '#/components/schemas/Money'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Invoice'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Pay invoice
  /invoices/{id}/refund:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Billing
      operationId: refund_invoice
      parameters:
      - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - refunds
              properties:
                refunds:
                  type: array
                  items:
                    type: object
                    required:
                    - payment_id
                    - amount
                    properties:
                      payment_id:
                        type: string
                        format: uuid
                      amount:
                        $ref: '#/components/schemas/Money'
                      reason:
                        type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Invoice'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Refund invoice
  /invoices/{id}/void:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Billing
      operationId: void_invoice
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Invoice'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Void invoice
  /payment_methods:
    get:
      tags:
      - Billing
      operationId: list_payment_methods
      parameters:
      - name: profile_id
        in: query
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  methods:
                    type: array
                    items:
                      $ref: '#/components/schemas/PaymentMethod'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: List payment methods
  /payment_methods/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
    delete:
      tags:
      - Billing
      operationId: detach_payment_method
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Detach payment method
  /payment_methods/{id}/set_default:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
    post:
      tags:
      - Billing
      operationId: set_default_payment_method
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Set default payment method
  /payment_setup_intents:
    post:
      tags:
      - Billing
      operationId: create_payment_setup_intent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - profile_id
              properties:
                profile_id:
                  type: string
                  format: uuid
                email:
                  type: string
                  format: email
                name:
                  type: string
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                type: object
                required:
                - client_secret
                properties:
                  client_secret:
                    type: string
                  customer_id:
                    type: string
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Create payment setup intent
  /charges:
    post:
      tags:
      - Billing
      operationId: create_charge
      parameters:
      - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - profile_id
              - amount
              - currency
              properties:
                profile_id:
                  type: string
                  format: uuid
                amount:
                  $ref: '#/components/schemas/Money'
                currency:
                  type: string
                  default: usd
                description:
                  type: string
                payment_method_id:
                  type: string
                metadata:
                  type: object
                  additionalProperties: true
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StripeCharge'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Create charge
  /messages:
    post:
      tags:
      - Messaging
      operationId: send_message
      summary: Send an email or SMS to a member; logs to communications
      parameters:
      - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendMessageRequest'
      responses:
        '201':
          description: Sent
          content:
            application/json:
              schema:
                allOf:
                - $ref: '#/components/schemas/Communication'
                - type: object
                  properties:
                    demo:
                      type: boolean
                      description: True when provider creds are not configured.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /communications:
    get:
      tags:
      - Messaging
      operationId: list_communications
      parameters:
      - $ref: '#/components/parameters/cursor'
      - name: filter[profile_id]
        in: query
        schema:
          type: string
          format: uuid
      - name: filter[channel]
        in: query
        schema:
          type: string
          enum:
          - email
          - sms
      - name: filter[status]
        in: query
        schema:
          type: string
      - name: filter[sent_at_gte]
        in: query
        schema:
          type: string
          format: date-time
      - name: filter[sent_at_lte]
        in: query
        schema:
          type: string
          format: date-time
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CommunicationList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: List communications
  /communications/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Messaging
      operationId: get_communication
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Communication'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Get communication
  /communications/{id}/events:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Messaging
      operationId: list_communication_events
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/MessageEvent'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: List communication events
  /message_templates:
    get:
      tags:
      - Messaging
      operationId: list_message_templates
      parameters:
      - name: filter[channel]
        in: query
        schema:
          type: string
          enum:
          - email
          - sms
      - name: filter[is_active]
        in: query
        schema:
          type: boolean
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageTemplateList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: List message templates
    post:
      tags:
      - Messaging
      operationId: create_message_template
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageTemplate'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageTemplate'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Create message template
  /message_templates/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Messaging
      operationId: get_message_template
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageTemplate'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Get message template
    patch:
      tags:
      - Messaging
      operationId: update_message_template
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageTemplate'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageTemplate'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Update message template
  /notifications/push:
    post:
      tags:
      - Messaging
      - Admin
      operationId: send_push
      summary: Send a push notification to members (admin)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - profile_ids
              - title
              - body
              properties:
                profile_ids:
                  type: array
                  items:
                    type: string
                    format: uuid
                title:
                  type: string
                body:
                  type: string
                data:
                  type: object
                  additionalProperties: true
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  delivered:
                    type: integer
                  failed:
                    type: integer
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /workflows:
    get:
      tags:
      - Workflows
      operationId: list_workflows
      parameters:
      - name: filter[trigger_type]
        in: query
        schema:
          type: string
      - name: filter[is_active]
        in: query
        schema:
          type: boolean
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkflowList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: List workflows
    post:
      tags:
      - Workflows
      operationId: create_workflow
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Workflow'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Workflow'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Create workflow
  /workflows/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Workflows
      operationId: get_workflow
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Workflow'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Get workflow
    patch:
      tags:
      - Workflows
      operationId: update_workflow
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Workflow'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Workflow'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Update workflow
  /workflows/{id}/activate:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Workflows
      operationId: activate_workflow
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Workflow'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Activate workflow
  /workflows/{id}/deactivate:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Workflows
      operationId: deactivate_workflow
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Workflow'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Deactivate workflow
  /workflows/{id}/runs:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Workflows
      operationId: list_workflow_runs
      parameters:
      - name: filter[status]
        in: query
        schema:
          type: string
          enum:
          - active
          - completed
          - errored
          - cancelled
      - name: filter[profile_id]
        in: query
        schema:
          type: string
          format: uuid
      - name: filter[started_at_gte]
        in: query
        schema:
          type: string
          format: date-time
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkflowRunList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: List workflow runs
    post:
      tags:
      - Workflows
      operationId: start_workflow_run
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - profile_id
              properties:
                profile_id:
                  type: string
                  format: uuid
                event_data:
                  type: object
                  additionalProperties: true
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkflowRun'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Start workflow run
  /workflow_runs/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Workflows
      operationId: get_workflow_run
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkflowRun'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Get workflow run
  /workflows/fire_event:
    post:
      tags:
      - Workflows
      - Admin
      operationId: fire_workflow_event
      summary: Fire a workflow trigger event manually (admin)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - event_type
              - profile_id
              properties:
                event_type:
                  type: string
                profile_id:
                  type: string
                  format: uuid
                event_data:
                  type: object
                  additionalProperties: true
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /workflows/cancel_active:
    post:
      tags:
      - Workflows
      operationId: cancel_active_workflows
      summary: Cancel any in-flight runs for a profile / trigger
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - profile_id
              properties:
                profile_id:
                  type: string
                  format: uuid
                trigger_type:
                  type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  cancelled:
                    type: integer
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /campaigns:
    get:
      tags:
      - Messaging
      operationId: list_campaigns
      parameters:
      - name: filter[status]
        in: query
        schema:
          type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CampaignList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: List campaigns
    post:
      tags:
      - Messaging
      operationId: create_campaign
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Campaign'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Campaign'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Create campaign
  /campaigns/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Messaging
      operationId: get_campaign
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Campaign'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Get campaign
  /campaigns/{id}/send:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Messaging
      operationId: send_campaign
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Campaign'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Send campaign
  /tasks:
    get:
      tags:
      - Tasks
      operationId: list_tasks
      parameters:
      - $ref: '#/components/parameters/cursor'
      - $ref: '#/components/parameters/limit'
      - name: filter[status]
        in: query
        schema:
          type: string
          enum:
          - open
          - done
          - dismissed
      - name: filter[assigned_to]
        in: query
        schema:
          type: string
          format: uuid
      - name: filter[profile_id]
        in: query
        schema:
          type: string
          format: uuid
      - name: filter[priority]
        in: query
        schema:
          type: string
          enum:
          - low
          - medium
          - high
      - name: filter[due_at_lte]
        in: query
        schema:
          type: string
          format: date-time
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: List tasks
    post:
      tags:
      - Tasks
      operationId: create_task
      parameters:
      - $ref: '#/components/parameters/idempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TaskCreate'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Task'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Create task
  /tasks/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Tasks
      operationId: get_task
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Task'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Get task
    patch:
      tags:
      - Tasks
      operationId: update_task
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TaskCreate'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Task'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Update task
    delete:
      tags:
      - Tasks
      operationId: delete_task
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Delete task
  /tasks/{id}/complete:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Tasks
      operationId: complete_task
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Task'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Complete task
  /tasks/{id}/dismiss:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Tasks
      operationId: dismiss_task
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Task'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Dismiss task
  /notes:
    get:
      tags:
      - Notes
      operationId: list_notes
      parameters:
      - $ref: '#/components/parameters/cursor'
      - name: filter[profile_id]
        in: query
        schema:
          type: string
          format: uuid
      - name: filter[remind_on_lte]
        in: query
        schema:
          type: string
          format: date
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NoteList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: List notes
  /notes/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Notes
      operationId: get_note
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Note'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Get note
    patch:
      tags:
      - Notes
      operationId: update_note
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NoteCreate'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Note'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Update note
    delete:
      tags:
      - Notes
      operationId: delete_note
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Delete note
  /documents:
    get:
      tags:
      - Documents
      operationId: list_documents
      parameters:
      - name: filter[status]
        in: query
        schema:
          type: string
          enum:
          - draft
          - published
          - archived
      - name: filter[attach_to]
        in: query
        schema:
          type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DocumentList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: List documents
    post:
      tags:
      - Documents
      operationId: create_document
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Document'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Document'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Create document
  /documents/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Documents
      operationId: get_document
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Document'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Get document
  /documents/{id}/fan_out:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Documents
      - Admin
      operationId: fan_out_document
      summary: Generate user_documents for all matching profiles
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  created:
                    type: integer
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /documents/{id}/resync:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Documents
      - Admin
      operationId: resync_document
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  updated:
                    type: integer
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Resync document
  /user_documents:
    get:
      tags:
      - Documents
      operationId: list_user_documents
      parameters:
      - name: filter[profile_id]
        in: query
        schema:
          type: string
          format: uuid
      - name: filter[status]
        in: query
        schema:
          type: string
          enum:
          - unsigned
          - signed
          - revoked
          - stale
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserDocumentList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: List user documents
  /user_documents/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Documents
      operationId: get_user_document
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserDocument'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Get user document
  /user_documents/{id}/file:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Documents
      operationId: get_user_document_file
      summary: Get a signed URL to the signed PDF (10-min TTL)
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserDocumentFile'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /user_documents/{id}/sign:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Documents
      operationId: sign_user_document
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - signed_by_name
              - signature_data
              properties:
                signed_by_name:
                  type: string
                signed_via:
                  type: string
                  default: typed
                signature_data:
                  type: string
                  description: data:image/png;base64,... or typed text
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserDocument'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Sign user document
  /tour_bookings:
    get:
      tags:
      - Leads
      operationId: list_tour_bookings
      parameters:
      - name: filter[status]
        in: query
        schema:
          type: string
          enum:
          - scheduled
          - completed
          - no_show
          - cancelled
      - name: filter[tour_date_gte]
        in: query
        schema:
          type: string
          format: date-time
      - name: filter[tour_date_lte]
        in: query
        schema:
          type: string
          format: date-time
      - name: filter[tour_guide_id]
        in: query
        schema:
          type: string
          format: uuid
      - name: filter[prospect_profile_id]
        in: query
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TourBookingList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: List tour bookings
    post:
      tags:
      - Leads
      operationId: create_tour_booking
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - prospect_profile_id
              - tour_date
              properties:
                prospect_profile_id:
                  type: string
                  format: uuid
                tour_date:
                  type: string
                  format: date-time
                tour_guide_id:
                  type: string
                  format: uuid
                notes:
                  type: string
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TourBooking'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Create tour booking
  /tour_bookings/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    patch:
      tags:
      - Leads
      operationId: update_tour_booking
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                status:
                  type: string
                  enum:
                  - scheduled
                  - completed
                  - no_show
                  - cancelled
                tour_guide_id:
                  type: string
                  format: uuid
                notes:
                  type: string
                completed_at:
                  type: string
                  format: date-time
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TourBooking'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Update tour booking
  /support/tickets:
    get:
      tags:
      - Support
      operationId: list_support_tickets
      parameters:
      - name: filter[status]
        in: query
        schema:
          type: string
          enum:
          - open
          - in_progress
          - resolved
          - closed
      - name: filter[assigned_to]
        in: query
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SupportTicketList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: List support tickets
    post:
      tags:
      - Support
      operationId: create_support_ticket
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - profile_id
              - category
              - subject
              - body
              properties:
                profile_id:
                  type: string
                  format: uuid
                category:
                  type: string
                subject:
                  type: string
                body:
                  type: string
                priority:
                  type: string
                  enum:
                  - low
                  - normal
                  - high
                context:
                  type: object
                  additionalProperties: true
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SupportTicket'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Create support ticket
  /support/tickets/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Support
      operationId: get_support_ticket
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                allOf:
                - $ref: '#/components/schemas/SupportTicket'
                - type: object
                  properties:
                    messages:
                      type: array
                      items:
                        $ref: '#/components/schemas/SupportTicketMessage'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Get support ticket
    patch:
      tags:
      - Support
      operationId: update_support_ticket
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                status:
                  type: string
                  enum:
                  - open
                  - in_progress
                  - resolved
                  - closed
                priority:
                  type: string
                  enum:
                  - low
                  - normal
                  - high
                category:
                  type: string
                assigned_to:
                  type: string
                  format: uuid
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SupportTicket'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Update support ticket
  /support/tickets/{id}/messages:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Support
      operationId: append_support_ticket_message
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - body
              properties:
                body:
                  type: string
                from_staff:
                  type: boolean
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SupportTicketMessage'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Append support ticket message
  /reports/revenue:
    get:
      tags:
      - Reports
      operationId: report_revenue
      parameters:
      - name: since
        in: query
        required: true
        schema:
          type: string
          format: date
      - name: until
        in: query
        required: true
        schema:
          type: string
          format: date
      - name: group_by
        in: query
        schema:
          type: string
          enum:
          - day
          - week
          - month
          default: day
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RevenueReport'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Report revenue
  /reports/engagement:
    get:
      tags:
      - Reports
      operationId: report_engagement
      parameters:
      - name: since
        in: query
        required: true
        schema:
          type: string
          format: date
      - name: until
        in: query
        required: true
        schema:
          type: string
          format: date
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EngagementReport'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Report engagement
  /reports/at_risk:
    get:
      tags:
      - Reports
      operationId: report_at_risk
      parameters:
      - name: since_days
        in: query
        schema:
          type: integer
          default: 30
          minimum: 7
          maximum: 90
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Member'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Report at risk
  /reports/celebrations:
    get:
      tags:
      - Reports
      operationId: report_celebrations
      parameters:
      - name: date
        in: query
        schema:
          type: string
          format: date
        description: Defaults to today (tenant TZ).
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Celebrations'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Report celebrations
  /reports/personal_records:
    get:
      tags:
      - Reports
      - Workouts
      operationId: report_personal_records
      parameters:
      - name: since
        in: query
        schema:
          type: string
          format: date
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PersonalRecord'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Report personal records
  /reports/communications:
    get:
      tags:
      - Reports
      - Messaging
      operationId: report_communications
      parameters:
      - name: days
        in: query
        schema:
          type: integer
          default: 30
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Report communications
  /reports/communication_failures:
    get:
      tags:
      - Reports
      - Messaging
      operationId: report_communication_failures
      parameters:
      - name: since_days
        in: query
        schema:
          type: integer
          default: 7
      - name: limit
        in: query
        schema:
          type: integer
          default: 50
          maximum: 500
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Report communication failures
  /reports/attendance:
    get:
      tags:
      - Reports
      operationId: report_attendance
      parameters:
      - name: since
        in: query
        required: true
        schema:
          type: string
          format: date
      - name: until
        in: query
        required: true
        schema:
          type: string
          format: date
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AttendanceReport'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Report attendance
  /reports/leads_funnel:
    get:
      tags:
      - Reports
      - Leads
      operationId: report_leads_funnel
      parameters:
      - name: since
        in: query
        schema:
          type: string
          format: date
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FunnelReport'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Report leads funnel
  /tags:
    get:
      tags:
      - Members
      operationId: list_tag_library
      summary: Distinct tags used across the tenant
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  type: string
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
  /webhooks:
    get:
      tags:
      - Webhooks
      operationId: list_webhook_subscriptions
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookSubscriptionList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: List webhook subscriptions
    post:
      tags:
      - Webhooks
      operationId: create_webhook_subscription
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - url
              - events
              properties:
                url:
                  type: string
                  format: uri
                events:
                  type: array
                  items:
                    type: string
                  minItems: 1
                description:
                  type: string
      responses:
        '201':
          description: Created. `signing_secret` is shown ONLY here.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookSubscriptionCreated'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Create webhook subscription
  /webhooks/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - Webhooks
      operationId: get_webhook_subscription
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookSubscription'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Get webhook subscription
    patch:
      tags:
      - Webhooks
      operationId: update_webhook_subscription
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                url:
                  type: string
                  format: uri
                events:
                  type: array
                  items:
                    type: string
                active:
                  type: boolean
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookSubscription'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Update webhook subscription
    delete:
      tags:
      - Webhooks
      operationId: delete_webhook_subscription
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Delete webhook subscription
  /webhooks/{id}/rotate_secret:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Webhooks
      operationId: rotate_webhook_secret
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                required:
                - signing_secret
                properties:
                  signing_secret:
                    type: string
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Rotate webhook secret
  /webhooks/{id}/deliveries:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    - name: filter[status]
      in: query
      schema:
        type: string
        enum:
        - pending
        - delivered
        - failed
        - exhausted
    get:
      tags:
      - Webhooks
      operationId: list_webhook_deliveries
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookDeliveryList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: List webhook deliveries
  /webhooks/{id}/deliveries/{delivery_id}/retry:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    - name: delivery_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Webhooks
      operationId: retry_webhook_delivery
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookDelivery'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      summary: Retry webhook delivery
  /public/stats:
    get:
      tags:
      - Public
      operationId: get_public_stats
      security: []
      summary: Aggregated platform stats for marketing pages
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BlogStats'
        '422':
          $ref: '#/components/responses/Validation'
  /public/marketing-leads:
    post:
      tags:
      - Public
      operationId: create_marketing_lead
      security: []
      summary: Public marketing-lead intake (Vercel BotID gated; cross-tenant)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MarketingLeadCreate'
      responses:
        '202':
          description: Accepted
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                    const: true
        '403':
          $ref: '#/components/responses/Forbidden'
  /public/{gym_slug}/schedule:
    parameters:
    - name: gym_slug
      in: path
      required: true
      schema:
        type: string
    - name: date
      in: query
      required: true
      schema:
        type: string
        format: date
    get:
      tags:
      - Public
      operationId: get_public_schedule
      security: []
      summary: Tenant's public class schedule for a date
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PublicScheduleRow'
        '422':
          $ref: '#/components/responses/Validation'
  /webhook_events:
    get:
      tags:
      - Webhooks
      operationId: list_webhook_events
      summary: Discover every webhook event type Rellavo can emit
      description: |
        Returns the full event taxonomy with example payloads. Sourced from
        the `public.webhook_event_catalog` table — the same source the
        marketing site and dashboard read. Any token can call this.
      responses:
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/WebhookEventCatalogEntry'
                  count:
                    type: integer
                  by_resource:
                    type: object
                    additionalProperties:
                      type: array
                      items:
                        $ref: '#/components/schemas/WebhookEventCatalogEntry'
  /webhooks/{id}/test:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Webhooks
      operationId: test_webhook_subscription
      summary: Fire a synthetic test delivery against a subscription
      responses:
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '202':
          description: Test delivery enqueued
          content:
            application/json:
              schema:
                type: object
                properties:
                  delivery_id:
                    type: string
                    format: uuid
                  status:
                    type: string
                  event_type:
                    type: string
                    example: webhook.test
                  payload:
                    type: object
  /search:
    get:
      tags:
      - Platform
      operationId: search_all
      summary: Cross-entity search (members, leads, classes, invoices, tasks)
      parameters:
      - name: q
        in: query
        required: true
        schema: { type: string, minLength: 2 }
      - name: types
        in: query
        required: false
        schema:
          type: string
          description: Comma-separated subset
          example: members,leads
      - name: limit
        in: query
        required: false
        schema: { type: integer, minimum: 1, maximum: 25 }
      responses:
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  q: { type: string }
                  total: { type: integer }
                  results:
                    type: object
                    additionalProperties:
                      type: array
                      items:
                        $ref: '#/components/schemas/SearchHit'
  /group_class_registrations/{id}/no_show:
    parameters:
    - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
    post:
      tags: [Schedule]
      operationId: mark_class_no_show
      summary: Mark a class registration as no-show
      responses:
        '200':
          description: OK
          content: { application/json: { schema: { $ref: '#/components/schemas/GroupClassRegistration' } } }
        '404': { $ref: '#/components/responses/NotFound' }
  /group_class_registrations/{id}/late_cancel:
    parameters:
    - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
    post:
      tags: [Schedule]
      operationId: mark_class_late_cancel
      summary: Mark a class registration as a late cancel
      responses:
        '200':
          description: OK
          content: { application/json: { schema: { $ref: '#/components/schemas/GroupClassRegistration' } } }
        '404': { $ref: '#/components/responses/NotFound' }
  /group_class_registrations/{id}/waitlist_promote:
    parameters:
    - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
    post:
      tags: [Schedule]
      operationId: promote_from_waitlist
      summary: Promote a waitlisted registration to reserved
      responses:
        '200':
          description: OK
          content: { application/json: { schema: { $ref: '#/components/schemas/GroupClassRegistration' } } }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }
  /appointments/{id}/cancel:
    parameters:
    - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
    post:
      tags: [Schedule]
      operationId: cancel_appointment_action
      summary: Cancel an appointment with an optional reason
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                reason: { type: string }
      responses:
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '200':
          description: OK
          content: { application/json: { schema: { $ref: '#/components/schemas/Appointment' } } }
  /appointments/{id}/no_show:
    parameters:
    - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
    post:
      tags: [Schedule]
      operationId: mark_appointment_no_show
      summary: Mark appointment as no-show
      responses:
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '200':
          description: OK
          content: { application/json: { schema: { $ref: '#/components/schemas/Appointment' } } }
  /appointments/{id}/reschedule:
    parameters:
    - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
    post:
      tags: [Schedule]
      operationId: reschedule_appointment
      summary: Move an appointment to a new date/time window
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [date, start_time, end_time]
              properties:
                date: { type: string, format: date }
                start_time: { type: string, example: '14:00' }
                end_time: { type: string, example: '15:00' }
                trainer_id: { type: string, format: uuid }
      responses:
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '200':
          description: OK
          content:
            application/json:
              schema:
                allOf:
                - $ref: '#/components/schemas/Appointment'
                - type: object
                  properties:
                    rescheduled_from:
                      type: object
                      properties:
                        date: { type: string, format: date }
                        start_time: { type: string }
                        end_time: { type: string }
  /appointments/availability:
    get:
      tags: [Schedule]
      operationId: get_appointment_availability
      summary: Bookable appointment slots for a trainer+type in a window
      parameters:
      - { name: trainer_id, in: query, required: true, schema: { type: string, format: uuid } }
      - { name: type_id,    in: query, required: true, schema: { type: string, format: uuid } }
      - { name: from,       in: query, required: true, schema: { type: string, format: date } }
      - { name: to,         in: query, required: true, schema: { type: string, format: date } }
      responses:
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  trainer_id: { type: string, format: uuid }
                  type_id: { type: string, format: uuid }
                  type_name: { type: string }
                  duration_minutes: { type: integer }
                  from: { type: string, format: date }
                  to: { type: string, format: date }
                  slots_by_date:
                    type: object
                    additionalProperties:
                      type: array
                      items:
                        type: object
                        properties:
                          start_time: { type: string }
                          end_time: { type: string }
  /members/bulk_tag:
    post:
      tags: [Members]
      operationId: bulk_tag_members
      summary: Add or remove tags across many members in one call
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [member_ids]
              properties:
                member_ids:
                  type: array
                  items: { type: string, format: uuid }
                  maxItems: 500
                add:
                  type: array
                  items: { type: string }
                remove:
                  type: array
                  items: { type: string }
      responses:
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  requested: { type: integer }
                  matched: { type: integer }
                  updated: { type: integer }
                  errors:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: string }
                        error: { type: string }
  /members/bulk_message:
    post:
      tags: [Members, Messaging]
      operationId: bulk_message_members
      summary: Queue the same email or SMS to multiple members
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [member_ids, channel]
              properties:
                member_ids:
                  type: array
                  items: { type: string, format: uuid }
                  maxItems: 500
                channel: { type: string, enum: [email, sms] }
                subject: { type: string }
                body:    { type: string }
                template_id: { type: string, format: uuid }
      responses:
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '202':
          description: Accepted
          content:
            application/json:
              schema:
                type: object
                properties:
                  requested: { type: integer }
                  queued:    { type: integer }
                  channel:   { type: string }
  /members/bulk_assign:
    post:
      tags: [Members]
      operationId: bulk_assign_members
      summary: Reassign multiple members to a single trainer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [member_ids]
              properties:
                member_ids:
                  type: array
                  items: { type: string, format: uuid }
                  maxItems: 500
                assigned_to:
                  type: [string, 'null']
                  format: uuid
      responses:
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  requested: { type: integer }
                  updated:   { type: integer }
                  assigned_to: { type: [string, 'null'], format: uuid }
  /exports/members.csv:
    get:
      tags: [Members]
      operationId: export_members_csv
      summary: Stream all members as CSV (no row cap)
      responses:
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '200':
          description: CSV stream
          content:
            text/csv:
              schema: { type: string, format: binary }
  /exports/invoices.csv:
    get:
      tags: [Billing]
      operationId: export_invoices_csv
      summary: Stream invoices as CSV, optionally bounded by date
      parameters:
      - { name: since, in: query, required: false, schema: { type: string, format: date } }
      - { name: until, in: query, required: false, schema: { type: string, format: date } }
      responses:
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '200':
          description: CSV stream
          content:
            text/csv:
              schema: { type: string, format: binary }
  /exports/communications.csv:
    get:
      tags: [Messaging]
      operationId: export_communications_csv
      summary: Stream the communications log as CSV
      parameters:
      - { name: since,   in: query, required: false, schema: { type: string, format: date } }
      - { name: until,   in: query, required: false, schema: { type: string, format: date } }
      - { name: channel, in: query, required: false, schema: { type: string, enum: [email, sms] } }
      responses:
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '200':
          description: CSV stream
          content:
            text/csv:
              schema: { type: string, format: binary }
  /exports/attendance.csv:
    get:
      tags: [Schedule]
      operationId: export_attendance_csv
      summary: Stream class registrations + resolved status as CSV
      parameters:
      - { name: since, in: query, required: false, schema: { type: string, format: date } }
      - { name: until, in: query, required: false, schema: { type: string, format: date } }
      responses:
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '200':
          description: CSV stream
          content:
            text/csv:
              schema: { type: string, format: binary }
  /workouts:
    get:
      tags: [Workouts]
      operationId: list_workouts
      summary: List programmed workouts (feature-gated to workouts tenants)
      parameters:
      - { name: athlete_id,     in: query, required: false, schema: { type: string, format: uuid } }
      - { name: date,           in: query, required: false, schema: { type: string, format: date } }
      - { name: date_range_end, in: query, required: false, schema: { type: string, format: date } }
      - { name: limit,          in: query, required: false, schema: { type: integer, minimum: 1, maximum: 200 } }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: array, items: { $ref: '#/components/schemas/Workout' } }
                  next_cursor: { type: [string, 'null'] }
        '403': { $ref: '#/components/responses/Forbidden' }
    post:
      tags: [Workouts]
      operationId: create_workout
      summary: Create a workout for an athlete (feature-gated)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [athlete_id, date]
              properties:
                athlete_id: { type: string, format: uuid }
                date:       { type: string, format: date }
                title:      { type: string }
                raw_text:   { type: string }
                status:     { type: string }
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Workout' }
        '403': { $ref: '#/components/responses/Forbidden' }
  /workouts/{id}:
    parameters:
    - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
    get:
      tags: [Workouts]
      operationId: get_workout
      summary: Fetch a workout with sections + exercises
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/WorkoutDetail' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
    patch:
      tags: [Workouts]
      operationId: update_workout
      summary: Update workout-level fields
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                title:    { type: string }
                raw_text: { type: string }
                status:   { type: string }
                date:     { type: string, format: date }
      responses:
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Workout' }
    delete:
      tags: [Workouts]
      operationId: delete_workout
      summary: Delete a workout (sections + exercises cascade)
      responses:
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '204': { description: Deleted }
  /personal_records:
    get:
      tags: [Workouts]
      operationId: list_personal_records_top
      summary: Personal records across the tenant (feature-gated)
      parameters:
      - { name: athlete_id,  in: query, required: false, schema: { type: string, format: uuid } }
      - { name: since,       in: query, required: false, schema: { type: string, format: date } }
      - { name: until,       in: query, required: false, schema: { type: string, format: date } }
      - { name: exercise_id, in: query, required: false, schema: { type: string, format: uuid } }
      - { name: pr_type,     in: query, required: false, schema: { type: string } }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: array, items: { $ref: '#/components/schemas/PersonalRecord' } }
                  next_cursor: { type: [string, 'null'] }
        '403': { $ref: '#/components/responses/Forbidden' }
  /athlete_maxes:
    get:
      tags: [Workouts]
      operationId: list_athlete_maxes
      summary: Current 1RM / pace records by athlete + exercise (feature-gated)
      parameters:
      - { name: athlete_id,  in: query, required: false, schema: { type: string, format: uuid } }
      - { name: exercise_id, in: query, required: false, schema: { type: string, format: uuid } }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: string, format: uuid }
                        athlete_id: { type: string, format: uuid }
                        exercise_id: { type: string, format: uuid }
                        max_type: { type: string }
                        weight: { type: number }
                        recorded_at: { type: string, format: date-time }
        '403': { $ref: '#/components/responses/Forbidden' }
  /public/{gym_slug}/availability:
    parameters:
    - { name: gym_slug, in: path, required: true, schema: { type: string } }
    get:
      tags: [Public]
      operationId: get_public_availability
      security: []
      summary: Unauthenticated availability lookup for the embeddable booking widget
      parameters:
      - { name: from, in: query, required: true, schema: { type: string, format: date } }
      - { name: to,   in: query, required: true, schema: { type: string, format: date } }
      - { name: trainer_id, in: query, required: false, schema: { type: string, format: uuid } }
      - { name: type_id,    in: query, required: false, schema: { type: string, format: uuid } }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  gym:
                    type: object
                    properties:
                      slug: { type: string }
                      display_name: { type: string }
                  slots:
                    type: array
                    items:
                      type: object
                      properties:
                        trainer_id: { type: string, format: uuid }
                        trainer_name: { type: string }
                        type_id: { type: string, format: uuid }
                        type_name: { type: string }
                        date: { type: string, format: date }
                        start_time: { type: string }
                        end_time: { type: string }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
  /public/{gym_slug}/book_appointment:
    parameters:
    - { name: gym_slug, in: path, required: true, schema: { type: string } }
    post:
      tags: [Public]
      operationId: public_book_appointment
      security: []
      summary: Unauthenticated booking — used by the embeddable widget
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [trainer_id, type_id, date, start_time, first_name]
              properties:
                trainer_id: { type: string, format: uuid }
                type_id:    { type: string, format: uuid }
                date:       { type: string, format: date }
                start_time: { type: string, example: '14:00' }
                first_name: { type: string }
                last_name:  { type: string }
                email:      { type: string, format: email }
                phone:      { type: string }
                notes:      { type: string }
      responses:
        '201':
          description: Booked
          content:
            application/json:
              schema:
                type: object
                properties:
                  appointment: { $ref: '#/components/schemas/Appointment' }
                  profile_id:  { type: string, format: uuid }
                  gym:
                    type: object
                    properties:
                      slug: { type: string }
                      display_name: { type: string }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
        '422': { $ref: '#/components/responses/Validation' }
        '429':
          description: Rate limited
  /sql/select:
    post:
      tags:
      - Admin
      operationId: sql_select
      summary: Run a SELECT against the tenant's data (admin + allow_sql_admin only)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SqlSelectRequest'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SqlSelectResponse'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/Validation'
