PropertyPlex Integration API

Connect your Property Management System (PMS) to PropertyPlex for real-time room status, reservations, and work order management.

REST API JSON Webhooks API Key Auth

Getting Started

Base URL

https://your-server.com/api/v1/integration

All requests and responses use JSON. Include Content-Type: application/json in request headers.

info The Integration API requires a Professional or Enterprise plan. Starter plans will receive a 403 response.

Quick Start

  1. Create an API key in System Admin → Integrations
  2. Copy the key (shown once) — format: ppx_a1b2c3d4e5f6...
  3. Include it in all requests via the X-API-Key header
  4. Start with GET /properties/:propId/rooms to list rooms

Authentication

Authenticate every request by including your API key in the X-API-Key header:

# Preferred
curl -H "X-API-Key: ppx_your_api_key_here" \
  https://your-server.com/api/v1/integration/properties/:propId/rooms

# Alternative (Bearer token)
curl -H "Authorization: Bearer ppx_your_api_key_here" \
  https://your-server.com/api/v1/integration/properties/:propId/rooms

API Key Format

Keys start with ppx_ followed by 32 hexadecimal characters. They are shown once on creation — store them securely. Only the prefix is visible afterward.

Permissions

Each API key can be scoped to specific permissions. Include only the permissions your integration needs:

PermissionDescription
rooms.readRead room data and status
rooms.writeUpdate room status and guest info
rooms.syncSync full room inventory from PMS
workorders.readRead work orders
workorders.createCreate work orders
workorders.writeUpdate work order status
reservations.readRead reservation data
reservations.writeCreate and update reservations

Property Scoping

API keys can optionally be scoped to specific properties. When scoped, requests to other properties return 403 Forbidden. Unscoped keys can access all properties in the tenant.

Rate Limits

Default: 100 requests per minute per API key (configurable per key in System Admin).

Rate limit info is returned in response headers:

HeaderDescription
X-RateLimit-LimitRequests allowed per minute
X-RateLimit-RemainingRequests remaining in current window

When exceeded, you'll receive a 429 Too Many Requests response:

{
  "error": "Rate limit exceeded",
  "retryAfter": 23
}

Rooms

Manage room status, guest information, and room inventory. Room status drives the housekeeping and front-desk workflow.

Room Statuses

READY OCCUPIED TURNING INSPECTION MAINTENANCE OUT_OF_ORDER
lightbulb Inspection enforcement: When a property has inspection enabled, setting a TURNING room to READY will automatically redirect to INSPECTION status. The response includes a message field explaining the redirect.
GET /properties/:propId/rooms List all rooms rooms.read expand_more

Returns all rooms for a property with current status, guest info, and open work order count.

Query Parameters

ParameterTypeDescription
statusstringFilter by status: READY, OCCUPIED, TURNING, INSPECTION, MAINTENANCE, OUT_OF_ORDER
floorintegerFilter by floor number
roomTypestringFilter by room type (e.g., STANDARD, SUITE)

Response

{
  "count": 6,
  "rooms": [
    {
      "id": "uuid",
      "externalId": "102",
      "name": "Room 102",
      "roomNumber": "102",
      "roomType": "STANDARD",
      "floor": 1,
      "status": "OCCUPIED",
      "guestName": "James Wilson",
      "guestCheckoutDate": 1739836800000,
      "maxOccupancy": 2,
      "bedType": "KING",
      "bedCount": 1,
      "amenities": ["wifi", "minibar"],
      "description": "Garden view room",
      "locationName": "Floor 1",
      "openWorkOrders": 0,
      "sortOrder": 10,
      "createdAt": 1739000000000,
      "updatedAt": 1739500000000
    }
  ]
}

Example

curl -H "X-API-Key: ppx_your_key" \
  https://your-server.com/api/v1/integration/properties/PROP_ID/rooms?status=TURNING
GET /properties/:propId/rooms/:roomId Get room detail rooms.read expand_more

Returns detailed information for a single room, including open work orders.

Response

Same fields as list endpoint, plus:

FieldTypeDescription
guestNotesstringNotes about the current guest
openWorkOrdersarrayList of open work order objects (id, title, status, priority, etc.)
POST /properties/:propId/rooms/sync Sync room inventory rooms.sync expand_more

Push your complete room inventory to PropertyPlex. Matches rooms by externalId — creates new rooms, updates existing ones, and marks removed rooms as OUT_OF_ORDER.

warning This replaces the full room inventory. Any room in PropertyPlex with an externalId not in your payload will be marked OUT_OF_ORDER.

Request Body

FieldTypeRequiredDescription
roomsarrayRequiredArray of room objects
rooms[].externalIdstringRequiredYour system's unique room ID
rooms[].namestringRoom display name (e.g., "Room 102")
rooms[].roomNumberstringRoom number
rooms[].roomTypestringSTANDARD, SUITE, COTTAGE, CABIN, VILLA
rooms[].floorintegerFloor number
rooms[].maxOccupancyintegerMaximum guest capacity
rooms[].bedTypestringKING, QUEEN, DOUBLE, TWIN, etc.
rooms[].bedCountintegerNumber of beds
rooms[].amenitiesarrayList of amenity strings (e.g., ["wifi", "minibar"])
rooms[].descriptionstringRoom description

Response

{
  "message": "Room sync complete",
  "created": 3,
  "updated": 2,
  "removed": 1,
  "unchanged": 0
}

Example

curl -X POST -H "X-API-Key: ppx_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "rooms": [
      { "externalId": "101", "name": "Room 101", "roomNumber": "101", "roomType": "STANDARD", "floor": 1, "bedType": "KING", "bedCount": 1 },
      { "externalId": "102", "name": "Room 102", "roomNumber": "102", "roomType": "STANDARD", "floor": 1, "bedType": "QUEEN", "bedCount": 2 }
    ]
  }' \
  https://your-server.com/api/v1/integration/properties/PROP_ID/rooms/sync
PATCH /properties/:propId/rooms/:roomId/status Update room status rooms.write expand_more

Update a room's status and optionally set guest info. Triggers room.status_changed webhook.

Request Body

FieldTypeRequiredDescription
statusstringRequiredREADY, OCCUPIED, TURNING, INSPECTION, MAINTENANCE, OUT_OF_ORDER
guestNamestringGuest name (typically set with OCCUPIED)
guestCheckoutDateintegerCheckout date as Unix timestamp (ms)
guestNotesstringNotes about the guest or stay

Response

{
  "id": "uuid",
  "externalId": "102",
  "name": "Room 102",
  "status": "INSPECTION",
  "guestName": null,
  "updatedAt": 1739500000000,
  "message": "Room redirected to INSPECTION (inspection required for this property)"
}
POST /properties/:propId/rooms/:roomId/check-in Check in guest rooms.write expand_more

Sets a room to OCCUPIED with guest details. Optionally links to a reservation to auto-import special requests and mark the reservation as CHECKED_IN.

Request Body

FieldTypeRequiredDescription
guestNamestringRequiredName of the guest
guestCheckoutDateintegerExpected checkout as Unix timestamp (ms)
guestNotesstringAdditional notes
reservationIdstringLink to a PropertyPlex reservation ID

Response

{
  "message": "Check-in successful",
  "roomId": "uuid",
  "externalId": "102",
  "status": "OCCUPIED",
  "guestName": "Jane Smith",
  "vipStatus": "VIP",
  "guestCount": { "adults": 2, "children": 1 }
}
POST /properties/:propId/rooms/:roomId/check-out Check out guest rooms.write expand_more

Sets the room to TURNING (triggers housekeeping) and clears all guest information. No request body needed.

Response

{
  "message": "Check-out successful",
  "roomId": "uuid",
  "externalId": "102",
  "status": "TURNING"
}

Reservations

Push reservation data from your PMS to PropertyPlex. Reservations are displayed to housekeeping staff so they can see upcoming arrivals and special requests.

Reservation Statuses

CONFIRMED CHECKED_IN CHECKED_OUT CANCELLED NO_SHOW
POST /properties/:propId/reservations Create or upsert reservation reservations.write expand_more

