openapi: 3.0.3
info:
  title: RPM PRO REST API
  description: |
    RPM PRO property management REST API (WiseLoop wlRestService).

    **Base path:** `/API/{controller}/V1/{action}.json`

    Most endpoints are public. Admin auth (`/API/auth/V1/`) establishes a session cookie required for `admin*` controllers (properties, invoices, tenants, reservations).
    See APIDocs/admin-api.php for the full admin API guide.

    Version is synchronized with `versions.php`.
  version: "5.9.2"
  contact:
    name: RPM PRO
    url: https://www.rpm-pro.com

servers:
  - url: "{baseUrl}/API"
    description: RPM PRO installation
    variables:
      baseUrl:
        default: https://your-domain.com
        description: Site root URL (no trailing slash)

tags:
  - name: Site Settings
  - name: Site Content
  - name: Site Pages
  - name: Site Reviews
  - name: Site Sliders
  - name: Site Navigation
  - name: Site FAQ
  - name: Site Events
  - name: Site Attractions
  - name: Site Contact
  - name: VR Properties
  - name: LTR Properties
  - name: Blog
  - name: Tenant Auth
  - name: Booking
  - name: Lease Applications
  - name: Admin Auth
  - name: Admin Properties
  - name: Admin Invoices
  - name: Admin Tenants
  - name: Admin Reservations

