Documentation for AI Agents

Machine-readable API reference — plain Markdown, no HTML navigation required

🤖

If you are an AI agent, start here

Do not scrape the HTML reference pages. Use the canonical Markdown document instead — it contains the same endpoint catalog with all fields, examples, and auth rules in a format optimized for automated review. For admin integrations, read the Admin Authentication and Admin Properties/Invoices/Tenants/Reservations sections — login via /API/auth/V1/login.json before calling admin* endpoints.

Recommended URLs for agents

URLFormatBest for
/APIDocs/agents.php text/markdown (live) Primary — always current version from codebase
/APIDocs/agents.md Markdown file Static export, curl/wget, offline tools
/APIDocs/llms.txt Plain text index Discovery — lists all agent-readable resources
/APIDocs/openapi/rpm-pro-api.openapi.yaml OpenAPI 3.0 Code generation, Postman import
HTTP headers on agents.php: X-RPM-Docs-Version (5.9.3.3), X-RPM-Docs-Format: agents-markdown, Content-Type: text/markdown

Full reference (preview)

Below is the same content served by agents.php. Copy or fetch the raw URL for parsing.

Markdown · v5.9.3.3 Open raw ↗
# RPM-PRO REST API — Agent Reference

> **For AI agents:** This is the canonical machine-readable API documentation for RPM-PRO.
> Prefer this file over scraping HTML pages. Version **5.9.3.3**.

## Quick links

| Resource | URL |
|----------|-----|
| **This document (raw markdown)** | `/APIDocs/agents.md` or `/APIDocs/agents.php` |
| OpenAPI 3.0 spec | `/APIDocs/openapi/rpm-pro-api.openapi.yaml` |
| Postman collection | `/APIDocs/postman/RPM-PRO-API.postman_collection.json` |
| Human HTML docs | `/APIDocs/index.php` |
| Agent landing page | `/APIDocs/for-agents.php` |

## Architecture

- **Framework:** WiseLoop `wlRestService`
- **Base URL pattern:** `/API/{controller}/V1/{action}.json`
- **Default format:** JSON
- **Action mapping:** `settingsGet()` → GET, `quotePost()` → POST
- **Services:** PSR-4 classes in `src/RPM/`, accessed via `rpm_*_service()` factories
- **Rate limit:** 25 requests per 5 seconds per IP
- **Response cache:** 10 minutes in `API/cache/`

## Authentication

| Level | Mechanism | Notes |
|-------|-----------|-------|
| public | None | Most endpoints |
| session | PHP session cookie (`tenantId`) | `publicAuth/me`, `leaseApplications/apply` |
| admin | `adminAPIKey` + `adminAPISecret` via `/API/auth/V1/login.json` | Auth only — no protected data endpoints yet |

**Admin API note:** All current business endpoints are on the public allowlist (`MySqlAuthHandler.php`). Admin login establishes `wlSession` but does not gate data access today. See Admin API section below.

## Service registry (factories)

| Factory | Service class |
|---------|---------------|
| `rpm_site_settings_service()` | `RPM\Site\SiteSettingsService` |
| `rpm_site_lead_service()` | `RPM\Site\SiteLeadService` |
| `rpm_site_blog_service()` | `RPM\Site\BlogService` |
| `rpm_property_catalog_service()` | `RPM\Property\CatalogService` |
| `rpm_booking_quote_calculator()` | `RPM\Booking\Quote\QuoteCalculator` |
| `rpm_booking_reservation_service()` | `RPM\Booking\Reservation\ReservationService` |
| `rpm_finance_invoice_service()` | `RPM\Finance\InvoiceService` |
| `rpm_encryption_service()` | `RPM\Security\EncryptionService` |

## Endpoints (ordered for review)

Order: Authentication → Properties → Booking → Website/CMS → Blog & Attractions → Integrations

### Admin Authentication

Controller: auth · Establishes wlSession for admin* endpoints. Login is public; user requires session.

#### `POST` /API/auth/V1/login.json

**Auth:** public

Admin API key login; sets wlSession.

### Request body (JSON)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `adminAPIKey` | string | Yes | From admins table |
| `adminAPISecret` | string | Yes | API secret |

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `adminId` | int | No | Admin ID |
| `adminFirstName` | string | No | First name |
| `adminLastName` | string | No | Last name |
| `adminRole` | string | No | 0=super, 1=landlord, 2=svc_rep, 3=HK |
| `superuser` | string | No | Superuser flag |

**Example request:**

```json
{ "adminAPIKey": "your-key", "adminAPISecret": "your-secret" }
```

**HTTP errors:** 401 Invalid credentials

#### `GET` /API/auth/V1/user.json

**Auth:** admin

Current admin session.

**HTTP errors:** 401 Unauthorized

### Admin Properties

Controller: adminProperties · Service: CatalogService::adminListProperties / adminGetPropertyById

#### `GET` /API/adminProperties/V1/properties.json

**Auth:** admin

List all properties (LTR+VR by default). Includes archived/off-website unless filtered.

**Service:** `rpm_property_catalog_service()`

**Roles:** `super_admin, svc_rep, landlord (read, own properties)`

### Query parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `type` | string | No | all (default), ltr, or vr catalog mode |
| `page` | int | No | Page number (default 1) |
| `limit` | int | No | Per page (default 20, max 100) |
| `includeArchived` | string | No | 1 = include archived properties |
| `onWebsite` | string | No | 0 or 1 to filter onWebsite flag |
| `search` | string | No | Name/address search (max 128 chars) |
| `beds` | string | No | Bedroom filter |
| `baths` | string | No | Bathroom filter |
| `location` | string | No | Address search |
| `propertyType` | string | No | propertyType filter (when in list mode) |
| `pets` | string | No | 1 = pets allowed only |
| `featured` | string | No | 1 = featured VR listings only |

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `items` | array | No | Property objects with isArchived |
| `page` | int | No | Current page |
| `limit` | int | No | Page size |
| `total` | int | No | Total matching properties |
| `ltrTotal` | int | No | LTR count when type=all |
| `vrTotal` | int | No | VR count when type=all |

**HTTP errors:** 401 Unauthorized, 403 Forbidden (wrong role)

#### `GET` /API/adminProperties/V1/properties/{id}.json

**Auth:** admin

Single property by ID (LTR or VR).

**Roles:** `super_admin, svc_rep, landlord (read, own properties)`

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | propertyId |

**HTTP errors:** 404 Property not found

#### `GET` /API/adminProperties/V1/property/{id}.json

**Auth:** admin

Alias for single property by ID.

**Roles:** `super_admin, svc_rep, landlord (read, own properties)`

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | propertyId |

**HTTP errors:** 404 Property not found

#### `POST` /API/adminProperties/V1/properties.json

**Auth:** admin

Create LTR or VR property (type field).

**Service:** `CatalogService::adminCreateProperty()`

**Roles:** `super_admin`