Create a new reservation. If externalId is provided and already exists, the existing reservation is updated (upsert).

Request Body

FieldTypeRequiredDescription
guestNamestringRequiredGuest name
checkInDateintegerRequiredCheck-in date as Unix timestamp (ms)
checkOutDateintegerRequiredCheck-out date as Unix timestamp (ms)
externalIdstringYour system's reservation ID (enables upsert)
confirmationCodestringBooking confirmation number
guestEmailstringGuest email
guestPhonestringGuest phone
roomIdstringPropertyPlex room UUID to assign
adultsCountintegerNumber of adults
childrenCountintegerNumber of children
rateAmountnumberNightly rate amount
rateCurrencystringCurrency code (default: USD)
channelstringBooking channel (e.g., "Booking.com", "Direct")
specialRequestsstringGuest special requests (shown to housekeeping)
vipStatusstringVIP level (e.g., "VIP", "VVIP", "LOYALTY_GOLD")
statusstringDefault: CONFIRMED
notesstringInternal notes
sourcestringSource system identifier

Response

{
  "message": "Reservation created",
  "id": "uuid",
  "externalId": "RES-12345"
}
GET /properties/:propId/reservations List reservations reservations.read expand_more

List reservations with optional filters.

Query Parameters

ParameterTypeDescription
statusstringFilter by reservation status
fromDateintegerOnly reservations checking out on or after this date (ms)
toDateintegerOnly reservations checking in on or before this date (ms)
limitintegerMax results (default: 100, max: 500)

Response

{
  "count": 2,
  "reservations": [
    {
      "id": "uuid",
      "externalId": "RES-12345",
      "confirmationCode": "GH-7842",
      "guestName": "Emily Johnson",
      "guestEmail": "emily@example.com",
      "roomId": "uuid",
      "roomName": "Room 103",
      "roomNumber": "103",
      "checkInDate": 1739836800000,
      "checkOutDate": 1740096000000,
      "adultsCount": 2,
      "childrenCount": 0,
      "rateAmount": 189.00,
      "rateCurrency": "USD",
      "channel": "Booking.com",
      "specialRequests": "Late check-in, extra pillows",
      "vipStatus": null,
      "status": "CONFIRMED",
      "createdAt": 1739000000000,
      "updatedAt": 1739000000000
    }
  ]
}
PATCH /properties/:propId/reservations/:resId Update reservation reservations.write expand_more

Update any fields on an existing reservation. Only provided fields are changed.

Accepts the same fields as the create endpoint (all optional). Triggers reservation.updated or reservation.cancelled webhook.

Response

{
  "message": "Reservation updated",
  "id": "uuid"
}
DELETE /properties/:propId/reservations/:resId Delete reservation reservations.write expand_more

Permanently delete a reservation. Triggers reservation.cancelled webhook.

Response

{
  "message": "Reservation deleted"
}

Work Orders

Create and manage maintenance work orders. Work orders created via the API are tagged with source: "INTEGRATION" and automatically assigned a sequential work order number.

Work Order Statuses

OPEN IN_PROGRESS COMPLETED VERIFIED CANCELLED

Priorities

LOW NORMAL HIGH URGENT
auto_awesome Auto-escalation: Work orders created for OCCUPIED rooms are automatically escalated to HIGH priority (if submitted as NORMAL or LOW).
POST /properties/:propId/work-orders Create work order workorders.create expand_more

Create a maintenance work order. Triggers workorder.created webhook and sends push notifications to property users.

Request Body

FieldTypeRequiredDescription
titlestringRequiredWork order title
descriptionstringDetailed description
roomIdstringPropertyPlex room UUID
prioritystringLOW, NORMAL (default), HIGH, URGENT
issueTypestringHVAC, PLUMBING, ELECTRICAL, APPLIANCE, FURNITURE, TV_INTERNET, HOUSEKEEPING, SAFETY, OTHER
dueDateintegerDue date as Unix timestamp (ms)

Response