paths:
  /siteSettings/V1/settings.json:
    get:
      tags: [Site Settings]
      summary: Get public site settings
      operationId: siteSettingsGet
      responses:
        "200":
          description: Site settings object
          content:
            application/json:
              schema:
                type: object

  /siteContent/V1/content.json:
    get:
      tags: [Site Content]
      summary: List all CMS content blocks
      operationId: siteContentList
      responses:
        "200":
          description: Content blocks

  /siteContent/V1/content/{key}.json:
    get:
      tags: [Site Content]
      summary: Get content block by key
      operationId: siteContentGet
      parameters:
        - name: key
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Content block

  /sitePages/V1/pages.json:
    get:
      tags: [Site Pages]
      summary: List static pages
      operationId: sitePagesList
      responses:
        "200":
          description: "{ items: Page[] }"

  /sitePages/V1/pages/{slug}.json:
    get:
      tags: [Site Pages]
      summary: Get page by slug
      operationId: sitePageGet
      parameters:
        - name: slug
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Page object
        "404":
          description: Page not found

  /siteReviews/V1/reviews.json:
    get:
      tags: [Site Reviews]
      summary: List property reviews
      operationId: siteReviewsList
      parameters:
        - name: propertyId
          in: query
          schema:
            type: integer
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
      responses:
        "200":
          description: "{ items: Review[] }"

  /siteSliders/V1/sliders.json:
    get:
      tags: [Site Sliders]
      summary: List homepage sliders
      operationId: siteSlidersList
      responses:
        "200":
          description: "{ items: Slider[] }"

  /siteNavigation/V1/menus.json:
    get:
      tags: [Site Navigation]
      summary: Get navigation menus
      operationId: siteNavigationGet
      responses:
        "200":
          description: Navigation structure

  /siteFaq/V1/faqs.json:
    get:
      tags: [Site FAQ]
      summary: List FAQs
      operationId: siteFaqList
      responses:
        "200":
          description: "{ items: Faq[] }"

  /siteEvents/V1/events.json:
    get:
      tags: [Site Events]
      summary: List events
      operationId: siteEventsList
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
      responses:
        "200":
          description: Events list

  /siteEvents/V1/events/{id}.json:
    get:
      tags: [Site Events]
      summary: Get event by ID
      operationId: siteEventGet
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Event object
        "404":
          description: Not found

  /siteAttractions/V1/attractions.json:
    get:
      tags: [Site Attractions]
      summary: List attractions
      operationId: siteAttractionsList
      parameters:
        - name: category
          in: query
          schema:
            type: integer
        - name: categoryId
          in: query
          schema:
            type: integer
        - name: limit
          in: query
          schema:
            type: integer
            default: 100
      responses:
        "200":
          description: Attractions list

  /siteAttractions/V1/attractions/{id}.json:
    get:
      tags: [Site Attractions]
      summary: Get attraction by ID
      operationId: siteAttractionGet
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Attraction object
        "404":
          description: Not found

  /siteAttractions/V1/categories.json:
    get:
      tags: [Site Attractions]
      summary: List attraction categories
      operationId: siteAttractionCategories
      responses:
        "200":
          description: Categories

  /siteContact/V1/message.json:
    post:
      tags: [Site Contact]
      summary: Submit contact form
      operationId: siteContactMessage
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                email:
                  type: string
                message:
                  type: string
      responses:
        "200":
          description: Success
        "400":
          description: Validation error

  /siteContact/V1/subscribe.json:
    post:
      tags: [Site Contact]
      summary: Newsletter subscribe
      operationId: siteContactSubscribe
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                email:
                  type: string
      responses:
        "200":
          description: Success
        "400":
          description: Validation error

  /vrProperties/V1/vrProperties.json:
    get:
      tags: [VR Properties]
      summary: List VR properties
      operationId: vrPropertiesList
      responses:
        "200":
          description: Property list

  /vrProperties/V1/vrProperties/{id}.json:
    get:
      tags: [VR Properties]
      summary: Get VR property by ID
      operationId: vrPropertyGet
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Property object
        "404":
          description: Not found

  /vrProperties/V1/vrPropertyImages.json:
    get:
      tags: [VR Properties]
      summary: List all VR property images
      operationId: vrPropertyImagesList
      responses:
        "200":
          description: Images

  /vrProperties/V1/vrPropertyImages/{propertyId}.json:
    get:
      tags: [VR Properties]
      summary: Images for a VR property
      operationId: vrPropertyImagesGet
      parameters:
        - name: propertyId
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Images for property

  /ltrProperties/V1/ltrProperties.json:
    get:
      tags: [LTR Properties]
      summary: List LTR properties
      operationId: ltrPropertiesList
      responses:
        "200":
          description: Property list

  /ltrProperties/V1/ltrProperties/{id}.json:
    get:
      tags: [LTR Properties]
      summary: Get LTR property by ID
      operationId: ltrPropertyGet
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Property object
        "404":
          description: Not found

  /ltrProperties/V1/ltrPropertyImages.json:
    get:
      tags: [LTR Properties]
      summary: List all LTR property images
      operationId: ltrPropertyImagesList
      responses:
        "200":
          description: Images

  /ltrProperties/V1/ltrPropertyImages/{propertyId}.json:
    get:
      tags: [LTR Properties]
      summary: Images for an LTR property
      operationId: ltrPropertyImagesGet
      parameters:
        - name: propertyId
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Images

  /networkBlog/V1/networkBlogPosts.json:
    get:
      tags: [Blog]
      summary: List blog posts
      operationId: blogPostsList
      responses:
        "200":
          description: Posts list

  /networkBlog/V1/networkBlogPosts/{id}.json:
    get:
      tags: [Blog]
      summary: Get blog post by ID
      operationId: blogPostGet
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Post object
        "404":
          description: Not found

  /networkBlog/V1/networkBlogByCat/{categoryId}.json:
    get:
      tags: [Blog]
      summary: Posts by category
      operationId: blogPostsByCategory
      parameters:
        - name: categoryId
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Posts list

  /networkBlog/V1/networkBlogCategory.json:
    get:
      tags: [Blog]
      summary: List blog categories
      operationId: blogCategoriesList
      responses:
        "200":
          description: Categories

  /networkBlog/V1/networkBlogCategory/{id}.json:
    get:
      tags: [Blog]
      summary: Get blog category by ID
      operationId: blogCategoryGet
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Category object
        "404":
          description: Not found

  /publicAuth/V1/login.json:
    post:
      tags: [Tenant Auth]
      summary: Tenant login
      operationId: tenantLogin
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TenantLoginRequest"
      responses:
        "200":
          description: "{ success, user }"
        "400":
          description: Missing fields
        "401":
          description: Invalid credentials
        "403":
          description: Account inactive

  /publicAuth/V1/logout.json:
    post:
      tags: [Tenant Auth]
      summary: Tenant logout
      operationId: tenantLogout
      responses:
        "200":
          description: "{ success: true }"

  /publicAuth/V1/me.json:
    get:
      tags: [Tenant Auth]
      summary: Current tenant session
      operationId: tenantMe
      responses:
        "200":
          description: Tenant session data
        "401":
          description: Not authenticated

  /publicAuth/V1/register.json:
    post:
      tags: [Tenant Auth]
      summary: Register new tenant
      operationId: tenantRegister
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TenantRegisterRequest"
      responses:
        "200":
          description: "{ success, activateUrl }"
        "400":
          description: Validation error
        "409":
          description: Email already registered

  /publicAuth/V1/activate.json:
    get:
      tags: [Tenant Auth]
      summary: Activate tenant account
      operationId: tenantActivate
      parameters:
        - name: tenantEmail
          in: query
          required: true
          schema:
            type: string
        - name: hash
          in: query
          required: true
          schema:
            type: string
        - name: applyToLeaseId
          in: query
          schema:
            type: integer
      responses:
        "200":
          description: "{ success, user, applyToLeaseId }"
        "400":
          description: Invalid or expired link
        "429":
          description: Rate limited

  /publicBooking/V1/availability/{propertyId}.json:
    get:
      tags: [Booking]
      summary: Check availability for date range
      operationId: bookingAvailability
      parameters:
        - name: propertyId
          in: path
          required: true
          schema:
            type: integer
        - name: dateFrom
          in: query
          required: true
          schema:
            type: string
            format: date
        - name: dateTo
          in: query
          required: true
          schema:
            type: string
            format: date
      responses:
        "200":
          description: "{ available, message, price?, nightRates?, spots? }"
        "400":
          description: Missing parameters

  /publicBooking/V1/calendar/{propertyId}.json:
    get:
      tags: [Booking]
      summary: Calendar with pricing and blocked ranges
      operationId: bookingCalendar
      parameters:
        - name: propertyId
          in: path
          required: true
          schema:
            type: integer
        - name: months
          in: query
          schema:
            type: integer
            default: 12
            maximum: 24
      responses:
        "200":
          description: Calendar data with ranges and dayPrices
        "400":
          description: Invalid propertyId

  /publicBooking/V1/quote.json:
    post:
      tags: [Booking]
      summary: Calculate booking quote
      operationId: bookingQuote
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BookingQuoteRequest"
      responses:
        "200":
          description: Quote breakdown with total
        "400":
          description: Validation error

  /publicBooking/V1/validateCoupon.json:
    post:
      tags: [Booking]
      summary: Validate coupon code
      operationId: bookingValidateCoupon
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [propertyId, couponCode]
              properties:
                propertyId:
                  type: integer
                couponCode:
                  type: string
      responses:
        "200":
          description: "{ valid, type, value, message }"

  /publicBooking/V1/book.json:
    post:
      tags: [Booking]
      summary: Create reservation
      operationId: bookingCreate
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BookingCreateRequest"
      responses:
        "200":
          description: "{ success, orderId, invoiceId?, quote, message } or requiresPortal"
        "400":
          description: Validation error

  /leaseApplications/V1/apply.json:
    post:
      tags: [Lease Applications]
      summary: Submit lease application
      operationId: leaseApply
      description: Requires tenant session (tenantId in PHP session).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [propertyId]
              properties:
                propertyId:
                  type: integer
      responses:
        "200":
          description: "{ success, applicationId }"
        "401":
          description: Login required
        "400":
          description: Missing propertyId

  /auth/V1/login.json:
    post:
      tags: [Admin Auth]
      summary: Admin API login
      operationId: adminLogin
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AdminLoginRequest"
      responses:
        "200":
          description: Admin session data (adminId, adminRole, superuser, etc.)
        "401":
          description: Invalid credentials

  /auth/V1/logout.json:
    post:
      tags: [Admin Auth]
      summary: Admin API logout
      operationId: adminLogout
      responses:
        "200":
          description: Session cleared

  /auth/V1/user.json:
    get:
      tags: [Admin Auth]
      summary: Get authenticated admin user
      operationId: adminUser
      responses:
        "200":
          description: Admin session object
        "401":
          description: Unauthorized

  /auth/V1/user/{field}.json:
    get:
      tags: [Admin Auth]
      summary: Get single admin session field
      operationId: adminUserField
      parameters:
        - name: field
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Field value
        "401":
          description: Unauthorized

  /adminProperties/V1/properties.json:
    get:
      tags: [Admin Properties]
      summary: List all properties (admin)
      operationId: adminPropertiesList
      security:
        - adminSession: []
      parameters:
        - name: type
          in: query
          schema:
            type: string
            enum: [all, ltr, vr]
        - name: page
          in: query
          schema:
            type: integer
        - name: limit
          in: query
          schema:
            type: integer
        - name: includeArchived
          in: query
          schema:
            type: string
        - name: search
          in: query
          schema:
            type: string
      responses:
        "200":
          description: Paginated property list
        "401":
          description: Unauthorized
        "403":
          description: Forbidden (role)
    post:
      tags: [Admin Properties]
      summary: Create property (LTR or VR)
      operationId: adminPropertyCreate
      security:
        - adminSession: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [type, propertyName, propertyDesc, propertyRate]
              properties:
                type:
                  type: string
                  enum: [ltr, vr]
                propertyName:
                  type: string
                propertyDesc:
                  type: string
                propertyRate:
                  type: string
                latePenalty:
                  type: string
                propertyDeposit:
                  type: string
                petsAllowed:
                  type: integer
      responses:
        "200":
          description: Created property
        "400":
          description: Validation error
        "403":
          description: Forbidden (super_admin only)
        "409":
          description: Duplicate property name

  /adminProperties/V1/properties/{id}.json:
    get:
      tags: [Admin Properties]
      summary: Get property by ID (admin)
      operationId: adminPropertyGet
      security:
        - adminSession: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Property object
        "404":
          description: Not found
    put:
      tags: [Admin Properties]
      summary: Update property fields
      operationId: adminPropertyUpdate
      security:
        - adminSession: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                propertyName:
                  type: string
                propertyDesc:
                  type: string
                propertyRate:
                  type: string
                onWebsite:
                  type: integer
                isArchived:
                  type: integer
      responses:
        "200":
          description: Updated property
        "403":
          description: Forbidden (super_admin only)
        "404":
          description: Not found

  /adminInvoices/V1/invoices.json:
    get:
      tags: [Admin Invoices]
      summary: List invoices (admin)
      operationId: adminInvoicesList
      security:
        - adminSession: []
      parameters:
        - name: tenantId
          in: query
          schema:
            type: integer
        - name: propertyId
          in: query
          schema:
            type: integer
        - name: status
          in: query
          schema:
            type: integer
        - name: dateFrom
          in: query
          schema:
            type: string
            format: date
        - name: dateTo
          in: query
          schema:
            type: string
            format: date
        - name: search
          in: query
          schema:
            type: string
        - name: page
          in: query
          schema:
            type: integer
        - name: limit
          in: query
          schema:
            type: integer
      responses:
        "200":
          description: Paginated invoice list
    post:
      tags: [Admin Invoices]
      summary: Create invoice (admin)
      operationId: adminInvoiceCreate
      security:
        - adminSession: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [description, dueDate]
              properties:
                description:
                  type: string
                tenantId:
                  type: integer
                adminId:
                  type: integer
                propertyId:
                  type: integer
                dueDate:
                  type: string
                  format: date
                tax:
                  type: number
                lineItems:
                  type: array
                  items:
                    type: object
      responses:
        "200":
          description: Created invoice
        "403":
          description: Forbidden (super_admin only)

  /adminInvoices/V1/invoices/{id}.json:
    get:
      tags: [Admin Invoices]
      summary: Get invoice with line items (admin)
      operationId: adminInvoiceGet
      security:
        - adminSession: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Invoice detail
        "404":
          description: Not found
    put:
      tags: [Admin Invoices]
      summary: Update invoice (admin)
      operationId: adminInvoiceUpdate
      security:
        - adminSession: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                status:
                  type: integer
                tax:
                  type: number
                description:
                  type: string
                lineItems:
                  type: array
                  items:
                    type: object
      responses:
        "200":
          description: Updated invoice
        "403":
          description: Forbidden (super_admin only)
        "404":
          description: Not found

  /adminTenants/V1/tenants.json:
    get:
      tags: [Admin Tenants]
      summary: List tenants (admin)
      operationId: adminTenantsList
      security:
        - adminSession: []
      parameters:
        - name: search
          in: query
          schema:
            type: string
        - name: page
          in: query
          schema:
            type: integer
        - name: limit
          in: query
          schema:
            type: integer
      responses:
        "200":
          description: Paginated tenant list
    post:
      tags: [Admin Tenants]
      summary: Create tenant (admin)
      operationId: adminTenantCreate
      security:
        - adminSession: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [tenantEmail, tenantFirstName, tenantLastName, password]
              properties:
                tenantEmail:
                  type: string
                tenantFirstName:
                  type: string
                tenantLastName:
                  type: string
                password:
                  type: string
                isActive:
                  type: integer
      responses:
        "200":
          description: Created tenant
        "403":
          description: Forbidden (super_admin only)
        "409":
          description: Duplicate email

  /adminTenants/V1/tenants/{id}.json:
    get:
      tags: [Admin Tenants]
      summary: Get tenant by ID (admin)
      operationId: adminTenantGet
      security:
        - adminSession: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Tenant profile
        "404":
          description: Not found
    put:
      tags: [Admin Tenants]
      summary: Update tenant (admin)
      operationId: adminTenantUpdate
      security:
        - adminSession: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                tenantFirstName:
                  type: string
                isActive:
                  type: integer
                password:
                  type: string
      responses:
        "200":
          description: Updated tenant
        "403":
          description: Forbidden (super_admin only)
        "404":
          description: Not found

  /adminReservations/V1/reservations.json:
    get:
      tags: [Admin Reservations]
      summary: List VR reservations (admin)
      operationId: adminReservationsList
      security:
        - adminSession: []
      parameters:
        - name: propertyId
          in: query
          schema:
            type: integer
        - name: tenantId
          in: query
          schema:
            type: integer
        - name: status
          in: query
          schema:
            type: string
        - name: dateFrom
          in: query
          schema:
            type: string
            format: date
        - name: dateTo
          in: query
          schema:
            type: string
            format: date
        - name: search
          in: query
          schema:
            type: string
        - name: page
          in: query
          schema:
            type: integer
        - name: limit
          in: query
          schema:
            type: integer
      responses:
        "200":
          description: Paginated reservation list
    post:
      tags: [Admin Reservations]
      summary: Create reservation (admin)
      operationId: adminReservationCreate
      security:
        - adminSession: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [propertyId, dateFrom, dateTo, name, email]
              properties:
                propertyId:
                  type: integer
                dateFrom:
                  type: string
                  format: date
                dateTo:
                  type: string
                  format: date
                name:
                  type: string
                email:
                  type: string
                totalPrice:
                  type: number
                skipAvailabilityCheck:
                  type: boolean
      responses:
        "200":
          description: Created reservation
        "403":
          description: Forbidden (super_admin only)

  /adminReservations/V1/reservations/{id}.json:
    get:
      tags: [Admin Reservations]
      summary: Get reservation by ID (admin)
      operationId: adminReservationGet
      security:
        - adminSession: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Reservation detail
        "404":
          description: Not found
    put:
      tags: [Admin Reservations]
      summary: Update reservation (admin)
      operationId: adminReservationUpdate
      security:
        - adminSession: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                status:
                  type: string
                dateFrom:
                  type: string
                  format: date
                dateTo:
                  type: string
                  format: date
                totalPrice:
                  type: number
      responses:
        "200":
          description: Updated reservation
        "403":
          description: Forbidden (super_admin only)
        "404":
          description: Not found
    delete:
      tags: [Admin Reservations]
      summary: Cancel reservation (admin)
      operationId: adminReservationDelete
      security:
        - adminSession: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Cancelled reservation
        "403":
          description: Forbidden (super_admin only)
        "409":
          description: Already cancelled

  /adminExpenses/V1/expenses.json:
    get:
      tags: [Admin Expenses]
      summary: List expenses (admin)
      operationId: adminExpensesList
      security:
        - adminSession: []
      responses:
        "200":
          description: Paginated expense list
    post:
      tags: [Admin Expenses]
      summary: Create expense (admin)
      operationId: adminExpenseCreate
      security:
        - adminSession: []
      responses:
        "200":
          description: Created expense
        "403":
          description: Forbidden (super_admin only)

  /adminExpenses/V1/expenses/{id}.json:
    get:
      tags: [Admin Expenses]
      summary: Get expense by ID (admin)
      operationId: adminExpenseGet
      security:
        - adminSession: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Expense detail
    put:
      tags: [Admin Expenses]
      summary: Update expense (admin)
      operationId: adminExpenseUpdate
      security:
        - adminSession: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Updated expense
    delete:
      tags: [Admin Expenses]
      summary: Cancel expense (admin)
      operationId: adminExpenseDelete
      security:
        - adminSession: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Cancelled expense
        "409":
          description: Settled payments block cancel

  /adminHousekeeping/V1/requests.json:
    get:
      tags: [Admin Housekeeping]
      summary: List housekeeping requests (admin)
      operationId: adminHousekeepingList
      security:
        - adminSession: []
      responses:
        "200":
          description: Paginated request list
    post:
      tags: [Admin Housekeeping]
      summary: Create housekeeping request (admin)
      operationId: adminHousekeepingCreate
      security:
        - adminSession: []
      responses:
        "200":
          description: Created request

  /adminHousekeeping/V1/requests/{id}.json:
    get:
      tags: [Admin Housekeeping]
      summary: Get housekeeping request (admin)
      operationId: adminHousekeepingGet
      security:
        - adminSession: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Request detail
    delete:
      tags: [Admin Housekeeping]
      summary: Archive housekeeping request (admin)
      operationId: adminHousekeepingDelete
      security:
        - adminSession: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Archived request

  /adminReports/V1/income.json:
    get:
      tags: [Admin Reports]
      summary: Income summary report (admin)
      operationId: adminReportIncome
      security:
        - adminSession: []
      parameters:
        - name: dateFrom
          in: query
          required: true
          schema:
            type: string
            format: date
        - name: dateTo
          in: query
          required: true
          schema:
            type: string
            format: date
        - name: propertyId
          in: query
          schema:
            type: integer
      responses:
        "200":
          description: Income summary

  /adminReports/V1/occupancy.json:
    get:
      tags: [Admin Reports]
      summary: VR occupancy summary (admin)
      operationId: adminReportOccupancy
      security:
        - adminSession: []
      parameters:
        - name: dateFrom
          in: query
          required: true
          schema:
            type: string
            format: date
        - name: dateTo
          in: query
          required: true
          schema:
            type: string
            format: date
        - name: propertyId
          in: query
          schema:
            type: integer
      responses:
        "200":
          description: Occupancy summary