### Request body (JSON)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `type` | string | Yes | ltr or vr |
| `propertyName` | string | Yes | Unique property name (max 128) |
| `propertyDesc` | string | Yes | Description (max 5000) |
| `propertyRate` | string | Yes | Nightly/monthly rate |
| `latePenalty` | string | No | Required for LTR; default 0.00 for VR |
| `propertyDeposit` | string | No | Required for LTR |
| `petsAllowed` | int | No | 0 or 1 |
| `landlord` | int | No | Landlord adminId (default: caller) |
| `propertyAddress` | string | No | Street address |
| `propertyLocation` | string | No | VR location label |

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `propertyId` | int | No | New property ID |
| `type` | string | No | ltr or vr |

**HTTP errors:** 400 validation, 401, 403, 409 duplicate name

#### `PUT` /API/adminProperties/V1/properties/{id}.json

**Auth:** admin

Update whitelisted property fields.

**Roles:** `super_admin`

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | propertyId |

### Request body (JSON)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `propertyName` | string | No | Mutable name |
| `propertyDesc` | string | No | Description |
| `propertyRate` | string | No | Rate |
| `onWebsite` | int | No | 0 or 1 |
| `isArchived` | int | No | 0 or 1 soft-archive |

**HTTP errors:** 400 unknown fields, 404, 409 duplicate name

#### `PUT` /API/adminProperties/V1/property/{id}.json

**Auth:** admin

Alias for property update.

**Roles:** `super_admin`

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | propertyId |

**HTTP errors:** 400, 404

#### `DELETE` /API/adminProperties/V1/properties/{id}.json

**Auth:** admin

Soft-archive property (isArchived=1). Blocked when active future VR reservations exist.

**Roles:** `super_admin`

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | propertyId |

**HTTP errors:** 404, 409 blocking dependencies

#### `DELETE` /API/adminProperties/V1/property/{id}.json

**Auth:** admin

Alias for property archive.

**Roles:** `super_admin`

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | propertyId |

**HTTP errors:** 404, 409

### Admin Invoices

Controller: adminInvoices · Service: rpm_finance_invoice_service()

#### `GET` /API/adminInvoices/V1/invoices.json

**Auth:** admin

List invoices with optional filters (excludes drafts unless status=9).

**Service:** `InvoiceService::listInvoices()`

**Roles:** `super_admin, svc_rep, landlord (read, own properties)`

### Query parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `tenantId` | int | No | Filter by tenant |
| `propertyId` | int | No | Filter by property |
| `status` | int | No | 0=Unpaid, 1=Paid, 2=Refunded, 3=Processing, 5=Cancelled, 7=Void, 9=Draft |
| `dateFrom` | string | No | Invoice date >= (YYYY-MM-DD) |
| `dateTo` | string | No | Invoice date <= (YYYY-MM-DD) |
| `search` | string | No | ID, description, tenant name/email |
| `page` | int | No | Page (default 1) |
| `limit` | int | No | Per page (default 20, max 100) |

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `items` | array | No | Invoice summary rows |
| `items[].statusLabel` | string | No | Human-readable status |
| `page` | int | No | Current page |
| `total` | int | No | Total matches |

**HTTP errors:** 401, 403

#### `GET` /API/adminInvoices/V1/invoices/{id}.json

**Auth:** admin

Single invoice with line items and computed totals.

**Roles:** `super_admin, svc_rep, landlord (read, own properties)`

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | Invoice ID |

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `lineItems` | array | No | invoiceItems rows |
| `subtotalComputed` | float | No | Sum of line items |
| `totalComputed` | float | No | Total with tax |
| `paidComputed` | float | No | Settled payments |

**HTTP errors:** 404 Invoice not found

#### `GET` /API/adminInvoices/V1/invoice/{id}.json

**Auth:** admin

Alias for single invoice.

**Roles:** `super_admin, svc_rep, landlord (read, own properties)`

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | Invoice ID |

**HTTP errors:** 404 Invoice not found

#### `POST` /API/adminInvoices/V1/invoices.json

**Auth:** admin

Create invoice with optional line items.

**Service:** `InvoiceService::adminCreateInvoice()`

**Roles:** `super_admin`

### Request body (JSON)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `description` | string | Yes | RENT\|BOOKING\|HOUSEKEEPING\|SERVICE\|DEPOSIT\|ADJUSTMENT\|OWNER |
| `tenantId` | int | No | Invoicee tenant (or use adminId) |
| `adminId` | int | No | Invoicee admin/landlord |
| `propertyId` | int | No | Linked property |
| `dueDate` | string | Yes | YYYY-MM-DD |
| `tax` | float | No | Tax rate % |
| `tax2` | float | No | Second tax rate % |
| `status` | int | No | Invoice status (default 0) |
| `lineItems` | array | No | [{description, amount, quantity, taxed}] |

**HTTP errors:** 400 validation, 401, 403

#### `PUT` /API/adminInvoices/V1/invoices/{id}.json

**Auth:** admin

Update invoice header and append line items.

**Service:** `InvoiceService::adminUpdateInvoice()`

**Roles:** `super_admin`

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | Invoice ID |

### Request body (JSON)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `status` | int | No | 0–9 status codes |
| `tax` | float | No | Tax rate % |
| `description` | string | No | Invoice type |
| `lineItems` | array | No | Additional line items to append |

**HTTP errors:** 400, 404

**Notes:** Calls rpm_refresh_invoice_totals() and rpm_sync_invoice_payment_state() after mutation.

#### `PUT` /API/adminInvoices/V1/invoice/{id}.json

**Auth:** admin

Alias for invoice update.

**Roles:** `super_admin`

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | Invoice ID |

**HTTP errors:** 404

#### `DELETE` /API/adminInvoices/V1/invoices/{id}.json

**Auth:** admin

Cancel invoice (status=5) when no settled payments. Reverses journal entry.

**Roles:** `super_admin`

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | Invoice ID |

**HTTP errors:** 404, 409 settled payments

#### `DELETE` /API/adminInvoices/V1/invoice/{id}.json

**Auth:** admin

Alias for invoice cancel.

**Roles:** `super_admin`

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | Invoice ID |

**HTTP errors:** 404, 409

### Admin Tenants

Controller: adminTenants · Service: rpm_admin_tenant_service()

#### `GET` /API/adminTenants/V1/tenants.json

**Auth:** admin

List tenants (paginated, searchable by email/name).

**Service:** `TenantService::listTenants()`

**Roles:** `super_admin, svc_rep, landlord (read, own properties)`

### Query parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `search` | string | No | Email or name search |
| `isActive` | string | No | 0 or 1 |
| `page` | int | No | Page (default 1) |
| `limit` | int | No | Per page (default 20, max 100) |

**HTTP errors:** 401, 403

#### `GET` /API/adminTenants/V1/tenants/{id}.json

**Auth:** admin

Single tenant profile (decrypted contact fields when available).

**Roles:** `super_admin, svc_rep, landlord (read, own properties)`

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | tenantId |

**HTTP errors:** 404 Tenant not found

#### `GET` /API/adminTenants/V1/tenant/{id}.json

