Update Availability
System Integration: PMS_API & OTA
This documentation explains the complete flow of the inventory update process (allotment), starting from the receipt of API instructions until the data is successfully stored in the delivery queue for OTA platforms.
Stage 1: Initialization & Data Injection
Repository: pms_api
File: bnl/bnl_api.php
Function: update_availability
The process begins when the system receives an availability update request.
Process Flow
Payload Receipt
The system receives a JSON request from the PMS API with a structure similar to the following:
{
"action": "update_availability",
"property_id": "57",
"echo_token": "7173eba4058589a6cfc216c67fl",
"values": [
{
"availability": 0,
"room_type_id": "350",
"date_from": "2026-05-23",
"date_to": "2026-05-23"
}
]
}
The request is processed through the function:
update_availability($reqjson)
The system then reads the availability value from the payload and stores it in a variable:
$final_alot
The system will save the internal calculations to the database using:
$this->utlFunc->updateDbPms_pdo(...)
Output
The final calculated values are stored in an array:
$finalUpdateArr
Formation of Final Update Array ($finalUpdateArr)
After the payload is validated, the system forms an internal data structure called:
$finalUpdateArr
This array serves as the primary payload for the subsequent OTA distribution process.
Example data structure:
$finalUpdateArr = [
[
'roomid' => '350',
'rateid' => '1234',
'keys' => [
'avail' => 0,
'minstay' => '',
'stopsell' => '',
'cta' => '',
'price' => ''
],
'date_xml_array' => [
'2026-05-23'
]
]
];
Field Descriptions:
| Field | Description |
|---|---|
roomid | Internal room ID |
rateid | Default rate plan ID of the room |
keys.avail | Availability value to be sent to OTA |
keys.minstay | Minimum stay |
keys.stopsell | Stop sell status |
keys.cta | Close to arrival |
keys.price | Room price |
date_xml_array | List of update dates |
This data is then sent to the XML generator process using:
$this->utlFunc->post_xmlPms_pdo(
$this->pms_name,
$this->property_id,
$finalUpdateArr,
[],
2,
1
);
Error Validation Before Distribution
Before the data is sent to the OTA layer, the system performs error validation:
if (preg_match('/errors/is', $returnResp)) {
header('HTTP/1.1 400 Bad Request');
}
If errors occur during request parsing or data validation, the system returns an HTTP 400 Bad Request.
If $finalUpdateArr is successfully formed and contains data, the system continues the distribution process:
if(is_array($finalUpdateArr) && count($finalUpdateArr)>0){
$this->utlFunc->post_xmlPms_pdo(...);
}
The post_xmlPms_pdo() function is responsible for converting internal data into an OTA XML request before passing it to the ota repository.
Stage 2: XML Payload Formation
Repository: pms_api
File: common_files/ota_utilities.class.php
Function: post_xmlPms_pdo()
Once the allotment value is determined, the data is converted into XML format so it can be processed by the OTA distribution engine.
Process Flow
XML Construction
The system builds an XML string containing:
- Property information
- Room ID
- Allotment attribute (
Alot) - Rate plan information
- Restriction data
Internal Transmission
The generated XML is sent via cURL to the endpoint:
$this->ota_base_url."/common_files/createOtaUpdateRequest.php?source=$sourse&property_id=$propertyId"
This endpoint is located in the ota repository.
Stage 3: OTA Request Generation, Distribution Terminal & Routing
Repository: ota
Files:
common_files/createOtaUpdateRequest.phputility/ota_update.phputility/ota_update.class.php
At this stage, the data enters the central distribution layer to be translated, mapped, and directed to various OTA channels.
Process Flow
Generate OTA Request (createOtaUpdateRequest.php)
The file common_files/createOtaUpdateRequest.php is the initial entry point on the ota repository side, responsible for building the OTA update request based on the inventory data received from pms_api.
Initialization & Authentication
The system performs:
- Initialization of the
createOtaUpdateRequestobject - Utility helper setup (
utilfunction) - cURL connection setup
- Authentication token validation
Furthermore, all incoming requests are recorded using a logging mechanism:
$createOtaUpdateRequest->utlFunc->appendLog(
'Request',
json_encode($_REQUEST)
);
Reading Request Data
The system reads the main data from the request payload:
property_idinput_arrayota_ids- Other source parameters
The input_array data contains inventory update details such as:
- availability
- price
- minimum stay
- stop sell
- CTA / CTD
- inclusion
The JSON payload is then converted into an internal array using:
$inputArray = json_decode($inputArrayJson, true);
Fetching Active OTA Channels
If ota_ids are not sent in the request, the system automatically fetches the list of active OTAs from the table:
external_active_status
The query used is:
SELECT ota_id_active
FROM external_active_status
WHERE property_id = '$propertyId'
This process ensures updates are only sent to active OTA channels.
Fetching Rate Plan Details
The system calls the function:
getRtDetails($inputArray)
This function retrieves rate plan and room configuration data from the tables:
RoomsRate_Plans
The information retrieved includes:
- default rate
- minimum nights
- inclusion
- max nights
- cut off nights
- last minute configuration
Calculation of Dynamic Rate & Restriction
For every date (date_xml_array), the system determines:
- Price (
price) - Minimum stay (
minstay) - Inclusion
- CTA / CTD
- stop sell
- allotment
If price or minimum stay is not sent, the system automatically uses the default configuration based on:
- day of the week (Mon-Sun)
- last minute configuration
- roll-in period
Tax & Additional Charges Calculation
If the property has an OTA tax configuration (allowota_tax = 1), the system automatically adds:
- tax percentage
- fixed additional charge
based on the table:
Additional_charges
Generate OTA XML Request
Once all data has been processed, the system builds the XML request using:
$xml = new SimpleXMLElement('<request></request>');
Each date will produce a <daterange> node containing attributes such as:
dayrateplan_idpriceAlotMinstopsellctactdmaxnightscutnights
Example XML generated for the payload above:
<request>
<propertyid>57</propertyid>
<otaids>...</otaids>
<callingLoc>external</callingLoc>
<daterange
day="2026-05-23"
rateplan_id="624"
price=""
Alot="0"
Min=""
stopsell=""
inclusion=""
cta=""
ctd=""
maxnights=""
cutnights=""
></daterange>
</request>
Note: The XML only contains rate plan 624 (the default rate plan for room 350). Other rate plans within the same room (4825, 69796) are not yet present — they will be added by
addRestXml()in the next stage.
Build OTA Update URL
The system then constructs the destination endpoint to:
utility/ota_update.php
Distribution Terminal (ota_update.php)
The file utility/ota_update.php receives the XML request generated by createOtaUpdateRequest.php.
Bootstrap & Initialization
The system performs:
- Utility class load
ota_update.class.phpload- cURL setup
- Logging setup
The request IP is also recorded for auditing and tracing purposes:
$request_ip = filter_input(INPUT_SERVER, 'REMOTE_ADDR');
Initial Validation (startInitial())
The startInitial() function is the primary entry point called first when ota_update.php receives a request.
function startInitial()
{
$this->current_Time();
$this->set_input();
return 1;
}
This function is responsible for:
- Recording the submission timestamp via
current_Time() - Delegating the entire initialization, parsing, and data preparation process to
set_input()
Parsing & Setup (set_input())
set_input() is the core function that handles the entire parsing, validation, and data preparation process before the flatfile is created.
Execution sequence inside set_input():
1. Reading parameters from the request
$this->priceflag = (int)$_REQUEST['priceflag'];
$this->alotflag = (int)$_REQUEST['alotflag'];
$this->alotflagBuff = (int)$_REQUEST['alotflag'];
$this->stopsellflag = (int)$_REQUEST['stopsellflag'];
$this->minflag = (int)$_REQUEST['minflag'];
// ...
if(isset($_REQUEST['source'])){
$this->callingLocBuffer = $_REQUEST['source']; // PMS name, e.g.: gustodian_json
}
2. Checksum validation
if ($_REQUEST['checksum'] != '1') {
$this->util_func->appendLog('set_input', "checksum value is not 1 so kill the instance
");
$this->util_func->saveOtaUpdateLog($_REQUEST['propertyid']);
exit;
}
If the checksum is invalid, the process is terminated.
3. Storing original payload in $this->reqLog
$this->reqLog = $_POST['xml']; // original XML payload stored here
$this->buffpostxml = $_POST['xml'];
$this->reqLog is the source of truth for the Alot value from the original payload. This variable is used later in addRestXml() to ensure the Alot value sent to the OTA matches what the PMS requested, rather than the value from the database.
4. Property status validation
$this->check_Enable();
if($this->propertyStatus == 0) {
echo "Flatfile could not generate, Due to inactive property";
exit;
}
5. Completing XML with other rate plans via addRestXml()
$_POST['xml'] = $this->addRestXml();
This is a critical step. addRestXml() is called before arraymorefilter(), so $this->reqLog is already available and contains the original payload with the correct Alot value.
addRestXml() is responsible for detecting other rate plans in the same room that are missing from the payload, then completing them with data from complete_bookdetails_phil — while simultaneously overriding the Alot value to match the original payload. Detailed process is explained in the next section.
6. Parsing XML into internal array
$arrr = $this->xml2array($inputString);
$filterdArray = $this->arraymorefilter($arrr); // tempUpdDataArr is populated here
$this->arrangeArray(); // finalUpdDataArr is populated here
$this->arrangeArray(1);
arraymorefilter() and arrangeArray() are called after addRestXml() is finished, so all rate plans — including those completed by addRestXml() — have entered tempUpdDataArr and finalUpdDataArr with the correct Alot values.
complete_bookdetails_phil Retrieves Alot from Database
Inside addRestXml(), for every rate plan not in the payload, the system hits complete_bookdetails_phil. This function calls complete_bookdetails(), which retrieves the Alot value from the database:
// complete_bookdetails() — Alot value taken from DB
$availability = $roomavailabilityqry->row('Rooms_Available_To'); // = 3 from DB
$roomcounts = $roomscount->row('rooms'); // = 0
$availability = $availability - $roomcounts; // = 3
$xmlStr .= "<daterange ... Alot="$availability" ...>"; // Alot = 3
After the response is received, other fields are cleared but Alot is not:
$current_string = preg_replace('/price\s*=\s*"(.*?)"/is', 'price=""', $current_string);
$current_string = preg_replace('/Min\s*=\s*"(.*?)"/is', 'Min=""', $current_string);
$current_string = preg_replace('/stopsell\s*=\s*"(.*?)"/is', 'stopsell=""', $current_string);
$current_string = preg_replace('/cta\s*=\s*"(.*?)"/is', 'cta=""', $current_string);
$current_string = preg_replace('/ctd\s*=\s*"(.*?)"/is', 'ctd=""', $current_string);
$current_string = preg_replace('/inclusion\s*=\s*"(.*?)"/is', 'inclusion=""',$current_string);
As a result, $daterange contains:
<daterange day="2026-05-23" rateplan_id="4825" Alot="3" price="" .../>
<daterange day="2026-05-23" rateplan_id="69796" Alot="3" price="" .../>
Alot Override in addRestXml()
After the while loop collects $daterange from complete_bookdetails_phil, the system retrieves the Alot value from $this->reqLog (original payload) and uses it to override all Alot values in $daterange.
This fix applies to all PMS.
while(preg_match('/(<daterange.*?)<\/daterange>/is', $current_string, $match)) {
$daterange .= trim($match[0]);
$current_string = $this->after($match[1], $current_string);
}
// Get alot from original payload XML (reqLog), not from DB
$payloadAlot = 0;
if (preg_match('/Alot\s*=\s*"(.*?)"/is', $this->reqLog, $alotMatch)) {
$payloadAlot = $alotMatch[1]; // take value from original payload
}
$daterange = preg_replace(
'/Alot\s*=\s*"(.*?)"/is',
'Alot="' . $payloadAlot . '"',
$daterange
);
$this->util_func->appendLog('addRestXml',
"{$this->callingLocBuffer} override Alot={$payloadAlot} in daterange
");
Why use $this->reqLog?
Because $this->reqLog already contains the original XML payload when addRestXml() is called, whereas $this->tempUpdDataArr is not yet populated (it is only populated after arraymorefilter() is called):
// Sequence in set_input():
$this->reqLog = $_POST['xml']; // original payload, already exists ✓
$_POST['xml'] = $this->addRestXml(); // reqLog is read here
$filterdArray = $this->arraymorefilter($arrr); // tempUpdDataArr populated here
Final Result
The final XML entering arraymorefilter():
<!-- From original payload (rate 624) -->
<daterange day="2026-05-23" rateplan_id="624" Alot="0" price="" .../>
<!-- From complete_bookdetails_phil, already overridden (rate 4825) -->
<daterange day="2026-05-23" rateplan_id="4825" Alot="0" price="" .../>
<!-- From complete_bookdetails_phil, already overridden (rate 69796) -->
<daterange day="2026-05-23" rateplan_id="69796" Alot="0" price="" .../>
Thus, tempUpdDataArr and finalUpdDataArr are populated correctly:
// tempUpdDataArr — all rate plans Alot = 0
$this->tempUpdDataArr[624]['2026-05-23'] = "##0##..."; // ✓
$this->tempUpdDataArr[4825]['2026-05-23'] = "##0##..."; // ✓
$this->tempUpdDataArr[69796]['2026-05-23'] = "##0##..."; // ✓
// finalUpdDataArr — all rate plans Alot = 0
$this->finalUpdDataArr["2026-05-23##2026-05-23"][624] = [{alot: 0}]; // ✓
$this->finalUpdDataArr["2026-05-23##2026-05-23"][4825] = [{alot: 0}]; // ✓
$this->finalUpdDataArr["2026-05-23##2026-05-23"][69796] = [{alot: 0}]; // ✓
Routing Update (sendUpdate())
The sendUpdate() function determines the type of update to be processed, such as:
- availability update
- price update
- restriction update
- reservation synchronization
Room Mapping Validation (otaMappedCheck)
The system validates that each internal room is correctly connected to an OTA room ID on each active channel.
This process ensures that no inventory data is sent to an invalid OTA mapping.
Logging & Audit Trail
The entire process is recorded using:
appendLog()
and in the final stage, the system saves logs using:
saveOtaUpdateLog($_REQUEST['propertyid']);
Stage 4: Final Queue Formation (Queueing)
Repository: ota
File: utility/ota_update.class.php
Function: flatfilecreation_new()
This stage is the final process before the data is sent to third-party OTA servers.
Process Flow
XML to JSON Conversion
The XML data resulting from distribution is validated and then converted into a final JSON structure stored in the variable:
$finalArr
Flatfile Formation (parseFlatfileData_new())
Since all rate plans are already in finalUpdDataArr with the correct Alot values, all rate plans enter the flatfile with values corresponding to the payload:
| Rate Plan | Alot in Flatfile |
|---|---|
| BAR (624) | 0 ✓ |
| ROOM ONLY (4825) | 0 ✓ |
| RO (69796) | 0 ✓ |
Queue Storage (process_queue)
To maintain system performance and avoid real-time direct transmission, data is first saved to the table:
process_queue
This queue will be processed asynchronously by OTA workers.
OTA Payload Formation
At this stage, the system also:
- Builds the final OTA payload
- Adjusts the request structure according to the channel
- Adds synchronization metadata
- Determines queue priority
Audit Trail (request_details_v2)
Request details are stored in the table:
request_details_v2
for the purposes of:
- troubleshooting
- technical audit
- resend process
- synchronization debugging
Stage 5: Confirmation & Alerting
Repository: ota
File: utility/ota_update.class.php
Function: sendAcknowledgeMent()
This stage is used to ensure the request was successfully received by the queue system and monitoring.
Process Flow
Queue Verification
The system calls the acknowledgment URL to ensure the synchronization request was successfully received by the queue processor.
Response Validation
The system validates the response from the queue server:
- success response
- empty response
- timeout
- invalid acknowledgment
If the response is invalid, the system will mark the process as a failed synchronization.
Slack Alerting (Early Warning System)
If a synchronization failure occurs, the system automatically sends a Slack notification containing:
- property details
- error message
- request information
- synchronization failure details
The main goal of this alerting is so the operations team can immediately investigate before an inventory mismatch occurs on the OTA.
Architecture Summary
| Stage | Main Activity | Repository | Primary File |
|---|---|---|---|
| Initialization | Receive Payload & Authentication | pms_api | bnl_api.php |
| Calculation | Inventory Calculation Logic | pms_api | bnl_api.php |
| Packaging | Convert Data to XML | pms_api | ota_utilities.class.php |
| Transformation | Generate OTA Request | ota | createOtaUpdateRequest.php |
| Transit | Receive & Route XML | ota | ota_update.php |
| Rate Plan Fix | Override Alot from Payload in addRestXml() | ota | ota_update.class.php |
| Queueing | Insert Data to process_queue | ota | ota_update.class.php |
| Monitoring | Slack Alerting & Acknowledgment | ota | ota_update.class.php |
High-Level Flow Diagram
API Payload (availability: 0, room: 350)
│
▼
update_availability()
(pms_api/bnl_api.php)
│ → Read availability from payload → $final_alot = 0
│ → Update internal DB (availability + booking)
│ → finalUpdateArr.avail = original payload (0)
│
▼
post_xmlPms_pdo()
(ota_utilities.class.php)
│ → Send finalUpdateArr via cURL
│
▼
createOtaUpdateRequest.php
│
├── Read Input Payload
├── Fetch OTA Active Channels
├── Fetch Rate Plan Details
├── Calculate Rate & Restriction
├── Generate XML: <daterange rateplan_id="624" Alot="0"/>
└── Send via cURL to ota_update.php
│
▼
ota_update.php → startInitial()
│ → current_Time()
│ → set_input()
│
▼
set_input()
│ → Read flags (alotflag, priceflag, etc.)
│ → Validate checksum
│ → reqLog = original XML payload (Alot="0") ← stored here
│ → Validate property status
│
├── addRestXml()
│ ├── Detect other rate plans (4825, 69796) not in payload
│ ├── Hit complete_bookdetails_phil → Alot = 3 (from DB)
│ ├── Override Alot = 0 (from reqLog = original payload)
│ └── Return final XML: all rate plans Alot = 0
│
├── arraymorefilter() → tempUpdDataArr all rate plans Alot = 0
└── arrangeArray() → finalUpdDataArr all rate plans Alot = 0
│
▼
sendUpdate() → otaMappedCheck()
│
▼
flatfilecreation_new()
│
├── parseFlatfileData_new()
│ ├── Rate 624: otaalot = 0
│ ├── Rate 4825: otaalot = 0
│ └── Rate 69796: otaalot = 0
├── Insert into process_queue
└── Save request_details_v2
│
▼
sendAcknowledgeMent()
│
├── Queue Confirmation
└── Slack Alerting if failed