beziworld / laravel-previo
Laravel client for the whole Previo hotel PMS API (XML, REST, POS, EQC): hotels, rates, availability, reservations, guests, rooms, billing and code lists.
Requires
- php: ^8.3
- ext-dom: *
- ext-json: *
- ext-libxml: *
- ext-xmlreader: *
- guzzlehttp/guzzle: ^7.8
- illuminate/contracts: ^12.0|^13.0
- illuminate/http: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.18
- orchestra/testbench: ^10.0|^11.0
- pestphp/pest: ^3.7|^4.0
- pestphp/pest-plugin-laravel: ^3.0|^4.0
- phpstan/phpstan-strict-rules: ^2.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-04 16:48:37 UTC
README
Previo hotel PMS client for Laravel covering the whole public Previo API: the XML API (/x1/),
the REST API (/rest/), the POS API (/pos/) and the EQC channel manager interface (/eqc1/), each function with a
dedicated, typed method:
- several Previo accounts (named connections, runtime credentials per tenant) and many hotels per account
- hotel profile, room kinds, rooms with housekeeping status, photos, amenities, translations, news and guest reviews
- pricelist, availability (daily and hourly), local taxes, discounts, services, stay packages, meal plans, price quotes
- reservations: search, detail, create, modify, change status, cancel, check-in with signature, door lock PIN codes
- reservation synchronisation of many hotels: streamed searches with flat memory use, incremental cursors, horizon
imports, per-hotel error isolation, Laravel events and a scheduler-ready
previo:sync-reservationscommand - guests: search, profile, create, update, delete, tags and categories; the hotel owning a guest card is resolved
- accounts, account items, cash desk payments, invoices and receipts (PDF), accounting exports
- code lists (countries, currencies, languages, segments, ZIP codes...), change polling (
System.getChanges2) - point of sale posting and EQC availability/rate updates and booking retrieval
- error documents turned into typed exceptions; safe retries for read-only calls, writes never retried
- generic
call(),rest()andposRequest()for anything Previo adds later
No ext-soap or XML library is needed: requests go through the Laravel HTTP client, so everything works with
Http::fake().
Installation
composer require beziworld/laravel-previo
php artisan vendor:publish --tag=previo-config # optional
PREVIO_API_KEY=your-api-key # from your Previo sales representative or info@previo.cz PREVIO_HOTEL_ID=123456 # hotId of the property PREVIO_LANGUAGE_ID= # optional: 1 Czech, 2 English, 3 German
API access is a paid Previo add-on; Previo can issue sandbox (test hotel) credentials on request. The API key is sent
as Authorization: ApiKey <key>. Accounts that still use the older login and password can set PREVIO_LOGIN and
PREVIO_PASSWORD instead; they are then sent inside each XML request (the REST API needs an API key).
Other settings: PREVIO_BASE_URL (default https://api.previo.app), PREVIO_TIMEOUT (30 s), PREVIO_RETRIES (2),
PREVIO_RETRY_DELAY (500 ms), PREVIO_TIMEZONE (Europe/Prague, used to read the local date-times Previo returns)
and PREVIO_APP_ID (application id sent to the POS API).
Heavy calls (reservation searches, System.getChanges2, payments, the XML guest search, reports and document exports)
use PREVIO_LONG_TIMEOUT (120 s) instead of PREVIO_TIMEOUT: the reservation search of a busy hotel returns several
megabytes and regularly takes longer than 30 seconds. PREVIO_CONNECT_TIMEOUT (10 s) limits the connection phase of
every call, so an unreachable API still fails fast.
Hotel, room kinds and rates
use Beziworld\Previo\Facades\Previo; $hotel = Previo::hotel(); // Hotel: name, address, phones, emails, checkInTime, latitude, stars... foreach (Previo::roomKinds() as $kind) { // list<RoomKind> $kind->id; // obkId $kind->name; $kind->capacity(); // beds + extra beds $kind->rooms; // list<Room>: id (objId), name } $rates = Previo::rates('2026-07-01', '2026-08-31', currencies: ['CZK', 'EUR'], ratePlanIds: [84855]); foreach ($rates->ratePlans as $plan) { foreach ($plan->seasons as $season) { // Season: from, to $price = $season->roomKind(94676)?->price('CZK', occupancy: 2); $price?->amount; // null when the hotel left the price empty } } Previo::ratePlans(); // list<RatePlan>: id, name, isBasePlan, nonRefundable, parentId
Without ratePlanIds Previo returns the base rate plan; without currencies the default currency of the hotel.
Availability
$availability = Previo::availability('2026-07-01', '2026-07-31', roomKindIds: [94676, 94681]); $plan = $availability[0]; // one entry per rate plan $plan->on('2026-07-10', 94676); // free rooms that day $plan->minimumFor(94676, '2026-07-10', '2026-07-14'); // free rooms for the whole stay
Previo limits one availability request to 365 days.
Reservations
use Beziworld\Previo\Data\ContactPerson; use Beziworld\Previo\Data\NewReservation; use Beziworld\Previo\Data\Reservation; use Beziworld\Previo\Data\RoomBooking; // Search: termType is overlap (default), created, check-in, check-out or modified $arrivals = Previo::reservations(today(), today(), termType: 'check-in', statuses: [Reservation::STATUS_CONFIRMED]); $changed = Previo::reservations(today()->subDay(), today(), termType: 'modified'); $found = Previo::reservations('2026-01-01', '2026-12-31', fulltext: 'Novák', limit: 50); foreach ($arrivals as $reservation) { // one entry per booked room $reservation->reservationId; // resId, shared by the rooms of a group booking $reservation->roomReservationId; // comId $reservation->voucher; $reservation->from; // CarbonImmutable, hotel local time $reservation->nights(); $reservation->roomKindName; ($reservation->guests[0] ?? null)?->fullName(); $reservation->isCancelled(); } $detail = Previo::reservation(54855023); // REST detail as an array // Create $created = Previo::createReservation(new NewReservation( from: '2026-07-10 14:00:00', to: '2026-07-14 10:00:00', rooms: [ new RoomBooking(roomKindId: 94676, guestCount: 2, guests: [ ['firstName' => 'Jan', 'lastName' => 'Novák', 'email' => 'jan@example.com', 'phone' => '+420600000000'], ]), ], statusId: Reservation::STATUS_CONFIRMED, voucher: 'WEB-1001', note: 'Booked on the website', totalPrice: 8400, currencyId: 1, contactPerson: new ContactPerson('Jan Novák', 'jan@example.com'), )); $resId = $created[0]->reservationId; // Modify, change status, cancel Previo::updateReservation($resId, ['term' => ['from' => '2026-07-10 14:00:00', 'to' => '2026-07-15 10:00:00']]); Previo::changeReservationStatus($resId, Reservation::STATUS_CONFIRMED); Previo::cancelRoomReservation($resId, roomReservationId: 71805339); // one room of a group Previo::cancelReservation($resId); // every room
Reservation writes are never retried automatically: after a timeout, search by voucher before sending again. Payment
card data is deliberately not supported by NewReservation.
Large searches: stream instead of loading
Hotel.searchReservations honours limit and offset only together with fulltext and does not report the number of
matches: any other search returns the whole matching set in one response. For a busy hotel and a long period that is
many megabytes, and turning it into arrays can exhaust a 128 MB worker. reservations() loads the whole set;
reservationsCursor() takes the same arguments and returns a LazyCollection that saves the response to a temporary
file while it downloads and converts one <reservation> at a time, so the memory use stays flat:
Previo::reservationsCursor(today()->subYear(), today()->addYear(), termType: 'overlap') ->each(function (Reservation $reservation): void { // store or queue one room reservation at a time }); // Any other read-only XML function with a large list can be streamed the same way foreach (Previo::streamCall('Hotel.getGuests', ['hotId' => 123], 'guest') as $guest) { /* array */ }
The request is sent when the collection is iterated (once per iteration); transient failures are retried before the first reservation is produced and the temporary file is removed when the iteration ends.
Multi-room reservations
One reservation (resId) with several rooms or services comes back as several room reservations (comId). Previo
attaches the guests to one component only; the others have an empty guest list, in the PMS as well. Reading guests
from a single component (for example the one a check-in link points to) therefore finds nobody although the
reservation has guests. Group the components and read the guests from the group:
use Beziworld\Previo\Data\ReservationGroup; foreach (ReservationGroup::fromReservations(Previo::reservations('2026-07-01', '2026-07-31')) as $group) { $group->reservationId; $group->primary(); // the component with the guests, else the first one $group->secondary(); // the other components $group->guests(); // guests of every component, each once $group->totalPrice(); // sum of the component prices without cancelled ones $group->from(); $group->to(); $group->currency(); $group->component(146138711); }
Statuses and change polling
Previo::reservationStatuses(); // list<ReservationStatus>: 1 option, 2 confirmed, 3 checked in, 7 cancelled... // Previo has no webhooks. Poll System.getChanges2 every 5 to 15 minutes; each call consumes the changes. foreach (Previo::changes(['getRates', 'getFreeCapacity']) as $change) { $change->hotelId; $change->type; // name of the function whose data changed, e.g. "getRates" } // At most 100 notices come back per call; drainChanges() calls again until a partial batch arrives. Previo::drainChanges(allHotels: true);
php artisan previo:poll-changes {connection?} {--type=*} drains the notices of every hotel of a connection and
dispatches a Beziworld\Previo\Events\ChangeDetected event (connection, change) for each of them.
Multiple accounts and hotels
One API key may give access to many hotels, and one application may use many Previo accounts. Accounts are configured
as named connections, in the same way as database connections; options outside connections are shared defaults:
// config/previo.php 'default' => env('PREVIO_CONNECTION', 'default'), 'connections' => [ 'default' => [ 'api_key' => env('PREVIO_API_KEY'), 'hotel_id' => env('PREVIO_HOTEL_ID'), 'language_id' => env('PREVIO_LANGUAGE_ID'), ], 'krakow' => [ 'api_key' => env('PREVIO_KRAKOW_API_KEY'), 'hotel_id' => env('PREVIO_KRAKOW_HOTEL_ID'), 'language_id' => 2, ], ],
Previo::hotel(); // default connection Previo::connection('krakow')->reservations(...); // another account // Credentials stored per tenant in the database, merged with the shared options $client = Previo::using([ 'api_key' => $tenant->previo_api_key, 'hotel_id' => $tenant->previo_hotel_id, ]); $client->availability('2026-07-01', '2026-07-31'); // Hotels of one account and of every configured connection Previo::availableHotels(); // list<AvailableHotel>: hotelId, name, connection Previo::allAvailableHotels(); // every connection, each hotel tagged with its connection name // Another hotel through the same credentials Previo::forHotel(730099)->reservations('2026-07-01', '2026-07-31'); Previo::forHotel(730099, connection: 'krakow')->rooms(); foreach (Previo::connection('krakow')->hotelClients() as $hotelId => $hotelClient) { // array<int, PrevioClient> $hotelClient->changes(); }
Language and time zone per hotel
Hotels of one account may need different languages (code lists, room kind names, REST X-Previo-Language-ID) or time
zones. Overrides inside a connection are applied by forHotel() (and by the synchronizer below); hotels without an
override use the options of the connection:
'connections' => [ 'default' => [ 'api_key' => env('PREVIO_API_KEY'), 'language_id' => 1, 'hotels' => [ 758015 => ['language_id' => 2, 'timezone' => 'Europe/Warsaw'], ], ], ],
A config file without connections (only top-level api_key, hotel_id...) keeps working as a single default
connection. Every method below is available on any client: Previo::connection('x')->billing()->payments() works the
same way as Previo::billing()->payments().
Synchronising many hotels
ReservationSynchronizer keeps a local copy of the reservations of every hotel of a connection. It walks the hotels
listed in the connection option sync_hotels (array or PREVIO_SYNC_HOTELS=123,456), or every hotel returned by
availableHotels(), streams each search (see above) and dispatches events:
ReservationFetched(connection, hotelId, typedReservation, mode) for every room reservation;HotelSyncCompleted(connection, hotelId, result) orHotelSyncFailed(connection, hotelId, exception, result) per hotel. A failing hotel is reported and skipped; the other hotels continue.
Modes:
| Mode | Term type | Period | Cursor |
|---|---|---|---|
| incremental | modified |
stored cursor of the hotel minus overlap_days until now plus overlap_days |
moved to the start of the run, only when the whole hotel succeeded |
| horizon | overlap |
today minus overlap_days until horizon_months ahead |
never moved |
| range | any | explicit from and to |
never moved |
The incremental mode catches every new booking, edit and status change cheaply; the horizon mode imports reservations
booked long in advance (a future period never matches modified). Without a stored cursor the incremental period
starts at today minus the overlap, so run the horizon mode once for the initial import.
// app/Listeners/StorePrevioReservation.php public function handle(ReservationFetched $event): void { StorePrevioReservation::dispatch($event->hotelId, $event->reservation->raw); // keep listeners light }
// routes/console.php use Illuminate\Support\Facades\Schedule; Schedule::command('previo:sync-reservations')->everyTenMinutes()->withoutOverlapping(); Schedule::command('previo:sync-reservations --mode=horizon')->dailyAt('03:30'); Schedule::command('previo:sync-reservations krakow')->everyTenMinutes(); // another connection Schedule::command('previo:poll-changes')->everyTenMinutes();
previo:sync-reservations {connection?} {--mode=incremental|horizon} {--from=} {--to=} {--term-type=} {--months=} {--hotel=*} prints a summary per hotel and exits with a failure code when any hotel failed. Runs of one connection
never overlap: a run that finds another one in progress (cache lock) ends without doing anything. In code:
use Beziworld\Previo\Sync\ReservationSynchronizer; $result = app(ReservationSynchronizer::class)->incremental('krakow'); app(ReservationSynchronizer::class)->horizon(months: 12, hotelIds: [758015]); app(ReservationSynchronizer::class)->range('2026-01-01', '2026-03-31', termType: 'created'); app(ReservationSynchronizer::class)->incremental(Previo::using($tenantCredentials)); // runtime credentials $result->successful(); $result->count(); $result->failed(); // list<HotelSyncResult>: hotelId, count, error, from, to
Options live under sync in config/previo.php: overlap_days (2), horizon_months (18), statuses (empty: every
status), cache_store and lock_seconds. Cursors are kept per connection and hotel in the cache by
CacheSyncStateStore; bind your own Beziworld\Previo\Sync\SyncStateStore (for example backed by a table) when the
cursor must survive a cache flush:
$this->app->singleton(SyncStateStore::class, DatabaseSyncStateStore::class);
Guests
$page = Previo::guests(lastName: 'Novák', limit: 50); // REST search; GuestPage: total, guests (max. 100 per page) $page = Previo::searchGuests(fulltext: 'Novák'); // XML search (name, e-mail and address) $guest = Previo::guest(237384154); // Guest: names, contact, address, gender, identity, nationalityIds, tagIds... $guest = Previo::createGuest(['firstName' => 'Jan', 'lastName' => 'Novák', 'email' => 'jan@example.com', 'address' => ['city' => 'Praha', 'couId' => 1], 'identity' => ['idNumber' => 'AB123', 'guiId' => 2]]); Previo::updateGuest($guest->id, ['phone' => '+420600000000', 'tags' => [1473418]]); Previo::deleteGuest($guest->id); Previo::guestTags(); // list<GuestTag> Previo::guestCategories(); // GuestCategories: default(), categories (adult, child...), discount model
A guest card belongs to the hotel it was created in. GET and PUT rest/guests/{id} answer 404 when the card is
addressed through another hotel of the same account, even when a reservation of that hotel lists the guest (a
returning guest who first stayed at another property). Resolve the owner first:
Previo::forHotel($reservationHotelId)->guestOwnerHotel($guestId); // ?int: current hotel first, then the others Previo::guestOwnerHotel($guestId, candidateHotelIds: [758015, 730099]); // without the availableHotels() call Previo::guest($guestId, resolveOwner: true); // or guestAnyHotel($guestId) Previo::updateGuest($guestId, $changes, resolveOwner: true);
Only a 404 moves on to the next hotel; any other failure (rejected key, outage, rate limit) is thrown, so it is never
mistaken for a missing card. The owner is remembered by the connection client and its hotel clients
(forgetGuestOwners() clears it); GuestNotFound is thrown when no hotel holds the card.
tags in createGuest() and updateGuest() is normalised: Previo accepts only a list of tag ids or a comma separated
id string and rejects anything else ("Tags must be an array tag IDs or string with IDs separated by commas"). Ids,
GuestTag objects, arrays with id or tagId, keyed arrays from forms and comma separated strings become a list of
ids; a value without any valid id is left out of the request.
Rooms, housekeeping and check-in
use Beziworld\Previo\Data\HotelRoom; foreach (Previo::rooms() as $room) { // list<HotelRoom> $room->name; $room->cleanStatusId; // HotelRoom::CLEAN_UNTIDY, CLEAN_TIDY, CLEAN_INSPECTION, CLEAN_CHECK_UP } Previo::setRoomCleanStatus(763548, HotelRoom::CLEAN_TIDY); Previo::premiseKinds(); // list<PremiseKind>: capacity, number of rooms, facilities Previo::objectKinds(); // list<RoomKind> including non-accommodation spaces Previo::hourlyAvailability('2026-07-01', '2026-07-02', [470597]); // hourly rented spaces, max. 72 hours Previo::updateCheckInGuest($resId, ['gueId' => 36491910, 'hasCompleteInfo' => true, /* identity, address... */]); Previo::uploadCheckInSignature($resId, guestId: 36491910, signature: $pngBase64); Previo::checkIn(roomReservationId: 65542463); // arrival today, a complete guest, tidy room Previo::cardKeys(65542463); // list<CardKey>: door lock PIN codes
Accounts, payments and documents
use Beziworld\Previo\Data\BillingDocument; use Beziworld\Previo\Data\NewAccountItem; use Beziworld\Previo\Data\Payment; $account = Previo::billing()->account($resId); // Account: total, paid, items (list<AccountItem>) $account->itemsTotal(); Previo::billing()->addAccountItems($comId, 'CZK', [ new NewAccountItem('Parking', price: 199.50, vatRate: 21, count: 2, segmentId: 8), ]); Previo::billing()->payments(limit: 50); // Page<Payment>: total, items Previo::billing()->addPayment(invoiceId: 31799225, price: 500, currencyId: 1, methodId: Payment::METHOD_CASH); $issued = Previo::billing()->payReservation($resId, BillingDocument::TYPE_INVOICE, Payment::METHOD_BANK_TRANSFER, withPdf: true); Previo::billing()->documents('2026-07-01', '2026-07-31', types: ['INV']); // list<BillingDocument> Previo::billing()->reservationDocuments($comId); $pdf = Previo::billing()->printDocument($issued->id); // binary PDF content $xml = Previo::billing()->exportDocuments('2026-07-01', '2026-07-31', 'pohoda'); Previo::billing()->gatewayPayment($comId)->isPaid();
Writes (account items, payments, guests) are never retried automatically.
Prices, services and content
Previo::pricing()->currencies(); // list<Currency> accepted by the hotel Previo::pricing()->fees(); // list<Fee>: local visitor tax, accommodation tax Previo::pricing()->cancellationPolicy()->conditionFor(hoursBeforeArrival: 48); Previo::pricing()->priceActions(); // list<PriceAction>: seasonal, first/last minute, long stay Previo::pricing()->services('2026-07-01', '2026-07-05', ['CZK']); // list<Service> (parking, breakfast...) Previo::pricing()->stayPackages(); // list<StayPackage> Previo::pricing()->mealPlans('2026-07-01', '2026-07-05')[0]->price('2026-07-02', mealTypeId: 2, roomKindId: 470625, guestCategoryId: 83014); Previo::pricing()->paymentMethods('2026-07-01', '2026-07-05'); Previo::pricing()->suggest([['from' => '2026-07-01', 'to' => '2026-07-05', 'roomKindId' => 61, 'guests' => [['guestCategoryId' => 167238, 'mealTypeId' => 2]]]]); // PriceSuggestion Previo::content()->messages(['en', 'de']); // translated texts Previo::content()->photogalleries(); // list<Photogallery>: cover(), photos Previo::content()->amenities(); // list<Amenity> Previo::content()->reviews(limit: 20); // list<GuestReview> Previo::content()->reviewSummary(); Previo::content()->news(); Previo::companies(); // partners: companies, travel agencies Previo::overviewReport('2026-07-01', '2026-07-31');
Code lists
Previo::system()->countries(); // list<Country> Previo::system()->currencies(); // list<Currency> Previo::system()->languages(); // list<Language> Previo::system()->segments(); // list<CodebookEntry>: account item segments Previo::system()->meals(); // meal types Previo::system()->zips(1); // list<ZipCode> of a country
Code lists change rarely; cache them in your application.
Point of sale (POS)
For restaurant and bar systems that post consumption to hotel accounts (set PREVIO_APP_ID when Previo assigned an
application id to your integration):
foreach (Previo::pos()->currentReservations() as $stay) { // guests in the hotel right now $stay->roomName; $stay->guests; // list<Guest> } Previo::pos()->addItems($stay->roomReservationId, currencyId: 1, items: [ new NewAccountItem('Beer 0.5 l', price: 59, vatRate: 21, count: 2, segmentId: 1, ownerType: 'guest', ownerId: 37880011), ]);
EQC (channel managers)
use Beziworld\Previo\Data\EqcRatePlanUpdate; use Beziworld\Previo\Data\EqcRoomTypeUpdate; Previo::eqc()->updateAvailabilityAndRates('2026-07-01', '2026-07-31', [ new EqcRoomTypeUpdate(roomTypeId: 61, availability: 3, ratePlans: [ new EqcRatePlanUpdate(ratePlanId: 84855, currency: 'CZK', perOccupancyRates: [1 => 1500, 2 => 1900], minLengthOfStay: 2), ]), ]); $pending = Previo::eqc()->bookings(); // pending bookings (not retried) $recent = Previo::eqc()->bookings(daysInPast: 1); // list<EqcBooking>
Any other function
Previo::call('Hotel.getMeals', ['hotId' => 123456], idempotent: true); // any XML function, raw array Previo::rest('GET', 'calendar/meals', ['filterFrom' => '2026-07-01', 'filterTo' => '2026-07-31']); // any REST route Previo::posRequest('GET', 'codebooks/segments', hotelScoped: false); // any POS route
call() does not add hotId for you. Pass idempotent: true only for read-only functions; those are retried.
API coverage
Every function of the official Previo documentation (XML, REST, POS and EQC) has a dedicated method. Methods written
as billing()->... are called on a client or on the facade, for example Previo::billing()->payments().
XML API (/x1/) |
Method |
|---|---|
| Hotel.addAccountItem | billing()->addAccountItems() |
| Hotel.addPayment | billing()->addPayment() |
| Hotel.get | hotel() |
| Hotel.getAccountItems | billing()->accountItems() |
| Hotel.getCompanies | companies() |
| Hotel.getConditions | pricing()->cancellationPolicy() |
| Hotel.getCurrencies | pricing()->currencies() |
| Hotel.getDocuments | billing()->reservationDocuments() |
| Hotel.getFees | pricing()->fees() |
| Hotel.getAllFees | pricing()->allFees() |
| Hotel.getGatewayPayInformation | billing()->gatewayPayment() |
| Hotel.getGuestCategories (deprecated) | hotelGuestCategories() |
| Hotel.getGuestReviews | content()->reviews() |
| Hotel.getGuestReviewsSummary | content()->reviewSummary() |
| Hotel.getGuests | searchGuests() |
| Hotel.getMeals (deprecated) | pricing()->meals() |
| Hotel.getMessages | content()->messages() |
| Hotel.getNews | content()->news() |
| Hotel.getObjectKindPhotogallery | content()->roomKindPhotogallery() |
| Hotel.getObjectKinds | objectKinds() |
| Hotel.getProperties | content()->amenities() |
| Hotel.getRates | rates() |
| Hotel.getRoomKinds | roomKinds() |
| Hotel.getPaymentMethods | pricing()->paymentMethods() |
| Hotel.getPayments | billing()->payments() |
| Hotel.getPhotogalleries | content()->photogalleries() |
| Hotel.getPriceActions | pricing()->priceActions() |
| Hotel.getPackages | pricing()->services() |
| Hotel.getStayPackages | pricing()->stayPackages() |
| Hotel.reservation | createReservation(), updateReservation(), changeReservationStatus(), cancelReservation(), cancelRoomReservation() |
| Hotel.searchReservations | reservations(), reservationsCursor(), ReservationSynchronizer |
| Hotel.setCollaboration | setCollaboration() |
| Hotels.availableHotels | availableHotels(), hotelClients(), allAvailableHotels() |
| Hotels.search | searchHotels() |
| Booking.getFreeCapacity (deprecated) | freeCapacity() |
| System.getChanges2 | changes(), system()->changes() |
| System.getCountries | system()->countries() |
| System.getCreditCardTypes | system()->creditCardTypes() |
| System.getCurrencies | system()->currencies() |
| System.getHotelProperties | system()->amenities() |
| System.getHotelTypes | system()->hotelTypes() |
| System.getLanguages | system()->languages() |
| System.getLocalities | system()->localities() |
| System.getMeals | system()->meals() |
| System.getNtakMainCategories | system()->ntakMainCategories() |
| System.getNtakSubCategories | system()->ntakSubCategories() |
| System.getObjectKindProperties | system()->roomKindProperties() |
| System.getPropertyAttributes | system()->propertyAttributes() |
| System.getReservationStatuses | reservationStatuses(), system()->reservationStatuses() |
| System.getSegments | system()->segments() |
| System.getStayProcedures | system()->stayProcedures() |
| System.getStayPrograms | system()->stayPrograms() |
| System.getZips | system()->zips() |
REST API (/rest/) |
Method |
|---|---|
| GET account | billing()->account() |
| GET billing/documents | billing()->documents() |
| GET billing/documents/export | billing()->exportDocuments() |
| GET billing/documents/print | billing()->printDocument() |
| GET calendar/availability | availability() |
| GET calendar/hourly-availability | hourlyAvailability() |
| GET calendar/meals | pricing()->mealPlans() |
| GET events/search | crmEvents() |
| GET guest-categories | guestCategories() |
| GET guests | guests() |
| GET guests/tags | guestTags() |
| GET guests/{id} | guest(), guestAnyHotel(), guestOwnerHotel() |
| POST guests | createGuest() |
| PUT guests/{id} | updateGuest() |
| DELETE guests/{id} | deleteGuest() |
| POST invoice/addPayment | billing()->payReservation() |
| GET packages/search | pricing()->searchStayPackages() |
| GET premise/kind | premiseKinds() |
| GET rate-plan | ratePlans() |
| GET reports/overview | overviewReport() |
| GET reservation | reservation() |
| PUT reservation/checkIn | updateCheckInGuest() |
| POST reservation/CheckInSignature | uploadCheckInSignature() |
| POST reservations/accounts/suggester | pricing()->suggest() |
| GET reservations/rooms/card-locking-keys | cardKeys() |
| POST reservations/rooms/check-in | checkIn() |
| GET rooms | rooms() |
| GET rooms/{id} | room() |
| PUT rooms/{id}/clean-statuses | setRoomCleanStatus() |
| GET services/search | pricing()->searchServices() |
POS API (/pos/) and EQC (/eqc1/) |
Method |
|---|---|
| GET pos/hotels | pos()->hotels() |
| GET pos/codebooks/currencies | pos()->currencies() |
| GET pos/codebooks/segments | pos()->segments() |
| GET pos/reservations/current | pos()->currentReservations() |
| POST pos/accounts/items | pos()->addItems() |
| POST eqc1/ar (AvailRateUpdate) | eqc()->updateAvailabilityAndRates() |
| POST eqc1/br (BookingRetrieval) | eqc()->bookings() |
Errors
| Exception | When |
|---|---|
InvalidConfiguration |
API key (or login and password) missing, hotel id missing for a hotel-scoped call, invalid timezone |
ApiException |
XML <error> document, REST or POS error body, EQC <Error code>, HTTP error, unreadable response or connection failure |
ApiException exposes apiCode() (Previo code such as 2004 invalid credentials, 1023 unauthorized hotel, 1030
simultaneous access, 6000 reservation locked; or the REST code), httpStatus(), operation() (for example
Hotel.searchReservations, GET rest/rate-plan, GET pos/hotels or EQC.AvailRateUpdate), userMessage() (localised REST message) and the helpers
isAuthenticationError(), isAuthorizationError(), isTransportError() and isRetryable(). All exceptions extend
PrevioException.
Read-only calls are retried (PREVIO_RETRIES) on connection errors, HTTP 5xx, 1003 (service temporarily
unavailable) and 1030, because Previo rejects a call while another one of the same account is still running.
Development
composer install
composer ci # pint --test, phpstan (level 8), pest
Author
Created and maintained by Kamil Ćwiertnia, beziworld.eu.
- E-mail: biuro@beziworld.eu
- Issues: GitHub issues
Need a hotel / PMS integration? Get in touch through beziworld.eu.
License
MIT. See LICENSE.md. This package is not affiliated with or endorsed by Previo.