**Auth:** admin

Alias for single tenant.

**Roles:** `super_admin, svc_rep, landlord (read, own properties)`

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | tenantId |

**HTTP errors:** 404 Tenant not found

#### `POST` /API/adminTenants/V1/tenants.json

**Auth:** admin

Create tenant account (bcrypt password).

**Service:** `TenantService::adminCreateTenant()`

**Roles:** `super_admin`

### Request body (JSON)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `tenantEmail` | string | Yes | Unique email |
| `tenantFirstName` | string | Yes | First name |
| `tenantLastName` | string | Yes | Last name |
| `password` | string | Yes | Plain password (hashed via rpm_password_hash) |
| `isActive` | int | No | 0 or 1 (default 0) |
| `tenantPhone` | string | No | Encrypted at rest |

**HTTP errors:** 400, 409 duplicate email

#### `PUT` /API/adminTenants/V1/tenants/{id}.json

**Auth:** admin

Update tenant profile fields.

**Service:** `TenantService::adminUpdateTenant()`

**Roles:** `super_admin`

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | tenantId |

### Request body (JSON)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `tenantFirstName` | string | No | First name |
| `isActive` | int | No | 0 or 1 |
| `password` | string | No | New password (rpm_password_hash) |

**HTTP errors:** 400, 404

#### `PUT` /API/adminTenants/V1/tenant/{id}.json

**Auth:** admin

Alias for tenant update.

**Roles:** `super_admin`

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | tenantId |

**HTTP errors:** 404

#### `DELETE` /API/adminTenants/V1/tenants/{id}.json

**Auth:** admin

Deactivate tenant (isActive=0). Not a hard SQL delete.

**Roles:** `super_admin`

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | tenantId |

**HTTP errors:** 404

#### `DELETE` /API/adminTenants/V1/tenant/{id}.json

**Auth:** admin

Alias for tenant deactivate.

**Roles:** `super_admin`

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | tenantId |

**HTTP errors:** 404

### Admin Reservations

Controller: adminReservations · Service: rpm_booking_reservation_service()

#### `GET` /API/adminReservations/V1/reservations.json

**Auth:** admin

List VR reservations with optional filters.

**Service:** `ReservationService::listReservations()`

**Roles:** `super_admin, svc_rep, landlord (read, own properties)`

### Query parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `propertyId` | int | No | Filter by VR property (serviceID) |
| `tenantId` | int | No | Filter by linked tenant |
| `status` | string | No | Reservation status code |
| `dateFrom` | string | No | Stay start >= (YYYY-MM-DD) |
| `dateTo` | string | No | Stay end <= (YYYY-MM-DD) |
| `search` | string | No | Guest name, email, or reservation ID |
| `page` | int | No | Page (default 1) |
| `limit` | int | No | Per page (default 20, max 100) |

**HTTP errors:** 401, 403

#### `GET` /API/adminReservations/V1/reservations/{id}.json

**Auth:** admin

Single reservation with stay dates.

**Roles:** `super_admin, svc_rep, landlord (read, own properties)`

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | Reservation ID |

**HTTP errors:** 404 Reservation not found

#### `GET` /API/adminReservations/V1/reservation/{id}.json

**Auth:** admin

Alias for single reservation.

**Roles:** `super_admin, svc_rep, landlord (read, own properties)`

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | Reservation ID |

**HTTP errors:** 404 Reservation not found

#### `POST` /API/adminReservations/V1/reservations.json

**Auth:** admin

Create VR reservation (admin path; no public CAPTCHA).

**Service:** `ReservationService::createReservation()`

**Roles:** `super_admin`

### Request body (JSON)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `propertyId` | int | Yes | VR property (serviceID) |
| `dateFrom` | string | Yes | Stay start YYYY-MM-DD |
| `dateTo` | string | Yes | Stay end YYYY-MM-DD |
| `name` | string | Yes | Guest name |
| `email` | string | Yes | Guest email |
| `totalPrice` | float | No | Booking total |
| `status` | string | No | Reservation status (default 1) |
| `skipAvailabilityCheck` | bool | No | Bypass availability validation |
| `adminBlock` | string | No | Admin block flag |

**HTTP errors:** 400 dates unavailable, 401, 403

#### `PUT` /API/adminReservations/V1/reservations/{id}.json

**Auth:** admin

Update reservation status and/or stay dates.

**Service:** `ReservationService::adminUpdateReservation()`

**Roles:** `super_admin`

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | Reservation ID |

### Request body (JSON)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `status` | string | No | New status code |
| `dateFrom` | string | No | New stay start |
| `dateTo` | string | No | New stay end |
| `totalPrice` | float | No | Updated total |

**HTTP errors:** 404, 409 dates unavailable

#### `PUT` /API/adminReservations/V1/reservation/{id}.json

**Auth:** admin

Alias for reservation update.

**Roles:** `super_admin`

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | Reservation ID |

**HTTP errors:** 404

#### `DELETE` /API/adminReservations/V1/reservations/{id}.json

**Auth:** admin

Cancel reservation (status=3). Cancels unpaid linked invoices; retains paid invoices.

**Roles:** `super_admin`

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | Reservation ID |

**HTTP errors:** 404, 409 already cancelled

#### `DELETE` /API/adminReservations/V1/reservation/{id}.json

**Auth:** admin

Alias for reservation cancel.

**Roles:** `super_admin`

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | Reservation ID |

**HTTP errors:** 404, 409

### Admin Expenses

Controller: adminExpenses · Service: rpm_finance_expense_service()

#### `GET` /API/adminExpenses/V1/expenses.json

**Auth:** admin

List expenses with filters.

**Roles:** `super_admin, svc_rep, landlord (read, own properties)`

### Query parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `propertyId` | int | No | Filter by property |
| `status` | int | No | 0=Unpaid, 1=Paid, 3=Cancelled, 9=Draft |
| `dateFrom` | string | No | Expense date >= |
| `dateTo` | string | No | Expense date <= |
| `page` | int | No | Page (default 1) |
| `limit` | int | No | Per page (max 100) |

**HTTP errors:** 401, 403

#### `GET` /API/adminExpenses/V1/expenses/{id}.json

**Auth:** admin

Single expense with line items and computed totals.

**Roles:** `super_admin, svc_rep, landlord (read, own properties)`

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | Expense ID |

**HTTP errors:** 404

#### `POST` /API/adminExpenses/V1/expenses.json

**Auth:** admin

Create expense with optional line items.

**Roles:** `super_admin`

### Request body (JSON)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `description` | string | Yes | RENT\|BOOKING\|HOUSEKEEPING\|SERVICE\|OWNER\|ADJUSTMENT |
| `paytoId` | string | Yes | Payee selector (admin or vendor id) |
| `dueDate` | string | Yes | YYYY-MM-DD |
| `propertyId` | int | No | Linked property |
| `lineItems` | array | No | Line items to append |

**HTTP errors:** 400, 403

#### `PUT` /API/adminExpenses/V1/expenses/{id}.json

