Security News
Cloudflare Adds Security.txt Setup Wizard
Cloudflare has launched a setup wizard allowing users to easily create and manage a security.txt file for vulnerability disclosure on their websites.
@findhotel/sapi
Advanced tools
SDK provides a high level TypeScript/JavaScript API for searching hotels, hotels' offers and rooms.
There is no continuous integration/delivery in SAP SDK. Merging a PR into main branch doesn't automatically trigger the release process. For releases SAPI SDK uses Ship.js tool. It gives more control over the process. Ship.js automatically creates a PR before publishing every release, so this allows:
GitHub token is required for the release process:
GITHUB_TOKEN=xxx
To create and deploy a new release of SAPI SDK please follow the next steps:
main
branchmake release
command - this
will start the release processCHANGELOG.md
in the root directory. Write down
changes with descriptions using keep a changelog guiding
principles and commit your changes to the newly created release
PRSquash and merge
the PR. This will trigger automated
release process using GitHub actions and after some time the new
release will be published to NMPSometimes You might want to test your changes on the website for example on the local or custom/test environment before merging your branch to main and before making a new SAPI SDK release. To do so You can create and publish a pre-release.
Pre-release publishes a new version to NPM with a tag other than latest
(the default tag).
To publish a pre-release please follow the next steps:
commit your changes to your development branch
build SAPI SDK from the root of the project:
make core-build
create a new version npm version prepatch (preminor, premajor) --preid=beta (alpha, beta, etc...)
:
npm version prepatch --preid=beta
this would result in something like this 1.4.1-beta.0
If you’ve already released a pre-release and want to increment the pre-release part of the version only, run:
npm version prerelease
this would result in something like this 1.4.1-beta.1
publish your changes with a tag other than latest
:
npm publish --tag=next
after the new version is published to npm, install it to your local environment specifying the new version:
npm install @findhotel/sapi@1.4.1-beta.0
First, install SAPI SDK via the npm package manager:
npm install @findhotel/sapi
Then, import SAPI into your project:
import sapi from '@findhotel/sapi'
Create SAPI client:
const profileKey = 'profile-key'
const sapiClient = await sapi(profileKey, {
anonymousId: 'fd9dbb5f-b337-4dd7-b640-1f177d1d3caa',
language: 'en',
currency: 'USD',
countryCode: 'US'
})
Now SAPI client is ready to be used in your application.
For full documentation and supported options check client api.
Sapi SDK uses uuid
for generation of random strings.
uuid
uses a global crypto
object which is not
available in React Native environment.
To add support, crypto
polyfill should be installed on the
consumer side. We recommend installing just
crypto.getRandomValues
polyfill if you don't use other
crypto
methods. More information about polyfill can be
found here
Search for the hotels and hotels' offers:
const searchParameters = {
placeId: '47319',
checkIn: '2021-10-10',
checkOut: '2021-10-11',
rooms: '2'
}
const callbacks = {
onStart: (response) => {
log('Search started', response)
},
onAnchorReceived: (response) => {
log('Anchor received', response)
},
onHotelsReceived: (response) => {
log('Hotels received', response)
},
onOffersReceived: (response) => {
log('Offers received', response)
},
onComplete: (response) => {
log('Search completed', response)
}
}
const search = await sapiClient.search(searchParameters, callbacks)
For full documentation, check search method api.
Get rooms data and rooms' offers:
const rooms = await sapiClient.rooms({
hotelId: '47319',
checkIn: '2021-10-10',
checkOut: '2021-10-11',
rooms: '2'
})
For full documentation, check rooms method api.
Get hotel by id:
const hotel = await sapiClient.hotel('1196472')
For full documentation, check hotel method api.
Get hotels by ids:
const hotels = await sapiClient.hotels(['1714808','1380416','1710829'])
For full documentation, check hotels method api.
Get offers for a single hotel:
const parameters = {
hotelId: '1196472'
}
const callbacks = {
onStart: (response) => {
log('Offers started', response)
},
onOffersReceived: (response) => {
log('Offers received', response)
},
onComplete: (response) => {
log('Offers completed', response)
}
}
const offers = await sapiClient.offers(parameters, callbacks)
For full documentation, check offers method api.
Get hotel(s) availability:
const parameters = {
hotelIds: ['1380416','1359951'],
startDate: '2023-08-05',
endDate: '2023-08-12',
nights: 2,
rooms: '2',
}
const callbacks = {
onStart: (response) => {
log('Availability request started', response)
},
onAvailabilityReceived: (response) => {
log('Availability received', response)
},
onComplete: (response) => {
log('Availability completed', response)
}
}
const response = await sapiClient.hotelAvailability(parameters, callbacks)
For full documentation, check availability method api.
Create SAPI client:
const profileKey = 'profile-key'
const sapiClient = await sapi(profileKey, {
anonymousId: 'fd9dbb5f-b337-4dd7-b640-1f177d1d3caa',
language: 'en',
currency: 'USD',
countryCode: 'US',
deviceType: 'mobile',
pageSize: 20,
initWithAppConfig: {
},
algoliaClientOptions: {
timeouts: {
connect: 1, // Connection timeout in seconds
read: 2, // Read timeout in seconds
write: 30, // Write timeout in seconds
},
},
callbacks: {
onConfigReceived: (config) => {
log('Config received', config)
}
},
getTotalRate: (rate) => {
let totalRate = rate.base
if (includeTaxes) totalRate += rate.taxes
if (includeHotelFees) totalRate += rate.hotelFees
return totalRate
}
})
name | required | type | default | description | example |
---|---|---|---|---|---|
anonymousId | yes | string | Unique ID identifying users | 2d360284-577b-4a53-8b91-68f72b9227fa | |
language | no | string | en | 2-char language code | en |
currency | no | string | USD | 3-char uppercased ISO currency code | USD |
countryCode | no | string | US | 2-char uppercased ISO country code | US |
deviceType | yes | string | desktop , mobile , tablet | desktop | |
pageSize | no | number | 20 | Displayed page size | 20 |
initWithAppConfig | no | AppConfig | External app config to override internal one | ||
algoliaClientOptions | no | AlgoliaSearchOptions | Algolia client options used for debugging and setting additional options like timeouts etc. | ||
variations | no | Record<string, string> | A/B test variations | {'pp000004-tags2': 'b', 'pp000004-tags3': '1'} | |
callbacks | no | Record<string, fnc> | Client callbacks | ||
getTotalRate | no | function | Total rate including taxes and fees | Function to calculate total display rate based on tax display logic. Used for price filter and sort by price functionalities. |
onConfigReceived(configs)
Returns configuration settings that are used by sapiClient.
{
"exchangeRates": {
"EUR": 0.982994
},
"lov": [
{
"id": 9,
"categoryID": 19,
"objectID": "Facility:9",
"value": {
"en": "Airport shuttle"
}
},
{
"categoryID": 2,
"id": 7,
"objectID": "Facility:7",
"value": {
"en": "Swimming pool"
}
}
]
}
search()
methodSearch is a method of sapiClient for searching hotels and offers for
provided searchParameters
:
const search = await sapiClient.search(searchParameters, callbacks)
All parameters are optional.
name | type | description | example |
---|---|---|---|
hotelId | string | Hotel Id for hotel search | 1371626 |
placeId | string | Place Id for place search | 47319 |
geolocation | {lat: number, lon: number, precision: string} | Geolocation query | {lat: 36.114303, lon: -115.178312} |
address | string | Address search string | Nieuwe Looiersdwarsstraat 17, |
boundingBox | [p1Lat, p1Lng, p2Lat, p2Lng] | topLeft and bottomRight coordinates of bounding box to perform search inside it | [46.650828100116044, 7.123046875, 45.17210966999772, 1.009765625] |
checkIn | string | Check in date (YYYY-MM-DD ) | 2021-10-10 |
checkOut | string | Check out date (YYYY-MM-DD ) | 2021-10-11 |
rooms | string | Rooms configuration | 2 |
dayDistance | number | Amount of full days from now to desired check in date (works in combination with nights parameter) | 15 |
nights | number | Number of nights of stay | 3 |
sortField | string | Defines the sort by criteria | popularity, price |
sortOrder | string | Defines the sort order | ascending, descending |
filters | Object | Object with filters | {starRatings: 5} |
cugDeals | string[] | Codes of closed user group deals to retrieve offers | ['signed_in', 'offline'] |
tier | string | User tier | member |
originId | string | Identifier of origin where the request was originated | c3po6twr70 |
preferredRate | number | Offer's price user saw on a CA (meta) platform | 196 |
label | string | Opaque value that will be passed to tracking systems | gha-vr |
userId | string | An authenticated user ID, e.g. the Google ID of a user. Used by ACL | c34b0018-d434-4fad-8de6-9d8e8e18cb35 |
emailDomain | string | Email domain for authenticated user, Used by ACL | @manatuscostarica.com |
screenshots | number | Number of of screenshot taken by the user and detected by website, Used by ACL | 2 |
metadata | Record<string, string> | Additional metadata to be passed to the search | {esd: 'abcds', epv: 'qwert'} |
offerSort | string | If present, sorts offers by price (see explanation) | |
sbc | string | Split Booking Configuration. If present, searches for split booking offers | sbc: '2~3' |
The format for the split booking configuration is as follows:
LOS1~LOS2
where:
LOS1
and LOS2
are integers representing the length of stay for the first and second part of the split booking respectively.
for example if we have these parameters:
checkIn:'2023-08-10'
checkOut:'2023-08-15'
sbc:2~3
The first part of the split booking will be 2 nights and the second part will be 3 nights. So, SAPI searches for two stays:
split1: checkIn: '2023-08-10'
checkOut: '2023-08-12'
split2: checkIn: '2023-08-12'
checkOut: '2023-08-15'
name | type | description | example | filter scope |
---|---|---|---|---|
themeIds | number[] | Facet filtering by theme ids | [1, 2, 3] | Hotel |
chainIds | number[] | Hotels' chain ids | [4, 5] | Hotel |
facilities | string[] | Facility ids used for facet filtering | [299,7,6,21] | Hotel |
starRatings | number[] | Hotels' star rating | [3, 4] | Hotel |
propertyTypeId | number[] | Property type id | [1, 0] | Hotel |
guestRating | number | Minimum overall guest rating | 5 | Hotel |
noHostels | boolean | If true, hostels will be excluded from results | true | Hotel |
priceMin | number | Minimum price per night | 100 | Hotel/Offer |
priceMax | number | Maximum price per night | 200 | Hotel/Offer |
hotelName | string | Filters the result based on the Hotel name | Hilton Amsterdam | Hotel |
freeCancellation | boolean | If true, only offers with free cancellation will be returned | true | Offer |
payLater | boolean | If true, only offers with pay later option will be returned | true | Offer |
mealIncluded | boolean | If true, only offers with meal included option will be returned | true | Offer |
amenities | string[] | Meal types | ["breakfast","allInclusive"] | |
isVr | boolean | If true, then only vacation rentals are returned. If false, then vacation rentals are filtered out | true | Hotel |
Some of the favourite facilities used for filtering:
name | id |
---|---|
free_wifi | 299 |
swimming_pool | 7 |
restaurant | 5 |
parking | 6 |
pet_friendly | 8 |
kitchen | 50 |
hot_tub | 61 |
spa | 193 |
air_conditioned | 21 |
Some of the property types used for filtering:
name | id |
---|---|
hotel | 0 |
apartment | 9 |
hostel | 5 |
motel | 1 |
name | description |
---|---|
breakfast | |
lunch | |
dinner | |
halfBoard | "breakfast && dinner", or the hotel’s explicit use of the term "half-board" |
fullBoard | "breakfast && lunch && dinner", or the hotel’s explicit use of the term "full-board" |
allInclusive | "all meals && all drinks" or the hotel’s explicit use of the term "all-inclusive" |
Search method receives callbacks object as the second argument:
const callbacks = {
onStart: (response) => {
log('Search started', response)
},
onAnchorReceived: (response) => {
log('Anchor received', response)
},
onHotelsReceived: (response) => {
log('Hotels received', response)
},
onOffersReceived: (response) => {
log('Offers received', response)
},
onComplete: (response) => {
log('Search completed', response)
}
}
Each callback returns the full search state available inside SAPI at the time of callbacks executions. This means that each subsequent callback incrementally adds new properties and returns updated state. The data in the state can be repeated between different callbacks.
Runs at the beginning of each new search.
The callback is supplied with generated searchId
and
actual search parameters that are used for the search, like dates,
currency, language used if invoker didn't provide those.
Another purpose of this callback is that invoker can track that hotels search has started, in Search SPA the event is HotelsSearched.
Runs when SAPI receives anchor and(?) anchor hotel (in case of hotel
search)
Returns static data about anchor response.anchor
and(?)
anchor hotel inside response.hotelEntities
object (in
case of hotel search).
Runs when SAPI receives static search results
Returns static data about hotels inside
response.hotelEntities
object as well as appropriate
hotel sort order in response.hotelIds
array (sorted
according to HSO).
Runs when SAPI receives a bunch of offers
Returns hotel's offers (incomplete response) in
response.hotelOfferEntities
object as well as updated
hotel sort order in response.hotelIds
array (sorted
according to HSO and taking into account the real offers data)
The offers response with the detailed description can be found in the Offers Response
Runs when current search is complete and all offers are retrieved
Return complete SAPI search response.
The offers response with the detailed description can be found in the Offers Response
The complete SAPI search response is too big to be displayed on the page so there is a simplified example:
{
"anchor": {
"admDivisionLevel1": "The Netherlands",
"admDivisionLevel2": "Amsterdam",
"admDivisionLevel3": "Bedrijventerrein Sloterdijk",
"hotelName": "Holiday Inn Express Amsterdam - Sloterdijk Station, an IHG Hotel",
"objectID": "hotel:1714808",
"objectType": "hotel",
"pageSize": 26,
"placeDisplayName": "Bedrijventerrein Sloterdijk, Amsterdam",
"priceBucketWidth": 19,
"_geoloc": {"lat": 52.388326, "lon": 4.837264, "precision": 5000, "radius": 20000}
},
"anchorHotelId": "1714808",
"anchorType": "hotel",
"facets": {
"facilities": {"1": 212, "2": 348, "3": 116},
"pricing.medianRateBkt": {"3": 4, "4": 12, "5": 19},
"pricing.medianRateMoFrBkt": {"3": 4, "4": 6, "5": 8, "6": 9},
"pricing.medianRateSaSuBkt": {"2": 2, "3": 4, "4": 6, "5": 10},
"pricing.minRateBkt": {"2": 22, "3": 38, "4": 46, "5": 98},
"propertyTypeId": {"0": 1030, "2": 6, "3": 601, "4": 86},
"starRating": {"1": 351, "2": 313, "3": 596, "4": 441, "5": 86},
"themeIds": {"1": 9, "2": 1373, "3": 143, "5": 1092, "8": 119}
},
"hotelEntities": {
"1041597": {
"chainID": "351",
"checkInTime": "15:00",
"checkOutTime": "12:00",
"country": "NL",
"displayAddress": "Stationsplein 49, Burgwallen-Nieuwe Zijde, Amsterdam, The Netherlands",
"facilities": [1, 2, 5, 8, 9, 11],
"guestRating": {"cleanliness": 7.94, "dining": 7.51, "facilities": 7.54, "location": 8.09, "overall": 7.83, "service": 8.8},
"guestType": {"business": 793, "couples": 2558, "families": 1145, "groups": 1276, "solo": 398},
"hotelName": "ibis Amsterdam Centre",
"imageURIs": ["https://i.travelapi.com/hotels/1000000/30000/25600/25525/c2387aed_w.jpg"],
"isDeleted": false,
"lastBooked": 1658320362,
"objectID": "1041597",
"parentChainID": "10",
"placeDisplayName": "Burgwallen-Nieuwe Zijde, Amsterdam",
"pricing": {"medianRateBkt": 12, "medianRateMoFrBkt": 13, "medianRateSaSuBkt": 11, "minRate": 116, "minRateBkt": 75, "minRateBkt": 5, "priced": 1},
"propertyTypeId": 0,
"regularPriceRange": [224, 243],
"reviewCount": 5766,
"sentiments": [177, 250, 300, 419],
"starRating": 3,
"tags": ["b220918-1"],
"themeIds": [2, 5, 12],
"_geoloc": {"lat": 52.37947, "lon": 4.89699}
}
},
"hotelIds": ["1840170", "2766940", "3310443", "3035769"],
"hotelOfferEntities": {
"1041597": {
"anchorRate": {
"base": 244.6021305,
"hotelFees": 23.122149135,
"taxes": 20.4147
},
"availableOffersCount": 23,
"id": "1041597",
"offers": [
{
"id": "o5vFF4-wmvcc",
"currency": "EUR",
"availableRooms": 6,
"url": "https://r.vio.com?...",
"rate": {
"base": 197.17,
"taxes": 17.75,
"hotelFees": 31.8
},
"accessTier": "",
"providerCode": "BKS",
"intermediaryProvider": "BKS",
"roomName": "Standard Twin Room",
"package": {
"amenities": [
"breakfast"
],
"canPayLater": false
},
"cancellationPenalties": [],
"tags": [
"top_offer"
],
"metadata": {
"feedID": ""
}
}
],
"discount": {
"discountProvider": "FHT",
"hasDiscountProvider": true,
"hasParityProvider": true,
"modifier": "b"
}
}
},
"hotelsHaveStaticPosition": true,
"offset": 0,
"resultsCount": 26,
"resultsCountTotal": 3401,
"searchId": "0f9642a0-eab7-4acb-8313-bc6d19c408f6",
"searchParameters": {
"hotelId": "1714808",
"checkIn": "2022-09-01",
"checkOut": "2022-09-02",
"rooms": "2"
},
"status": {
"anchorComplete": true,
"nonAnchorComplete": true
}
}
After the search is complete:
const search = await sapiClient.search(searchParameters, callbacks)
it resolves in the search
object which has 2 methods:
search.loadMore()
- SDK will return the next set of results ("next" page) for the given search. The same callbacks from original search will be used.search.loadOffers(hotelId, {filters, offersSort})
- will load all offers (up to 23) for the provided hotelId
. You can also override offers' scope filters and offers' sort order.suggest()
methodSuggest provides autosuggestions for a given query
const suggestions = await sapiClient.suggest('London', 6)
name | type | description | required | example |
---|---|---|---|---|
query | string | Query string | yes | London |
suggestsCount | number | Desired number of suggestions (default 6) | no | 10 |
const suggestions = await sapiClient.suggest('London', 2)
[
{
"highlightValue": "<em>London</em>",
"objectID": "158584",
"objectType": "place",
"placeDisplayName": "United Kingdom",
"placeTypeName": "city",
"value": "London"
},
{
"highlightValue": "<em>London</em> Heathrow Airport",
"objectID": "167733",
"objectType": "place",
"placeDisplayName": "London, United Kingdom",
"placeTypeName": "airport",
"value": "London Heathrow Airport"
}
]
const suggestions = await sapiClient.suggest('London hotel', 2)
[
{
"highlightValue": "Park Plaza Westminster Bridge <em>London</em>",
"objectID": "1546646",
"objectType": "hotel",
"placeDisplayName": "London, United Kingdom",
"placeTypeName": "property",
"value": "Park Plaza Westminster Bridge London"
},
{
"highlightValue": "Hampton by Hilton <em>London</em> Stansted Airport",
"objectID": "3333916",
"objectType": "hotel",
"placeDisplayName": "United Kingdom",
"placeTypeName": "property",
"value": "Hampton by Hilton London Stansted Airport"
}
]
rooms()
methodRooms is a method of sapiClient for retrieving rooms information and offers for a particular itinerary:
const rooms = await sapiClient.rooms({
hotelId: '1714808',
checkIn: '2021-10-10',
checkOut: '2021-10-11',
rooms: '2'
})
name | type | description | required | example |
---|---|---|---|---|
hotelId | string | Hotel Id to retrieve rooms | yes | 1714808 |
checkIn | string | Check in date (YYYY-MM-DD ) | yes | 2021-10-10 |
checkOut | string | Check out date (YYYY-MM-DD ) | yes | 2021-10-11 |
rooms | string | Rooms configuration | yes | 2 |
providerCode | string | Provider code used for retrieving rooms offers | no | GAR |
cugDeals | string | Type of cug (closed user group) deals to retrieve | no | signed_in,offline |
tier | string | User tier | no | plus |
clickedOfferId | string | Offer id that user clicked on SRP | no | oXstvXafDb2o |
clickedOfferBaseRate | number | Base rate value of the offer which user clicked on SRP | no | 25 |
clickedOfferTaxes | number | Taxes value of the offer which user clicked on SRP | no | 10 |
clickedOfferHotelFees | number | Hotel fees value of the offer which the user clicked on SRP | no | 5 |
preHeat | number | Enables pre-heat mode | no | 1 |
label | string | Opaque value that will be passed to tracking systems | no | gha-vr |
userId | string | User ID is an authenticated user ID, e.g. the Google ID of a user. | no | 7ea95191-b929-469d-8b02-0397dafd53a1 |
emailDomain | string | User email domain is for authenticated user as a value, if email is available. | no | @findhotel.net |
searchId | string | SearchId override (SDK generates a new one if no provided) | no | 08230dfcc5f0fb95caaa82ce559ea60c4a975d6f |
metadata | Record<string, string> | Additional data to be passed to API | no | {vclid: '21d2d152-de19-4f39-b40a-d4bc3002c6e8'} |
{
"anonymousId": "fd9dbb5f-b337-4dd7-b640-1f177d1d3caa",
"hotelId": "1714808",
"searchId": "08230dfcc5f0fb95caaa82ce559ea60c4a975d6f",
"rooms": [
{
"amenities": ["Non-Smoking Facility"],
"bedTypes": [],
"description": "20 sqm room Continental breakfast included Free WiFi LCD TV Spacious work desk Choice of pillows Coffee and tea facilities Large walk-in power shower",
"id": "K0LnAe-G9WI",
"images": [{"url": "http://images.getaroom-cdn.com/image/...."}],
"masterId": "9643931",
"name": "1 Double Standard",
"offers": [
{
"availableRooms": 0,
"canPayLater": false,
"cancellationPenalties": [],
"cug": ["signed_in"],
"id": "o0KDe_mFPN4k",
"links": [{
"href": "https://secure.findhotel.net/checkout/?hotelID=1714808...",
"method": "GET",
"type": "checkout"
}],
"prices": [{
"chargeable": {"base": "1352.77", "taxes": "59.94"},
"currencyCode": "EUR",
"hotelFees": {"breakdown": [], "total": "0"},
"type": "chargeable_currency"
}],
"providerCode": "GAR",
"providerRateType": "public",
"services": ["breakfast"]
}
]
}
]
}
hotel()
methodHotel is a method of sapiClient for retrieving a single hotel by id
const hotel = await sapiClient.hotel('1196472')
name | type | description | required | example |
---|---|---|---|---|
hotelId | string | Hotel Id | yes | 1196472 |
An example of the response for request with hotelId
1196472
and "pt-BR" language
{
"objectID": "1196472",
"guestRating": {
"cleanliness": 8.82,
"dining": 8.73,
"facilities": 8.73,
"location": 8.8,
"overall": 8.54,
"pricing": 4.49,
"rooms": 8.82,
"service": 8.67
},
"isDeleted": false,
"starRating": 4,
"_geoloc": {
"lat": 52.377805,
"lon": 4.914172
},
"feesOpt": "<p>Os seguintes depósitos e taxas são cobrados pelo hotel no momento do serviço prestado, durante o check-in ou check-out. </p> <ul> <li>Taxa para café da manhã em estilo buffet: EUR 25.00 por pessoa (aproximadamente)</li> <li>Taxa para o acesso à internet com fio no quarto: EUR 15 por dia (as tarifas podem variar)</li> </ul> <p>A lista acima pode estar incompleta. As taxas e os depósitos podem não incluir impostos e estão sujeitos a mudanças. </p>",
"checkInSpInst": "O funcionário da recepção receberá os hóspedes na chegada. Para mais informações, entre em contato com o estabelecimento usando os dados que estão na confirmação da reserva. Os hóspedes que forem usar o traslado da Estação Central de Amsterdã devem entrar em contato com o estabelecimento com antecedência. Devido à COVID-19, as opções de comida e bebida deste estabelecimento podem estar limitadas de acordo com os regulamentos locais.",
"policies": "<ul> <li>Este estabelecimento oferece transporte da estação de trem. Os hóspedes devem entrar em contato com o estabelecimento antes da viagem com os detalhes da chegada usando as informações para contato presentes na confirmação da reserva. </li> <li>É necessário reservar os seguintes serviços: serviços de massagem. As reservas podem ser feitas contatando este hotel antes da viagem usando as informações para contato fornecidas na confirmação da reserva. </li> <li>Somente hóspedes registrados podem ter acesso aos quartos. </li></ul>",
"phone": "+31205191200",
"imageURIs": [
"https://i.travelapi.com/hotels/2000000/1460000/1452100/1452083/a39e2fc8_w.jpg",
"https://i.travelapi.com/hotels/2000000/1460000/1452100/1452083/3892e861_w.jpg",
"https://i.travelapi.com/hotels/2000000/1460000/1452100/1452083/aed25438_w.jpg"
],
"checkInTime": "15:00",
"checkInInst": "<ul> <li>Pessoas extras podem incorrer em taxas adicionais que variam dependendo da política do estabelecimento</li><li>Documento de identificação oficial com foto e cartão de crédito, cartão de débito ou depósito em dinheiro podem ser exigidos no momento do check-in para despesas extras.</li></ul>",
"reviewCount": 17538,
"chainID": "2101",
"checkInEnd": "01:00",
"lastBooked": 1608030831,
"checkOutTime": "12:00",
"feesMnd": "<p>Você deverá pagar os seguintes encargos no estabelecimento:</p> <ul><li>Depósito: EUR 50 por noite</li><li>A cidade cobra um imposto de EUR 3.00 por pessoa, por noite até 21 noites. Esse imposto não se aplica a crianças de até 16 anos. </li><li>Será cobrado um imposto municipal/local de 6.422 %</li></ul> <p>Incluímos todas as cobranças que o estabelecimento nos forneceu. No entanto, as cobranças podem variar com base, por exemplo, na duração da estadia ou no tipo de quarto reservado. </p>",
"reviewCountBkt": 9,
"hotelName": "Movenpick Hotel Amsterdam City Centre",
"sentiments": [{
"value": "Gostou do bar",
"id": 46
},
{
"value": "Processo rápido de check-in/check-out",
"id": 180
}
],
"facilities": [{
"categoryID": 4,
"importance": 2,
"value": "Centro de Negócios",
"id": 1
},
{
"categoryID": 17,
"importance": 2,
"value": "Serviço de quarto",
"id": 2
}
],
"placeID": "311007",
"guestType": {
"business": 1333,
"couples": 3125,
"families": 1484,
"groups": 1121,
"solo": 206
},
"themes": [{
"value": "Cidade",
"id": 2
},
{
"value": "Empresarial",
"id": 12
}
],
"propertyType": {
"value": "Hotel",
"id": 0
},
"placeDisplayName": "Oostelijk Havengebied, Amsterdã",
"displayAddress": "Piet Heinkade 11, Oostelijk Havengebied, Amsterdã, Holanda"
}
hotels()
methodHotel is a method of sapiClient for retrieving multiple hotels by hotel ids
const hotels = await sapiClient.hotels(['1714808','1380416','1710829'])
name | type | description | required | example |
---|---|---|---|---|
hotelIds | string[] | Hotel Ids | yes | ['1714808','1380416','1710829'] |
An example of the response for request with hotelId
1196472
and "pt-BR" language
{
"1196472": {
"objectID": "1196472",
"guestRating": {
"cleanliness": 8.82,
"dining": 8.73,
"facilities": 8.73,
"location": 8.8,
"overall": 8.54,
"pricing": 4.49,
"rooms": 8.82,
"service": 8.67
},
"isDeleted": false,
"starRating": 4,
"_geoloc": {
"lat": 52.377805,
"lon": 4.914172
},
"feesOpt": "<p>Os seguintes depósitos e taxas são cobrados pelo hotel no momento do serviço prestado, durante o check-in ou check-out. </p> <ul> <li>Taxa para café da manhã em estilo buffet: EUR 25.00 por pessoa (aproximadamente)</li> <li>Taxa para o acesso à internet com fio no quarto: EUR 15 por dia (as tarifas podem variar)</li> </ul> <p>A lista acima pode estar incompleta. As taxas e os depósitos podem não incluir impostos e estão sujeitos a mudanças. </p>",
"checkInSpInst": "O funcionário da recepção receberá os hóspedes na chegada. Para mais informações, entre em contato com o estabelecimento usando os dados que estão na confirmação da reserva. Os hóspedes que forem usar o traslado da Estação Central de Amsterdã devem entrar em contato com o estabelecimento com antecedência. Devido à COVID-19, as opções de comida e bebida deste estabelecimento podem estar limitadas de acordo com os regulamentos locais.",
"policies": "<ul> <li>Este estabelecimento oferece transporte da estação de trem. Os hóspedes devem entrar em contato com o estabelecimento antes da viagem com os detalhes da chegada usando as informações para contato presentes na confirmação da reserva. </li> <li>É necessário reservar os seguintes serviços: serviços de massagem. As reservas podem ser feitas contatando este hotel antes da viagem usando as informações para contato fornecidas na confirmação da reserva. </li> <li>Somente hóspedes registrados podem ter acesso aos quartos. </li></ul>",
"phone": "+31205191200",
"imageURIs": [
"https://i.travelapi.com/hotels/2000000/1460000/1452100/1452083/a39e2fc8_w.jpg",
"https://i.travelapi.com/hotels/2000000/1460000/1452100/1452083/3892e861_w.jpg",
"https://i.travelapi.com/hotels/2000000/1460000/1452100/1452083/aed25438_w.jpg"
],
"checkInTime": "15:00",
"checkInInst": "<ul> <li>Pessoas extras podem incorrer em taxas adicionais que variam dependendo da política do estabelecimento</li><li>Documento de identificação oficial com foto e cartão de crédito, cartão de débito ou depósito em dinheiro podem ser exigidos no momento do check-in para despesas extras.</li></ul>",
"reviewCount": 17538,
"chainID": "2101",
"checkInEnd": "01:00",
"lastBooked": 1608030831,
"checkOutTime": "12:00",
"feesMnd": "<p>Você deverá pagar os seguintes encargos no estabelecimento:</p> <ul><li>Depósito: EUR 50 por noite</li><li>A cidade cobra um imposto de EUR 3.00 por pessoa, por noite até 21 noites. Esse imposto não se aplica a crianças de até 16 anos. </li><li>Será cobrado um imposto municipal/local de 6.422 %</li></ul> <p>Incluímos todas as cobranças que o estabelecimento nos forneceu. No entanto, as cobranças podem variar com base, por exemplo, na duração da estadia ou no tipo de quarto reservado. </p>",
"reviewCountBkt": 9,
"hotelName": "Movenpick Hotel Amsterdam City Centre",
"sentiments": [{
"value": "Gostou do bar",
"id": 46
},
{
"value": "Processo rápido de check-in/check-out",
"id": 180
}
],
"facilities": [{
"categoryID": 4,
"importance": 2,
"value": "Centro de Negócios",
"id": 1
},
{
"categoryID": 17,
"importance": 2,
"value": "Serviço de quarto",
"id": 2
}
],
"placeID": "311007",
"guestType": {
"business": 1333,
"couples": 3125,
"families": 1484,
"groups": 1121,
"solo": 206
},
"themes": [{
"value": "Cidade",
"id": 2
},
{
"value": "Empresarial",
"id": 12
}
],
"propertyType": {
"value": "Hotel",
"id": 0
},
"placeDisplayName": "Oostelijk Havengebied, Amsterdã",
"displayAddress": "Piet Heinkade 11, Oostelijk Havengebied, Amsterdã, Holanda"
}
}
offers()
methodRetrieves offers for a single hotel by provided parameters
const parameters = {
hotelId: '1196472',
checkIn: '2022-10-10',
checkOut: '2022-10-11',
rooms: '2'
}
const callbacks = {
onStart: (response) => {
log('Offers started', response)
},
onOffersReceived: (response) => {
log('Offers received', response)
},
onComplete: (response) => {
log('Offers completed', response)
}
}
const offers = await sapiClient.offers(parameters, callbacks)
name | type | description | required | example |
---|---|---|---|---|
hotelId | string | Hotel Id | yes | 1196472 |
checkIn | string | Check in date (YYYY-MM-DD) (SDK generates default date if no provided) | no | 2022-10-10 |
checkOut | string | Check out date (YYYY-MM-DD)) (SDK generates default date if no provided) | no | 2022-10-11 |
rooms | string | Rooms configuration | no | 2 |
searchId | string | SearchId override (SDK generates a new one if no provided) | no | 08230dfcc5f0fb95caaa82ce559ea60c4a975d6f |
cugDeals | string[] | Codes of closed user group deals to retrieve offers | no | ['signed_in', 'offline'] |
tier | string | User tier | no | member |
freeCancellation | boolean | (Deprecated, use freeCancellation in the filters object instead) If true, only offers with free cancellation will be returned | no | true |
isAnchor | boolean | Anchor/Non anchor hotel (default false ) | no | true |
originId | string | Identifier of origin where the request was originated | no | c3po6twr70 |
label | string | Opaque value that will be passed to tracking systems | no | gha-vr |
preferredRate | number | Offer's price user saw on a CA (meta) platform | no | 196 |
metadata | Record<string, string> | Additional data to be passed to API | no | {esd: 'abcds', epv: 'qwert'} |
filters | Object | Object with filters | no | {pay_later:true} |
offersSort | string | If present, sorts offers by price (see explanation) | no | |
getAllOffers | boolean | If true SAPI returns all (up to 23) offers for the hotel (by default or if it's false SAPI returns only up to 3 top offers) | no | |
optimizeRooms | boolean | If true then rooms optimization logic is enabled for non anchor hotels. Rooms optimization tries to find best offer across possible rooms configurations for a given occupancy | no | |
optimizeAnchorRooms | boolean | If true then rooms optimization logic is enabled for an anchor hotel. Rooms optimization tries to find best offer across possible rooms configurations for a given occupancy | no | |
sbc | string | Split Booking Configuration. If present, searches for split booking offers | no | sbc: '2~3' |
Offer sorting by one parameter for now. It is price
, but there are different variants of sorting by price.:
It overwrites OSO sorting and sorting boost as well. If offersSort parameter is provided, then OSO sorting and sorting boost will be ignored.
Object with callbacks:
const callbacks = {
onStart: (response) => {
log('Offers started', response)
},
onOffersReceived: (response) => {
log('Offers received', response)
},
onComplete: (response) => {
log('Offers completed', response)
}
}
Runs at the beginning of each new offers request.
Returns adjusted and validated offers request parameters.
Runs on every received batch of offers.
Returns hotel's offers (incomplete response).
Runs after offers request complete.
Returns hotel's offers (complete response).
An example of the response for request parameters:
const parameters = {
hotelId: '1926746',
checkIn: '2022-07-10',
checkOut: '2022-07-11',
rooms: '2'
}
The response with the detailed description can be found in the Offers Response
hotelAvailability()
methodReturns availability of the provided hotels for different dates (aka price calendar)
const parameters = {
hotelIds: ['1380416','1359951'],
startDate: '2023-08-05',
endDate: '2023-08-12',
nights: 2,
rooms: '2',
}
const callbacks = {
onStart: (response) => {
log('Availability request started', response)
},
onAvailabilityReceived: (response) => {
log('Availability received', response)
},
onComplete: (response) => {
log('Availability completed', response)
}
}
const response = await sapiClient.hotelAvailability(parameters, callbacks)
name | type | description | required | example |
---|---|---|---|---|
hotelIds | string, string[] | Hotel id(s) to retrieve offers for | yes | ['1380416','1359951'] |
startDate | string | Date of first check in (YYYY-MM-DD) | yes | '2023-10-10' |
endDate | string | Date of last check in (including). Can't be further than 61 days from StartDate (YYYY-MM-DD) | yes | '2023-10-11' |
nights | number | Nights is length of stay | yes | 3 |
rooms | string | Rooms configuration | yes | '2' |
cugDeals | string[] | Codes of closed user group deals to retrieve offers | no | ['signed_in', 'offline'] |
tier | string | User tier | no | 'member' |
originId | string | Identifier of origin where the request was originated | no | 'c3po6twr70' |
Object with callbacks:
const callbacks = {
onStart: (response) => {
log('Availability request started', response)
},
onAvailabilityReceived: (response) => {
log('Availability received', response)
},
onComplete: (response) => {
log('Availability completed', response)
}
}
Runs at the beginning of each new availability request.
Returns adjusted and validated availability search parameters.
Runs on every received batch of availability.
Returns availability with offers (incomplete response).
Runs after availability request is completed.
Returns availability with offers (complete response).
An example of the response:
{
"1380416": {
"2023-08-05": {
"hotelID": "1380416",
"cheapestRate": {
"base": 531.9901869159,
"taxes": 34.7298130841,
"hotelFees": 0
},
"offers": [
{
"id": "oEfGbUTje2BE",
"currency": "EUR",
"availableRooms": null,
"url": "https://r.vio.com?lbl=providerRateType%3Dmember&ofd=...",
"rate": {
"base": 531.9901869159,
"taxes": 34.7298130841,
"hotelFees": 0
},
"accessTier": "sensitive",
"providerCode": "WTM",
"intermediaryProvider": "FHT",
"package": {
"amenities": [
"breakfast"
],
"canPayLater": false
},
"cancellationPenalties": [
{
"start": "2023-07-30T10:00:00Z",
"end": "2023-08-02T09:59:00Z",
"amount": 0,
"currency": "EUR"
}
],
"tags": [
"top_offer",
"exclusive_cheapest_offer"
],
"metadata": {
"providerRateType": "member",
"feedID": ""
},
"roomName": "STANDARD DOUBLE/TWIN"
},
]
},
"2023-08-06": {
"hotelID": "1380416",
"cheapestRate": {
"base": 493.9438317757,
"taxes": 32.2961682243,
"hotelFees": 0
},
"offers": [
{
"id": "omVVqhCXEbIw",
"currency": "EUR",
"availableRooms": null,
"url": "https://r.vio.com?lbl=providerRateType%3Dmember&ofd=book_uri...",
"rate": {
"base": 493.9438317757,
"taxes": 32.2961682243,
"hotelFees": 0
},
"accessTier": "sensitive",
"providerCode": "WTM",
"intermediaryProvider": "FHT",
"package": {
"amenities": [
"breakfast"
],
"canPayLater": false
},
"cancellationPenalties": [],
"tags": [
"top_offer",
"exclusive_cheapest_offer"
],
"metadata": {
"providerRateType": "member",
"feedID": ""
},
"roomName": "STANDARD DOUBLE/TWIN"
}
]
}
}
}
rooms
is a string that encodes the configuration of rooms requested by the user and follows the next rules:
|
:
,
1:4,6|3
→ Two rooms, one with one adult and two children ages four and six and the other with three adults and no children3
→ One room with three adults and no children2:4
→ One room with two adults and one child aged four1:0,13,16
→ One room with one adult and three children (aged zero, thirteen and sixteen)searchId
?Every search should have a searchId
- a random unique string used for tracking purposes. SAPI SDK automatically generates a unique searchId
each time clients call search()
, rooms()
or offers()
methods. Sometimes it might be useful to preserve the searchId
between multiple actions. For this case all mentioned methods accept searchId
as a parameter and use it instead of internally generated one.
SAPI SDK exposes a special method generateSearchId
to generate searchId
which then can be used in the clients app.
import {generateSearchId} from '@findhotel/sapi'
const searchId = generateSearchId()
const rooms = await sapiClient.rooms({
hotelId: '47319',
checkIn: '2023-10-10',
checkOut: '2023-10-11',
rooms: '2',
searchId
})
FAQs
FindHotel Search API
The npm package @findhotel/sapi receives a total of 924 weekly downloads. As such, @findhotel/sapi popularity was classified as not popular.
We found that @findhotel/sapi demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Cloudflare has launched a setup wizard allowing users to easily create and manage a security.txt file for vulnerability disclosure on their websites.
Security News
The Socket Research team breaks down a malicious npm package targeting the legitimate DOMPurify library. It uses obfuscated code to hide that it is exfiltrating browser and crypto wallet data.
Security News
ENISA’s 2024 report highlights the EU’s top cybersecurity threats, including rising DDoS attacks, ransomware, supply chain vulnerabilities, and weaponized AI.