components:
  schemas:
    TenantLoginRequest:
      type: object
      required: [email, password]
      properties:
        email:
          type: string
        tenantEmail:
          type: string
        password:
          type: string
          format: password

    TenantRegisterRequest:
      type: object
      required: [tenantFirstName, tenantLastName, tenantEmail, password]
      properties:
        tenantFirstName:
          type: string
        tenantLastName:
          type: string
        tenantEmail:
          type: string
        password:
          type: string
        password1:
          type: string
        password2:
          type: string
        tenantAddress:
          type: string
        tenantCity:
          type: string
        tenantState:
          type: string
        tenantZip:
          type: string
        tenantCountry:
          type: string
        tenantPhone:
          type: string
        applyToLeaseId:
          type: integer

    BookingQuoteRequest:
      type: object
      required: [propertyId, dateFrom, dateTo]
      properties:
        propertyId:
          type: integer
        serviceID:
          type: integer
          description: Alias for propertyId
        dateFrom:
          type: string
          format: date
        dateTo:
          type: string
          format: date
        adults:
          type: integer
          default: 1
        children:
          type: integer
          default: 0
        pets:
          type: integer
          default: 0
        couponCode:
          type: string

    BookingCreateRequest:
      allOf:
        - $ref: "#/components/schemas/BookingQuoteRequest"
        - type: object
          properties:
            name:
              type: string
            firstName:
              type: string
            lastName:
              type: string
            email:
              type: string
            phone:
              type: string
            address:
              type: string
            city:
              type: string
            state:
              type: string
            zip:
              type: string
            tenantId:
              type: integer
            extraRooms:
              type: array
              items:
                type: object

    AdminLoginRequest:
      type: object
      required: [adminAPIKey, adminAPISecret]
      properties:
        adminAPIKey:
          type: string
        adminAPISecret:
          type: string

  securitySchemes:
    adminSession:
      type: apiKey
      in: cookie
      name: PHPSESSID
      description: Established via POST /auth/V1/login.json (WiseLoop wlAuthData in PHP session)

security: []