**Auth:** admin

Update expense header and append line items.

**Roles:** `super_admin`

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | Expense ID |

**HTTP errors:** 400, 404

#### `DELETE` /API/adminExpenses/V1/expenses/{id}.json

**Auth:** admin

Cancel expense (status=3) when no settled payments.

**Roles:** `super_admin`

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | Expense ID |

**HTTP errors:** 404, 409 settled payments

### Admin Housekeeping

Controller: adminHousekeeping · Service: rpm_housekeeping_service()

#### `GET` /API/adminHousekeeping/V1/requests.json

**Auth:** admin

List housekeeping requests.

**Roles:** `super_admin, svc_rep`

### Query parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `propertyId` | int | No | Filter by property |
| `status` | int | No | Request status (6=archived excluded by default) |
| `assignedTo` | int | No | Housekeeper adminId |
| `dateFrom` | string | No | Request date >= |
| `dateTo` | string | No | Request date <= |

**HTTP errors:** 401, 403

#### `GET` /API/adminHousekeeping/V1/requests/{id}.json

**Auth:** admin

Single housekeeping request.

**Roles:** `super_admin, svc_rep`

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | requestId |

**HTTP errors:** 404

#### `POST` /API/adminHousekeeping/V1/requests.json

**Auth:** admin

Create housekeeping request.

**Roles:** `super_admin`

### Request body (JSON)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `propertyId` | int | Yes | Property ID |
| `requestTitle` | string | Yes | Request title |
| `requestDate` | string | No | Scheduled date |

**HTTP errors:** 400, 403

#### `PUT` /API/adminHousekeeping/V1/requests/{id}.json

**Auth:** admin

Update housekeeping request fields.

**Roles:** `super_admin`

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | requestId |

**HTTP errors:** 404

#### `DELETE` /API/adminHousekeeping/V1/requests/{id}.json

**Auth:** admin

Archive request (requestStatus=6).

**Roles:** `super_admin`

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | requestId |

**HTTP errors:** 404, 409 already archived

### Admin Reports

Controller: adminReports · Service: rpm_finance_report_service() — read-only

#### `GET` /API/adminReports/V1/income.json

**Auth:** admin

Income summary from settled transactions (amountin) in date range.

**Roles:** `super_admin, svc_rep`

### Query parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `dateFrom` | string | Yes | YYYY-MM-DD |
| `dateTo` | string | Yes | YYYY-MM-DD |
| `propertyId` | int | No | Optional property filter |

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `totalIncome` | float | No | Sum of income transactions |
| `transactionCount` | int | No | Matching rows |

**HTTP errors:** 400 missing dates, 403

#### `GET` /API/adminReports/V1/occupancy.json

**Auth:** admin

VR occupancy summary — booked nights per property.

**Roles:** `super_admin, svc_rep`

### Query parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `dateFrom` | string | Yes | Stay checkout >= |
| `dateTo` | string | Yes | Stay checkout <= |
| `propertyId` | int | No | Optional VR property filter |

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `items` | array | No | Per-property occupancy rows |
| `totalNights` | int | No | Grand total nights |

**HTTP errors:** 400, 403

### Tenant Authentication

Controller: publicAuth · PHP session cookie (Secure, HttpOnly, SameSite=Lax). Requires HTTPS in production; set RPM_FORCE_HTTPS=1 to redirect HTTP.

#### `POST` /API/publicAuth/V1/login.json

**Auth:** public

Authenticate tenant; sets session (tenantId, propertyId, leaseId).

### Request body (JSON)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `email` | string | Yes | tenantEmail alias accepted |
| `password` | string | Yes | Plain password |

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `success` | bool | No | true |
| `user.tenantId` | int | No | Tenant ID |
| `user.tenantEmail` | string | No | Email |
| `user.tenantFirstName` | string | No | First name |
| `user.tenantLastName` | string | No | Last name |

**Example request:**

```json
{ "email": "guest@example.com", "password": "your-password" }
```

**Example response:**

```json
{
  "success": true,
  "user": {
    "tenantId": 15,
    "tenantEmail": "guest@example.com",
    "tenantFirstName": "Jane",
    "tenantLastName": "Guest"
  }
}
```

**HTTP errors:** 400 missing fields, 401 invalid credentials, 403 inactive account, 429 lockout (5 failures / 15 min per IP and email)

#### `POST` /API/publicAuth/V1/logout.json

**Auth:** public

Destroy tenant session.

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `success` | bool | No | true |

#### `GET` /API/publicAuth/V1/me.json

**Auth:** session

Current tenant session data.

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `tenantId` | int | No | Session tenant |
| `tenantEmail` | string | No | Email |
| `tenantFirstName` | string | No | First name |
| `tenantLastName` | string | No | Last name |
| `propertyId` | int | No | Linked property |
| `leaseId` | int | No | Active lease |

**HTTP errors:** 401 Not authenticated

#### `POST` /API/publicAuth/V1/register.json

**Auth:** public

Register new tenant; sends activation email.

### Request body (JSON)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `tenantFirstName` | string | Yes | First name |
| `tenantLastName` | string | Yes | Last name |
| `tenantEmail` | string | Yes | newEmail alias |
| `password` | string | Yes | password1 alias |
| `password2` | string | No | Confirmation |
| `tenantAddress` | string | No | Encrypted at rest |
| `tenantCity` | string | No | Encrypted |
| `tenantState` | string | No | Encrypted |
| `tenantZip` | string | No | Encrypted |
| `tenantCountry` | string | No | Encrypted |
| `tenantPhone` | string | No | Encrypted |
| `tenantAltPhone` | string | No | Encrypted |
| `applyToLeaseId` | int | No | Post-activation lease redirect |
| `isEmpty` | string | No | Honeypot — must be empty |

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `success` | bool | No | true |
| `activateUrl` | string | No | Activation link sent by email |

**HTTP errors:** 400 validation, 409 email exists, 500 registration failure

#### `GET` /API/publicAuth/V1/activate.json

**Auth:** public

Activate account via email link; starts session.

### Query parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `tenantEmail` | string | Yes | Account email |
| `hash` | string | Yes | Activation token |
| `applyToLeaseId` | int | No | Optional lease redirect |

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `success` | bool | No | true |
| `user` | object | No | Tenant profile |
| `applyToLeaseId` | int | No | Echo of query param |

**HTTP errors:** 400 invalid link, 429 rate limited (10/hour per IP)

### VR Properties

Controller: vrProperties · Service: CatalogService (Vrproperties table)

#### `GET` /API/vrProperties/V1/vrProperties.json

**Auth:** public

List vacation rental properties visible on website.

