Skip to content

Hotel API (PMS)

The Hotel API is a two-way bridge between an APRO point-of-sale and any Property Management System (PMS). The PMS pushes reservations into the POS so that guests can charge to their room; the POS exposes those charges back to the PMS so they land on the final hotel invoice.

Typical flow

PMS APRO POS
│ 1. POST /pms/reservations ─────────▶ │ (upsert guest rooms)
│ │
│ │ guests charge to room…
│ │
│ 2. GET /pms/charges?hotelId=… ─────▶│
│ ◀─── charges (and optionally receipts) │
│ │
│ 3. POST /pms/confirm-charges ───────▶│ (ack; won't be resent)
│ │
│ 4. DELETE /pms/reservations?… ───────▶│ (on check-out / close-out)

Step 2 is the PMS’s polling loop. Steps 1 and 4 are driven by events in the PMS. Step 3 must always follow a successful poll — otherwise the same charges will be returned again on the next poll.

1. Upsert reservations

Push the current list of active reservations into the POS. Keys are hotelId + Id + guestId; existing rows are updated in place.

POST https://cloudgateway.apro.at/hotelservice/api/v1/pms/reservations

Request body

FieldTypeRequiredDescription
reservationsReservation[]✅The reservations to upsert. May be empty together with deleteOthers: true (checks out every reservation).
deleteOthersbool—Default false. true = every reservation the POS holds for this hotel that is not in reservations is checked out. Without this flag nothing is ever checked out by this call.
hotelIdstring(✅)PMS tenant / property ID. Required when reservations is empty; otherwise the hotelId of the first reservation is used.

Reservation

FieldTypeRequiredMax. lengthDescription
Idstring✅40PMS-side reservation ID.
hotelIdstring✅—PMS tenant / property ID.
guestIdstring✅8PMS-side guest ID.
roomNrstring✅15Assigned room number.
titlestring—20Mr., Mrs., etc.
firstNamestring—100
lastNamestring—100Shown at the POS with at most 40 characters.
arrivalISO 8601✅Check-in timestamp.
departureISO 8601✅Check-out timestamp.
nrOfIndividualsinteger—Guest count, used for covers / buffet rules.
addressAddress—Postal address.
contactContact—Contact data.
additionalInfostring—1000Free text shown with the guest.
additionalInfoJsonstring—1000Free key/value data as a JSON string.
identifiersIdentifier[]—Keycard UIDs, NFC tags, room tablets etc. — any token the POS may receive when a guest wants to charge to a room.

Address

FieldTypeMax. length
streetstring100
postalCodestring50
citystring100
countrystring50

Contact

FieldTypeMax. length
salutationstring50 (not truncated, see below)
phoneNumberstring20
mobilePhoneNumberstring20
faxNumberstring20
mailAddressstring100
birthdayISO 8601

Identifier

FieldTypeMax. lengthDescription
valuestring40The identifier value (keycard UID, token). Only the first identifier is used.

Example request

POST /hotelservice/api/v1/pms/reservations
{
"reservations": [
{
"Id": "Aabc123",
"hotelId": "1",
"guestId": "Abc123",
"roomNr": "Abc123",
"title": "Mr.",
"firstName": "Max",
"lastName": "Mustermann",
"arrival": "2026-04-02T14:00:00",
"departure": "2026-04-04T14:00:00",
"nrOfIndividuals": 1,
"identifiers": [{ "value": "keycard-uid" }]
}
]
}

Download: hotel-reservations-upsert.json

Example response

200 OK
{
"newReservationsCount": 0,
"checkedOutReservationsCount": 0,
"updatedReservationsCount": 1,
"state": 1,
"isSuccessfull": true,
"errorCode": 0,
"message": null,
"context": null
}

Download: hotel-reservations-response.json

Checking out via the list (deleteOthers)

Reservations are only checked out by this call when deleteOthers is true. Then every reservation the POS holds for the hotel that is not in the payload is removed, and checkedOutReservationsCount reports how many. To check out the last remaining guests, send an empty list:

POST /hotelservice/api/v1/pms/reservations · check out everyone
{
"hotelId": "1",
"deleteOthers": true,
"reservations": []
}

2. Poll charges

Fetch everything booked against rooms since the last confirmed poll.

GET https://cloudgateway.apro.at/hotelservice/api/v1/pms/charges
?hotelId=1
&excludeAlreadyExported=true

Query parameters

NameTypeRequiredDescription
hotelIdstring✅PMS tenant / property ID.
excludeAlreadyExportedbool—true = only return items not yet confirmed (the usual polling mode). false = re-fetch everything in the configured window.

Response shape

Each item has a chargeType that controls which fields are populated:

chargeTypeMeaningpayments present?
ChargeAn open line booked to the room (guest hasn’t paid yet — POS is posting to the room folio).no
ReceiptA closed receipt (guest settled at the POS; PMS may ignore or reconcile).yes

Top-level envelope:

FieldTypeDescription
page.limitintegerPage size applied.
page.offsetintegerOffset into the backlog.
page.totalintegerItems still unconfirmed in total.
dataItem[]Charges and receipts.

Item

FieldTypeDescription
chargeType`“Charge""Receipt”`
bookDateISO 8601When the line was booked in the POS.
receiptNrintegerPOS receipt number.
userIdintegerOperator / waiter ID.
userNamestringOperator name.
outletintegerOutlet ID (e.g. 1 = bar, 2 = restaurant).
tableNrintegerTable number.
tablestringTable label.
totalAmountdecimalGross total of the line.
tipdecimalIncluded tip.
businesscaseIdGUIDAPRO business case ID.
transactionIdGUIDAPRO transaction ID — send this value in transactionIds to confirm.
reservationIdstring | nullPMS reservation ID, for Charge items.
guestIdstring | nullPMS guest ID.
roomNumberstring | nullRoom number.
chargesLineItem[]Individual article rows.
paymentsPayment[]Only on Receipt rows.

LineItem

FieldTypeDescription
articleNrintegerProduct number.
articleNamestringProduct name.
productgroupNrintegerCategory (Sparte) number.
productgroupNamestringCategory name.
quantitydecimalQuantity.
unitPricedecimalGross unit price.
vatPercentdecimalApplied VAT rate.

Example response

200 OK · charge + two receipts
{
"page": { "limit": 3, "offset": 0, "total": 8096 },
"data": [
{
"chargeType": "Charge",
"bookDate": "2026-04-15T09:15:04.95",
"receiptNr": 90,
"userId": 20,
"userName": "Johannes",
"outlet": 1,
"tableNr": 2,
"table": "Bar 2",
"totalAmount": 9.00,
"tip": 0.30,
"businesscaseId": "253c225f-7c4a-4d3f-80cd-63de4dbfca9e",
"transactionId": "69f94b1a-3453-7017-8ddb-94e2175d0365",
"reservationId": " 15631",
"guestId": " 6158",
"roomNumber": " 414",
"charges": [
{ "articleNr": 129, "articleName": "Zipfer 0,2", "productgroupName": "Bier", "quantity": 1.00, "unitPrice": 1.10, "vatPercent": 20.00 },
{ "articleNr": 213, "articleName": "Apfel ltg 0,5", "productgroupName": "Flaschen AF", "quantity": 1.00, "unitPrice": 2.70, "vatPercent": 20.00 },
{ "articleNr": 1626, "articleName": "Würstel Pommes", "productgroupName": "Hauptspeise", "quantity": 1.00, "unitPrice": 4.90, "vatPercent": 10.00 }
]
}
]
}

Download the full sample (Charge + two Receipts): hotel-charges-response.json

3. Confirm charges

Acknowledge the list you’ve successfully ingested. Once confirmed, the POS won’t return those transactions on a subsequent poll with excludeAlreadyExported=true.

POST https://cloudgateway.apro.at/hotelservice/api/v1/pms/confirm-charges

Request body

FieldTypeRequiredDescription
hotelIdstring✅PMS tenant / property ID.
transactionIdsGUID[]✅transactionId values from the previous /charges response.
POST /hotelservice/api/v1/pms/confirm-charges
{
"hotelId": "1",
"transactionIds": [
"69F94B1A-3453-7017-8DDB-94E2175D0365",
"155FFA87-DC19-EB1F-3DD8-540EF62A115D"
]
}

Download: hotel-confirm-charges.json

4. Remove a reservation

Explicit removal — useful at check-out or when the guest should no longer be able to charge to the room.

DELETE https://cloudgateway.apro.at/hotelservice/api/v1/pms/reservations
?hotelId=1
&reservationId=Abc123
&guestId=Abc123

All three query parameters are required. Either the targeted reservation is removed, or the call is a no-op if it doesn’t exist.

Idempotency & retries

  • POST /pms/reservations is idempotent on { hotelId, Id, guestId }. Re-sending the same payload converges to the same state.
  • POST /pms/confirm-charges is idempotent on transactionIds. Re-confirming an already-confirmed ID is a no-op.
  • Poll → confirm must be transactional in your own system: write the charges to your ledger first, then confirm. If the confirm fails, the same rows are returned next poll.

Errors

See Authentication → Error responses. On business-logic failures the response shape is:

{
"isSuccessfull": false,
"errorCode": 42,
"message": "No reservation found for guestId 'Abc123'.",
"context": { "guestId": "Abc123" }
}