PropertyPlex Integration API
Connect your Property Management System (PMS) to PropertyPlex for real-time room status, reservations, and work order management.
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.
Quick Start
- Create an API key in System Admin → Integrations
- Copy the key (shown once) — format:
ppx_a1b2c3d4e5f6... - Include it in all requests via the
X-API-Keyheader - Start with
GET /properties/:propId/roomsto 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:
| Permission | Description |
|---|---|
rooms.read | Read room data and status |
rooms.write | Update room status and guest info |
rooms.sync | Sync full room inventory from PMS |
workorders.read | Read work orders |
workorders.create | Create work orders |
workorders.write | Update work order status |
reservations.read | Read reservation data |
reservations.write | Create 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:
| Header | Description |
|---|---|
X-RateLimit-Limit | Requests allowed per minute |
X-RateLimit-Remaining | Requests 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
message field explaining the redirect.
Returns all rooms for a property with current status, guest info, and open work order count.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
status | string | Filter by status: READY, OCCUPIED, TURNING, INSPECTION, MAINTENANCE, OUT_OF_ORDER |
floor | integer | Filter by floor number |
roomType | string | Filter 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
Returns detailed information for a single room, including open work orders.
Response
Same fields as list endpoint, plus:
| Field | Type | Description |
|---|---|---|
guestNotes | string | Notes about the current guest |
openWorkOrders | array | List of open work order objects (id, title, status, priority, etc.) |
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.
externalId not in your payload will be marked OUT_OF_ORDER.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
rooms | array | Required | Array of room objects |
rooms[].externalId | string | Required | Your system's unique room ID |
rooms[].name | string | Room display name (e.g., "Room 102") | |
rooms[].roomNumber | string | Room number | |
rooms[].roomType | string | STANDARD, SUITE, COTTAGE, CABIN, VILLA | |
rooms[].floor | integer | Floor number | |
rooms[].maxOccupancy | integer | Maximum guest capacity | |
rooms[].bedType | string | KING, QUEEN, DOUBLE, TWIN, etc. | |
rooms[].bedCount | integer | Number of beds | |
rooms[].amenities | array | List of amenity strings (e.g., ["wifi", "minibar"]) | |
rooms[].description | string | Room 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
Update a room's status and optionally set guest info. Triggers room.status_changed webhook.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
status | string | Required | READY, OCCUPIED, TURNING, INSPECTION, MAINTENANCE, OUT_OF_ORDER |
guestName | string | Guest name (typically set with OCCUPIED) | |
guestCheckoutDate | integer | Checkout date as Unix timestamp (ms) | |
guestNotes | string | Notes 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)"
}
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
| Field | Type | Required | Description |
|---|---|---|---|
guestName | string | Required | Name of the guest |
guestCheckoutDate | integer | Expected checkout as Unix timestamp (ms) | |
guestNotes | string | Additional notes | |
reservationId | string | Link 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 }
}
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
Create a new reservation. If externalId is provided and already exists, the existing reservation is updated (upsert).
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
guestName | string | Required | Guest name |
checkInDate | integer | Required | Check-in date as Unix timestamp (ms) |
checkOutDate | integer | Required | Check-out date as Unix timestamp (ms) |
externalId | string | Your system's reservation ID (enables upsert) | |
confirmationCode | string | Booking confirmation number | |
guestEmail | string | Guest email | |
guestPhone | string | Guest phone | |
roomId | string | PropertyPlex room UUID to assign | |
adultsCount | integer | Number of adults | |
childrenCount | integer | Number of children | |
rateAmount | number | Nightly rate amount | |
rateCurrency | string | Currency code (default: USD) | |
channel | string | Booking channel (e.g., "Booking.com", "Direct") | |
specialRequests | string | Guest special requests (shown to housekeeping) | |
vipStatus | string | VIP level (e.g., "VIP", "VVIP", "LOYALTY_GOLD") | |
status | string | Default: CONFIRMED | |
notes | string | Internal notes | |
source | string | Source system identifier |
Response
{
"message": "Reservation created",
"id": "uuid",
"externalId": "RES-12345"
}
List reservations with optional filters.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
status | string | Filter by reservation status |
fromDate | integer | Only reservations checking out on or after this date (ms) |
toDate | integer | Only reservations checking in on or before this date (ms) |
limit | integer | Max 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
}
]
}
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"
}
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
Priorities
Create a maintenance work order. Triggers workorder.created webhook and sends push notifications to property users.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
title | string | Required | Work order title |
description | string | Detailed description | |
roomId | string | PropertyPlex room UUID | |
priority | string | LOW, NORMAL (default), HIGH, URGENT | |
issueType | string | HVAC, PLUMBING, ELECTRICAL, APPLIANCE, FURNITURE, TV_INTERNET, HOUSEKEEPING, SAFETY, OTHER | |
dueDate | integer | Due date as Unix timestamp (ms) |
Response
{
"message": "Work order created",
"id": "uuid",
"woNumber": 42,
"status": "OPEN",
"priority": "HIGH"
}
Query Parameters
| Parameter | Type | Description |
|---|---|---|
status | string | Comma-separated statuses (e.g., OPEN,IN_PROGRESS) |
priority | string | Filter by priority |
roomId | string | Filter by room UUID |
limit | integer | Max 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
}
]
}
Returns full details for a single work order, including timing data (startedAt, completedAt, hoursSpent).
Update work order status, priority, or add completion details. Triggers workorder.status_changed or workorder.completed webhooks.
Request Body
| Field | Type | Description |
|---|---|---|
status | string | New status |
priority | string | New priority |
completionNotes | string | Notes on what was done (typically on COMPLETED) |
hoursSpent | number | Hours spent on the work |
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
| Event | Description |
|---|---|
room.status_changed | Room status updated (e.g., TURNING → READY) |
workorder.created | New work order created |
workorder.status_changed | Work order status updated |
workorder.completed | Work order marked as completed |
reservation.created | New reservation created |
reservation.updated | Reservation details changed |
reservation.cancelled | Reservation cancelled or deleted |
Payload Structure
All webhook deliveries use POST with the following headers:
| Header | Description |
|---|---|
Content-Type | application/json |
X-PPX-Event | Event type (e.g., room.status_changed) |
X-PPX-Timestamp | Unix timestamp in milliseconds |
X-PPX-Signature | HMAC-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).
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
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.
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
| Code | Meaning |
|---|---|
200 | Success |
201 | Created (new resource) |
400 | Bad request — invalid or missing parameters |
401 | Unauthorized — missing or invalid API key |
403 | Forbidden — insufficient permissions or wrong plan |
404 | Not found — resource doesn't exist |
429 | Too many requests — rate limit exceeded |
500 | Server 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.