### Query parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `page` | int | No | Page number (default 1) |
| `limit` | int | No | Per page (default 20, max 100) |
| `beds` | string | No | Bedroom filter (alias bedrooms) |
| `baths` | string | No | Bathroom filter |
| `location` | string | No | Address search (max 128 chars) |
| `search` | string | No | Name/address search (max 128 chars) |
| `type` | string | No | propertyType filter |
| `pets` | string | No | 1 = pets allowed only |
| `featured` | string | No | 1 = featured listings only |

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `items` | array | No | Property objects |
| `page` | int | No | Current page |
| `limit` | int | No | Page size |
| `total` | int | No | Total matching properties |
| `items[].propertyId` | int | No | ID (>1999 for VR) |
| `items[].propertyName` | string | No | Name |
| `items[].propertyDesc` | string | No | Description HTML |
| `items[].propertyAddress` | string | No | Address |
| `items[].propertyLocation` | string | No | Location label |
| `items[].propertyRate` | float | No | Nightly rate |
| `items[].propertyWeekendRate` | float | No | Weekend rate |
| `items[].propertyDeposit` | float | No | Security deposit |
| `items[].petsAllowed` | string | No | Yes/No |
| `items[].propertyAmenities` | string | No | Amenities list |
| `items[].bedrooms` | string | No | Bedroom count |
| `items[].bathrooms` | string | No | Bathroom count |
| `items[].propertyBookingFee` | float | No | Booking fee |
| `items[].housekeepingFee` | float | No | Cleaning fee |
| `items[].petFee` | float | No | Per-pet fee |
| `items[].featuredImage` | string | No | Primary image filename |
| `items[].isFeatured` | int | No | Featured flag |
| `items[].seoTitle` | string | No | SEO title |
| `items[].seoDesc` | string | No | SEO description |

**Example response:**

```json
{
  "items": [{ "propertyId": 2001, "propertyName": "Lake View Cabin", "propertyRate": 189.00, "bedrooms": "3", "featuredImage": "lake.jpg" }],
  "page": 1,
  "limit": 20,
  "total": 45
}
```

#### `GET` /API/vrProperties/V1/vrProperties/{id}.json

**Auth:** public

Single VR property with all catalog fields.

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | propertyId |

**HTTP errors:** 404 Property not found

#### `GET` /API/vrProperties/V1/vrPropertyImages.json

**Auth:** public

All VR property images.

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `pictureId` | int | No | Image ID |
| `propertyId` | int | No | Property FK |
| `pictureName` | string | No | Alt text |
| `pictureUrl` | string | No | Filename |
| `isFeatured` | int | No | 1 = featured |
| `pictureOrder` | int | No | Sort order |
| `pictureDate` | string | No | Formatted upload date |

#### `GET` /API/vrProperties/V1/vrPropertyImages/{propertyId}.json

**Auth:** public

Images for one VR property.

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `propertyId` | int | Yes | Property ID |

### LTR Properties

Controller: ltrProperties · properties table

#### `GET` /API/ltrProperties/V1/ltrProperties.json

**Auth:** public

List long-term rental properties (same filters as VR).

### Query parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `page` | int | No | Page (default 1) |
| `limit` | int | No | Per page (max 100) |
| `beds` | string | No | Bedroom filter |
| `search` | string | No | Search term (max 128 chars) |

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `items` | array | No | LTR properties (no VR-only fields) |
| `items[].propertyId` | int | No | ID |
| `items[].propertyName` | string | No | Name |
| `items[].propertyRate` | float | No | Monthly rent |
| `items[].propertyDeposit` | float | No | Deposit |
| `items[].availOn` | string | No | Available date |
| `items[].featuredImage` | string | No | Primary image |

#### `GET` /API/ltrProperties/V1/ltrProperties/{id}.json

**Auth:** public

Single LTR property.

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | propertyId |

**HTTP errors:** 404 Property not found

#### `GET` /API/ltrProperties/V1/ltrPropertyImages/{propertyId}.json

**Auth:** public

Images from propertypictures table.

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `propertyId` | int | Yes | Property ID |

### Public Booking

Controller: publicBooking · Booking PSR-4 services

#### `GET` /API/publicBooking/V1/availability/{propertyId}.json

**Auth:** public

Check if date range is bookable with pricing preview.

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `propertyId` | int | Yes | VR property ID |

### Query parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `dateFrom` | string | Yes | YYYY-MM-DD (alias from) |
| `dateTo` | string | Yes | YYYY-MM-DD (alias to) |

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `available` | bool | No | true if bookable |
| `message` | string | No | Reason if unavailable |
| `price` | float | No | Total stay price when available |
| `nightRates` | array | No | Per-night breakdown |
| `intervals` | int | No | Night count |
| `spots` | int | No | Remaining capacity |

**Example request:**

```json
GET /API/publicBooking/V1/availability/2001.json?dateFrom=2026-08-01&dateTo=2026-08-07
```

**Example response:**

```json
{
  "available": true,
  "message": "",
  "price": 1134.00,
  "nightRates": [{ "date": "2026-08-01", "rate": 189.00 }],
  "intervals": 6,
  "spots": 1
}
```

**HTTP errors:** 400 missing propertyId or dates

#### `GET` /API/publicBooking/V1/calendar/{propertyId}.json

**Auth:** public

Calendar data: blocked ranges and per-day prices.

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `propertyId` | int | Yes | VR property |

### Query parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `months` | int | No | Forward months (default 12, max 24) |

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `propertyId` | int | No | Property |
| `today` | string | No | Server date YYYY-MM-DD |
| `until` | string | No | Range end date |
| `ranges` | array | No | Blocked/reserved date ranges |
| `baseRate` | float | No | Default nightly rate |
| `weekendRate` | float | No | Weekend rate |
| `dayPrices` | object | No | Map of date → price |

**Example response:**

```json
{
  "propertyId": 2001,
  "today": "2026-07-13",
  "until": "2027-07-13",
  "ranges": [{ "start": "2026-08-10", "end": "2026-08-15", "type": "reservation" }],
  "baseRate": 189.00,
  "weekendRate": 225.00,
  "dayPrices": { "2026-08-01": 189.00, "2026-08-02": 225.00 }
}
```

#### `POST` /API/publicBooking/V1/quote.json

**Auth:** public

Full quote with fees, taxes, and coupon.

**Service:** `QuoteCalculator::buildQuote()`

### Request body (JSON)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `propertyId` | int | Yes | serviceID alias |
| `dateFrom` | string | Yes | Check-in YYYY-MM-DD |
| `dateTo` | string | Yes | Check-out YYYY-MM-DD |
| `adults` | int | No | Guest count (bAdults alias, default 1) |
| `children` | int | No | bChildren alias |
| `pets` | int | No | bPets alias |
| `couponCode` | string | No | Optional discount code |
| `extraRooms` | array | No | Additional room prices |

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `propertyId` | int | No | Property |
| `dateFrom` | string | No | Check-in |
| `dateTo` | string | No | Check-out |
| `nights` | int | No | Night count |
| `subtotal` | float | No | Room subtotal |
| `discount` | float | No | Coupon discount |
| `couponApplied` | bool | No | Coupon valid |
| `cleaningFee` | float | No | Housekeeping |
| `damageWaiver` | float | No | Damage waiver |
| `bookingFee` | float | No | Booking fee |
| `peopleFees` | float | No | Extra guest fees |
| `petFee` | float | No | Pet fees |
| `taxRate` | float | No | Combined tax % |
| `tax` | float | No | Tax amount |
| `total` | float | No | Grand total |
| `lineItems` | array | No | { label, amount } breakdown |
| `nightRates` | array | No | Per-night rates |