{
  "message": "Work order created",
  "id": "uuid",
  "woNumber": 42,
  "status": "OPEN",
  "priority": "HIGH"
}
GET /properties/:propId/work-orders List work orders workorders.read expand_more

Query Parameters

ParameterTypeDescription
statusstringComma-separated statuses (e.g., OPEN,IN_PROGRESS)
prioritystringFilter by priority
roomIdstringFilter by room UUID
limitintegerMax results (default: 100, max: 500)

Response

{
  "count": 1,
  "workOrders": [
    {
      "id": "uuid",
      "woNumber": 42,
      "title": "AC not cooling",
      "description": "Guest reports AC blowing warm air",
      "status": "OPEN",
      "priority": "HIGH",
      "issueType": "HVAC",
      "roomName": "Room 102",
      "roomNumber": "102",
      "roomExternalId": "102",
      "assignedToName": "John Doe",
      "createdAt": 1739000000000,
      "updatedAt": 1739000000000
    }
  ]
}
GET /properties/:propId/work-orders/:woId Get work order detail workorders.read expand_more

Returns full details for a single work order, including timing data (startedAt, completedAt, hoursSpent).

PATCH /properties/:propId/work-orders/:woId Update work order workorders.write expand_more

Update work order status, priority, or add completion details. Triggers workorder.status_changed or workorder.completed webhooks.

Request Body

FieldTypeDescription
statusstringNew status
prioritystringNew priority
completionNotesstringNotes on what was done (typically on COMPLETED)
hoursSpentnumberHours spent on the work
info Completing a work order (COMPLETED or VERIFIED) automatically sets the associated room back to READY.

Response

{
  "id": "uuid",
  "woNumber": 42,
  "status": "COMPLETED",
  "priority": "HIGH",
  "updatedAt": 1739500000000
}

Webhooks

Receive real-time notifications when events happen in PropertyPlex. Configure webhooks in System Admin → Integrations.

Events

EventDescription
room.status_changedRoom status updated (e.g., TURNING → READY)
workorder.createdNew work order created
workorder.status_changedWork order status updated
workorder.completedWork order marked as completed
reservation.createdNew reservation created
reservation.updatedReservation details changed
reservation.cancelledReservation cancelled or deleted

Payload Structure

All webhook deliveries use POST with the following headers:

HeaderDescription
Content-Typeapplication/json
X-PPX-EventEvent type (e.g., room.status_changed)
X-PPX-TimestampUnix timestamp in milliseconds
X-PPX-SignatureHMAC-SHA256 signature for verification

Request body:

{
  "event": "room.status_changed",
  "timestamp": 1739567890123,
  "data": {
    "roomId": "uuid",
    "externalId": "102",
    "oldStatus": "TURNING",
    "newStatus": "READY",
    "guestName": null
  }
}

Verifying Signatures

Always verify the X-PPX-Signature header to ensure the webhook is authentic. The signature is computed as sha256=HMAC-SHA256(raw_body, webhook_secret).

Node.js
Python
const crypto = require('crypto');