**Example request:**

```json
{
  "propertyId": 2001,
  "dateFrom": "2026-08-01",
  "dateTo": "2026-08-07",
  "adults": 2,
  "children": 0,
  "pets": 0,
  "couponCode": "SUMMER10"
}
```

**Example response:**

```json
{
  "propertyId": 2001,
  "nights": 6,
  "subtotal": 1134.00,
  "discount": 113.40,
  "couponApplied": true,
  "cleaningFee": 150.00,
  "tax": 98.50,
  "total": 1269.10,
  "lineItems": [
    { "label": "6 nights", "amount": 1134.00 },
    { "label": "Discount", "amount": -113.40 },
    { "label": "Cleaning fee", "amount": 150.00 }
  ]
}
```

#### `POST` /API/publicBooking/V1/validateCoupon.json

**Auth:** public

Validate coupon without full quote.

### Request body (JSON)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `propertyId` | int | Yes | Property |
| `couponCode` | string | Yes | Code to check |

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `valid` | bool | No | Coupon accepted |
| `type` | string | No | abs or percent |
| `value` | float | No | Discount value |
| `message` | string | No | Status message |

#### `POST` /API/publicBooking/V1/book.json

**Auth:** public

Create reservation when tenant session or verified guest email + CAPTCHA.

**Service:** `ApiSecurityPolicy::assertBookingAllowed(), ReservationService::createReservation()`

### Request body (JSON)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `propertyId` | int | Yes | VR property |
| `dateFrom` | string | Yes | Check-in (max 10 chars) |
| `dateTo` | string | Yes | Check-out (max 10 chars) |
| `name` | string | No | Guest name (max 128) |
| `email` | string | No | Guest email — required for guest booking |
| `captchaToken` | string | No | Turnstile/reCAPTCHA token when provider is not builtin |
| `captcha` | string | No | Builtin math answer when apiCaptchaProvider=builtin |
| `captcha_sum` | string | No | md5(answer) for builtin provider |
| `phone` | string | No | Phone (max 40) |
| `address` | string | No | bAddress alias |
| `city` | string | No | bCity alias |
| `state` | string | No | bState alias |
| `zip` | string | No | bZip alias |
| `adults` | int | No | Guest count |
| `children` | int | No | Children count |
| `pets` | int | No | Pet count |
| `couponCode` | string | No | Coupon |
| `tenantId` | int | No | Linked tenant if logged in |
| `extraRooms` | array | No | Extra room selections |

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `success` | bool | No | true when reservation created |
| `requiresPortal` | bool | No | true if name/email missing |
| `orderId` | int | No | Booking order ID |
| `invoiceId` | int|null | No | Invoice if total > 0 |
| `tenantId` | int | No | Associated tenant |
| `quote` | object | No | Full quote object |
| `message` | string | No | Next-step instructions |

**Example request:**

```json
{
  "propertyId": 2001,
  "dateFrom": "2026-08-01",
  "dateTo": "2026-08-07",
  "name": "Jane Guest",
  "email": "jane@example.com",
  "phone": "555-0100",
  "adults": 2
}
```

**Example response:**

```json
{
  "success": true,
  "orderId": 8842,
  "invoiceId": 1205,
  "tenantId": 0,
  "quote": { "total": 1269.10 },
  "message": "Reservation and invoice created. Complete payment via the guest portal."
}
```

**Notes:** Requires tenant session OR guest email + CAPTCHA per apiPolicy in settings.json. Rate limited (429). Without name/email returns { requiresPortal: true, quote } directing to users/booking-home.php.

### Lease Applications

#### `POST` /API/leaseApplications/V1/apply.json

**Auth:** session

Submit LTR lease application for logged-in tenant.

### Request body (JSON)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `propertyId` | int | Yes | LTR property to apply for |

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `success` | bool | No | true |
| `applicationId` | int | No | leaseApplications insert ID |

**HTTP errors:** 401 Login required, 400 missing propertyId

### Site Settings

Controller: siteSettings · Service: rpm_site_settings_service()

#### `GET` /API/siteSettings/V1/settings.json

**Auth:** public

Returns public site configuration and API security policy (secrets excluded).

**Service:** `SiteSettingsService::settings(), ApiSecurityPolicy::publicPolicy()`

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `installUrl` | string | No | Site base URL |
| `logoUrl` | string | No | Logo path/URL |
| `siteName` | string | No | Display name |
| `businessName` | string | No | Legal business name |
| `businessAddress` | string | No | Business address |
| `businessEmail` | string | No | Contact email |
| `businessPhone` | string | No | Business phone |
| `businessTaxId` | string | No | Tax ID — only when apiExposeBusinessTaxId enabled in admin |
| `contactPhone` | string | No | Public contact phone |
| `localization` | string | No | Locale code |
| `paymentCurrency` | string | No | Currency code |
| `siteType` | string | No | Site type |
| `siteCategory` | string | No | Site category |
| `systemTimeZone` | string | No | PHP timezone |
| `systemDateFormat` | string | No | Date format |
| `vrCheckInTime` | string | No | Default VR check-in |
| `vrCheckOutTime` | string | No | Default VR check-out |
| `paymentCompleteMsg` | string | No | Payment success message |
| `paymentEmail` | string | No | Payment notification email |
| `uploadPath` | string | No | Only when apiExposeInternalPaths legacy opt-in |
| `VruploadPath` | string | No | Only when apiExposeInternalPaths legacy opt-in |
| `avatarFolder` | string | No | Only when apiExposeInternalPaths legacy opt-in |
| `propertyPicsPath` | string | No | Only when apiExposeInternalPaths legacy opt-in |
| `VrpropertyPicsPath` | string | No | Only when apiExposeInternalPaths legacy opt-in |
| `apiPolicy` | object | No | Public booking/CAPTCHA policy (no secrets) |
| `apiPolicy.apiBookingGuestEnabled` | int | No | 1 = guest checkout allowed |
| `apiPolicy.apiBookingRequireLogin` | int | No | 1 = tenant session required for book.json |
| `apiPolicy.apiCaptchaProvider` | string | No | builtin \| turnstile \| recaptcha_v2 \| recaptcha_v3 |
| `apiPolicy.apiCaptchaSiteKey` | string | No | Public CAPTCHA site key |
| `apiPolicy.apiContactCaptchaEnabled` | int | No | 1 = contact POST requires CAPTCHA |
| `apiPolicy.apiSubscribeCaptchaEnabled` | int | No | 1 = subscribe POST requires CAPTCHA |

**Example request:**

```json
GET /API/siteSettings/V1/settings.json
Accept: application/json
```

**Example response:**

```json
{
  "installUrl": "https://demo.rpm-pro.com/",
  "siteName": "My Rentals",
  "businessEmail": "info@example.com",
  "paymentCurrency": "USD",
  "systemTimeZone": "America/New_York",
  "vrCheckInTime": "16:00",
  "vrCheckOutTime": "10:00",
  "apiPolicy": {
    "apiBookingGuestEnabled": 1,
    "apiBookingRequireLogin": 0,
    "apiCaptchaProvider": "builtin",
    "apiCaptchaSiteKey": "",
    "apiContactCaptchaEnabled": 0,
    "apiSubscribeCaptchaEnabled": 0
  }
}
```

### Site Content

Controller: siteContent · CMS blocks from siteContent table

#### `GET` /API/siteContent/V1/content.json

**Auth:** public

All CMS content blocks (single row, all columns).

**Service:** `SiteSettingsService::siteContent()`

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `indexHeader` | string | No | Homepage header HTML |
| `indexSlogan` | string | No | Homepage slogan |
| `indexPageTitle` | string | No | Homepage SEO title |
| `indexPageDesc` | string | No | Homepage meta description |
| `indexPageKeywords` | string | No | Homepage keywords |
| `c2aText` | string | No | Call-to-action text |
| `c2aButtonText` | string | No | CTA button label |
| `c2aUrl` | string | No | CTA link URL |
| `whyUsHeader` | string | No | Why-us section header |
| `whyUsSlogan` | string | No | Why-us slogan |
| `whyUsH2` | string | No | Why-us subheading |
| `whyUsText` | string | No | Why-us body (HTML) |
| `keyPoint1..6` | string | No | Feature bullet points |
| `keyPointButtonText` | string | No | Feature section button |
| `keyPointButtonUrl` | string | No | Feature section URL |
| `indexExtra..4` | string | No | Additional homepage slots |

**Example response:**

```json
{
  "indexHeader": "Welcome to Lake Chatuge Rentals",
  "indexSlogan": "Your mountain getaway",
  "c2aText": "Browse our properties",
  "c2aButtonText": "View Rentals",
  "c2aUrl": "/vacation-rental-properties.php"
}
```

**Notes:** CMS fields may contain trusted admin HTML. Headless consumers must sanitize before rendering (e.g. DOMPurify) or use a strict Content-Security-Policy.

#### `GET` /API/siteContent/V1/content/{key}.json

**Auth:** public

Single content block by column key.

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `key` | string | Yes | siteContent column name, e.g. indexHeader |

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `{key}` | string | No | Value of the requested column |

**Example request:**

```json
GET /API/siteContent/V1/content/indexHeader.json
```

**Example response:**

```json
{ "indexHeader": "Welcome to our rentals" }
```

### Pages

Controller: sitePages

#### `GET` /API/sitePages/V1/pages.json

**Auth:** public

List active static pages.

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `items` | array | No | Array of page objects |
| `items[].pageId` | int | No | Primary key |
| `items[].pageTitle` | string | No | Page title |
| `items[].pageUrl` | string | No | URL slug |
| `items[].pageDesc` | string | No | Meta description |
| `items[].pageImage` | string | No | Featured image path |
| `items[].dateUpdated` | string | No | Last update timestamp |

**Example response:**

```json
{
  "items": [
    {
      "pageId": 11,
      "pageTitle": "Whitewater Rafting",
      "pageUrl": "whitewater-rafting",
      "pageDesc": "Local adventures",
      "pageImage": "",
      "dateUpdated": "2024-06-01 12:00:00"
    }
  ]
}
```

#### `GET` /API/sitePages/V1/pages/{slug}.json

**Auth:** public

Full page by URL slug.

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `slug` | string | Yes | pageUrl value |

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `pageId` | int | No | Primary key |
| `pageTitle` | string | No | Title |
| `pageContent` | string | No | HTML body |
| `pageUrl` | string | No | Slug |
| `pageKeywords` | string | No | SEO keywords |
| `pageDesc` | string | No | Meta description |
| `pageImage` | string | No | Image path |
| `dateUpdated` | string | No | Updated timestamp |

**HTTP errors:** 404 Page not found

**Notes:** Slug must match ^[a-z0-9-]{1,64}$ (400 otherwise). pageContent is trusted admin HTML — sanitize on render.

### Reviews

Controller: siteReviews

#### `GET` /API/siteReviews/V1/reviews.json

**Auth:** public

Property reviews with optional filters.

### Query parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `propertyId` | int | No | Filter by property |
| `limit` | int | No | Max results (default 20, max 50) |

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `items` | array | No | Review list |
| `items[].reviewId` | int | No | Review ID |
| `items[].reviewTitle` | string | No | Title |
| `items[].reviewText` | string | No | Review body |
| `items[].r1..r6` | int | No | Rating dimensions (1-5) |
| `items[].dateCreated` | string | No | Created date |
| `items[].propertyId` | int | No | Related property |
| `items[].tenantFirstName` | string | No | Reviewer first name |
| `items[].tenantLastName` | string | No | Reviewer last name |

**Example request:**

```json
GET /API/siteReviews/V1/reviews.json?propertyId=42&limit=10
```

**Example response:**

```json
{
  "items": [
    {
      "reviewId": 5,
      "reviewTitle": "Great stay",
      "reviewText": "Beautiful property...",
      "r1": 5, "r2": 5, "r3": 5, "r4": 5, "r5": 5, "r6": 5,
      "propertyId": 42,
      "tenantFirstName": "John",
      "tenantLastName": "Doe",
      "dateCreated": "2025-07-01 10:00:00"
    }
  ]
}
```

### Sliders

#### `GET` /API/siteSliders/V1/sliders.json

**Auth:** public

Homepage slider/carousel items.

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `items` | array | No | Slider list |
| `items[].sliderId` | int | No | ID |
| `items[].sliderUrl` | string | No | Background image URL |
| `items[].sliderHeader` | string | No | Headline |
| `items[].sliderText` | string | No | Subtext |
| `items[].sliderButtonText` | string | No | Button label |
| `items[].sliderButtonUrl` | string | No | Button link |

### Navigation

#### `GET` /API/siteNavigation/V1/menus.json

**Auth:** public

Site navigation menus with nested children.

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `menus` | array | No | Primary nav from menus table |
| `menus[].menuId` | int | No | Menu ID |
| `menus[].menuTitle` | string | No | Label |
| `menus[].menuUrl` | string | No | Link URL |
| `menus[].menuFor` | int | No | 1=custom, 2=blog, 3=pages |
| `menus[].children` | array | No | { title, url } sub-items |
| `links` | array | No | Default footer/quick links |

### FAQ

#### `GET` /API/siteFaq/V1/faqs.json

**Auth:** public

Active FAQ entries.

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `items` | array | No | FAQ list |
| `items[].faqId` | int | No | ID |
| `items[].faqQuestion` | string | No | Question (faqName alias) |
| `items[].faqAnswer` | string | No | Answer HTML (faqContent alias) |
| `items[].dateUpdated` | string | No | Updated |

### Events

#### `GET` /API/siteEvents/V1/events.json