function verifyWebhook(rawBody, signature, secret) {
  const hmac = crypto.createHmac('sha256', secret);
  hmac.update(rawBody);
  const expected = `sha256=${hmac.digest('hex')}`;
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// Express middleware
app.post('/webhook', (req, res) => {
  const sig = req.headers['x-ppx-signature'];
  if (!verifyWebhook(JSON.stringify(req.body), sig, WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  const event = req.headers['x-ppx-event'];
  console.log(`Received: ${event}`, req.body.data);
  res.status(200).send('OK');
});
import hmac, hashlib

def verify_webhook(raw_body, signature, secret):
    expected = 'sha256=' + hmac.new(
        secret.encode(), raw_body.encode(), hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

# Flask example
@app.route('/webhook', methods=['POST'])
def handle_webhook():
    sig = request.headers.get('X-PPX-Signature')
    if not verify_webhook(request.data.decode(), sig, WEBHOOK_SECRET):
        return 'Invalid signature', 401

    event = request.headers.get('X-PPX-Event')
    data = request.json['data']
    print(f"Received: {event}", data)
    return 'OK', 200
warning Failure handling: Webhooks that fail 10 consecutive times are automatically disabled. Your endpoint must respond within 5 seconds with a 2xx status code.

PMS Connectors

PropertyPlex includes built-in connectors for popular Hotel Management Systems. Instead of building your own integration, you can connect directly from System Admin > Integrations.

cloud
Cloudbeds
apartment
Mews
villa
Guesty
domain
OPERA Cloud

View all connector setup guides →

Error Handling

All errors return a JSON object with an error field:

{
  "error": "Description of what went wrong"
}

HTTP Status Codes

CodeMeaning
200Success
201Created (new resource)
400Bad request — invalid or missing parameters
401Unauthorized — missing or invalid API key
403Forbidden — insufficient permissions or wrong plan
404Not found — resource doesn't exist
429Too many requests — rate limit exceeded
500Server error

Plan Upgrade Required

If your tenant is on the Starter plan, API requests return:

{
  "error": "Integration API requires Professional or Enterprise plan",
  "code": "PLAN_UPGRADE_REQUIRED"
}

Code Examples

Room Sync (Node.js)

const API_KEY = 'ppx_your_api_key';
const BASE = 'https://your-server.com/api/v1/integration';
const PROP_ID = 'your-property-uuid';

// Sync room inventory
const res = await fetch(`${BASE}/properties/${PROP_ID}/rooms/sync`, {
  method: 'POST',
  headers: {
    'X-API-Key': API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    rooms: [
      {
        externalId: '101',
        name: 'Room 101',
        roomNumber: '101',
        roomType: 'STANDARD',
        floor: 1,
        bedType: 'KING',
        bedCount: 1,
        maxOccupancy: 2,
        amenities: ['wifi', 'minibar', 'safe'],
      },
      {
        externalId: '102',
        name: 'Room 102',
        roomNumber: '102',
        roomType: 'STANDARD',
        floor: 1,
        bedType: 'QUEEN',
        bedCount: 2,
        maxOccupancy: 4,
      },
    ],
  }),
});

const data = await res.json();
console.log(data);
// { message: 'Room sync complete', created: 2, updated: 0, removed: 0 }

Check-in Flow (Python)

import requests

API_KEY = 'ppx_your_api_key'
BASE = 'https://your-server.com/api/v1/integration'
PROP_ID = 'your-property-uuid'

headers = {
    'X-API-Key': API_KEY,
    'Content-Type': 'application/json',
}

# 1. Create reservation
res = requests.post(f'{BASE}/properties/{PROP_ID}/reservations', headers=headers, json={
    'externalId': 'RES-5001',
    'guestName': 'Alice Chen',
    'checkInDate': 1740000000000,
    'checkOutDate': 1740200000000,
    'roomId': 'room-uuid',
    'confirmationCode': 'CONF-1234',
    'specialRequests': 'Hypoallergenic pillows',
})
reservation_id = res.json()['id']

# 2. Check in (links reservation, imports special requests)
res = requests.post(f'{BASE}/properties/{PROP_ID}/rooms/room-uuid/check-in', headers=headers, json={
    'guestName': 'Alice Chen',
    'guestCheckoutDate': 1740200000000,
    'reservationId': reservation_id,
})
print(res.json())
# { message: 'Check-in successful', status: 'OCCUPIED', ... }

# 3. Check out (triggers housekeeping)
res = requests.post(f'{BASE}/properties/{PROP_ID}/rooms/room-uuid/check-out', headers=headers)
print(res.json())
# { message: 'Check-out successful', status: 'TURNING' }

Report Maintenance Issue (cURL)

curl -X POST \
  -H "X-API-Key: ppx_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "AC not cooling in Room 102",
    "description": "Guest reports AC unit blowing warm air",
    "roomId": "room-uuid",
    "priority": "HIGH",
    "issueType": "HVAC"
  }' \
  https://your-server.com/api/v1/integration/properties/PROP_ID/work-orders

PropertyPlex Integration API — Need help? Contact your PropertyPlex administrator.