**Auth:** public

List active events.

### Query parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `limit` | int | No | Default 50, max 100 |

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `items` | array | No | Event list |
| `items[].eventId` | int | No | ID |
| `items[].eventTitle` | string | No | Title |
| `items[].eventUrl` | string | No | Slug/URL |
| `items[].eventCategory` | string | No | Category |
| `items[].eventDateStart` | string | No | Start date |
| `items[].eventDateEnd` | string | No | End date |
| `items[].eventImage` | string | No | Image path |

#### `GET` /API/siteEvents/V1/events/{id}.json

**Auth:** public

Single event with full content.

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | eventId |

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `eventId` | int | No | ID |
| `eventTitle` | string | No | Title |
| `eventContent` | string | No | Full HTML body |
| `eventDesc` | string | No | Short description |
| `eventKeywords` | string | No | SEO keywords |
| `eventDateStart` | string | No | Start |
| `eventDateEnd` | string | No | End |
| `eventImage` | string | No | Image |
| `adminId` | int | No | Author admin |

**HTTP errors:** 404 Event not found

### Contact & Leads

Controller: siteContact · Service: rpm_site_lead_service()

#### `POST` /API/siteContact/V1/message.json

**Auth:** public

Submit website contact form; stores message and optional lead.

### Request body (JSON)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `senderName` | string | Yes | Sender full name |
| `senderEmail` | string | Yes | Reply email |
| `messageContent` | string | Yes | Message body |
| `messageSubject` | string | No | Subject (default: Website inquiry) |
| `senderPhone` | string | No | Optional phone (creates lead) |
| `Url` | string | No | Honeypot — must be empty |

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `success` | bool | No | true on success |
| `messageId` | int | No | messages table insert ID |

**Example request:**

```json
POST /API/siteContact/V1/message.json
Content-Type: application/json

{
  "senderName": "Jane Smith",
  "senderEmail": "jane@example.com",
  "senderPhone": "555-0100",
  "messageSubject": "Booking inquiry",
  "messageContent": "I would like information about property 42."
}
```

**Example response:**

```json
{ "success": true, "messageId": 128 }
```

**HTTP errors:** 400 validation failure (missing fields or honeypot triggered)

#### `POST` /API/siteContact/V1/subscribe.json

**Auth:** public

Newsletter / mailing list signup.

### Request body (JSON)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `email` | string | Yes | Subscriber email |
| `name` | string | No | Display name (default: Subscriber) |

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `success` | bool | No | true on success |

**Example request:**

```json
{ "email": "subscribe@example.com", "name": "Newsletter Reader" }
```

**Example response:**

```json
{ "success": true }
```

### Blog

Controller: networkBlog · BlogService

#### `GET` /API/networkBlog/V1/networkBlogPosts.json

**Auth:** public

List blog posts (excerpted content).

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `items` | array | No | Posts |
| `items[].postId` | int | No | ID |
| `items[].postTitle` | string | No | Title |
| `items[].postDesc` | string | No | Summary |
| `items[].postContent` | string | No | First 300 chars |
| `items[].postUrl` | string | No | Slug |
| `items[].postCategory` | int | No | Category ID |
| `items[].postImage` | string | No | Featured image |
| `items[].dateCreated` | string | No | Published date |

#### `GET` /API/networkBlog/V1/networkBlogPosts/{id}.json

**Auth:** public

Full blog post.

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | postId |

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `postId` | int | No | ID |
| `postTitle` | string | No | Title |
| `postContent` | string | No | Full HTML body |
| `postDesc` | string | No | Summary |
| `postUrl` | string | No | Slug |
| `postCategory` | int | No | Category |
| `postImage` | string | No | Image |
| `dateCreated` | string | No | Date |

**HTTP errors:** 404 Post not found

#### `GET` /API/networkBlog/V1/networkBlogByCat/{categoryId}.json

**Auth:** public

Posts filtered by category.

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `categoryId` | int | Yes | Category ID |

#### `GET` /API/networkBlog/V1/networkBlogCategory.json

**Auth:** public

List categories.

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `categoryId` | int | No | ID |
| `categoryName` | string | No | Name |
| `isActive` | int | No | Active flag |

### Attractions

#### `GET` /API/siteAttractions/V1/attractions.json

**Auth:** public

List attractions, optionally by category.

### Query parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `category` | int | No | Category ID (alias categoryId) |
| `limit` | int | No | Default 100, max 200 |

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `items` | array | No | Attractions |
| `items[].attractionId` | int | No | ID |
| `items[].attractionTitle` | string | No | Title |
| `items[].attractionUrl` | string | No | Slug |
| `items[].attractionContent` | string | No | Body HTML |
| `items[].attractionDesc` | string | No | Summary |
| `items[].attractionImage` | string | No | Image |
| `items[].categoryId` | int | No | Category FK |
| `items[].categoryName` | string | No | Category label |

#### `GET` /API/siteAttractions/V1/attractions/{id}.json

**Auth:** public

Single attraction.

### Path parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | int | Yes | attractionId |

**HTTP errors:** 404 Attraction not found

#### `GET` /API/siteAttractions/V1/categories.json

**Auth:** public

Attraction categories.

### Response fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `items` | array | No | Categories |
| `items[].categoryId` | int | No | ID |
| `items[].categoryName` | string | No | Name |
| `items[].categoryDesc` | string | No | Description |
| `items[].isActive` | int | No | Active flag |

### Integration scripts (standalone PHP)

Not wlRest — separate files under `/API/`.

#### `POST` /API/stripe-create-intent.php

**Auth:** csrf

Create Stripe PaymentIntent for invoice payment.

### Request body (JSON)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `invoiceId` | int | Yes | Invoice to pay |
| `stripe_csrf` | string | Yes | CSRF token |

**HTTP errors:** 403 invalid CSRF, 400 invalid invoice

#### `POST` /API/stripe-webhook.php

**Auth:** webhook

Stripe webhook handler (payment_intent.succeeded, refunds, disputes).

#### `GET` /API/quickbooks/oauth-start.php

**Auth:** admin

Start QuickBooks OAuth — requires myAdmin session.

#### `POST` /API/bookingpal/webhook.php

**Auth:** webhook

BookingPal inbound OTA reservations.

## JavaScript client

Public website client: `website/src/js/api.js` (`RpmApi`)

```javascript
RpmApi.settings();
RpmApi.vrProperty(2001);
RpmApi.quote({ propertyId: 2001, dateFrom: '2026-08-01', dateTo: '2026-08-07' });
RpmApi.login({ email: 'guest@example.com', password: 'secret' });
```

## Coding standards (API work)

- Thin controllers — validate input, delegate to `rpm_*_service()`
- New logic in `src/RPM/` with factory in appropriate bootstrap file
- Prepared statements for user input
- `rpm_money_cmp()` for currency comparisons
- CSRF on portal POST mutations; Stripe uses separate `stripe_csrf` namespace

---

_End of RPM-PRO API agent reference v5.9.3.3_