GetPropertyInventory
const url = 'https://example.com/wink.partner.v1.Inventory/GetPropertyInventory';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"userSession":{"itinerary":{"startDate":"example","nights":1,"room":{"adults":1,"children":[{"age":1,"quantity":1}],"quantity":1}},"languageCode":"example","quoteCurrencies":["example"],"promotionalCodes":["example"]},"propertyId":"example","urlName":"example","include":["INVENTORY_SCOPE_UNSPECIFIED"],"imageFormat":"example"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://example.com/wink.partner.v1.Inventory/GetPropertyInventory \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "userSession": { "itinerary": { "startDate": "example", "nights": 1, "room": { "adults": 1, "children": [ { "age": 1, "quantity": 1 } ], "quantity": 1 } }, "languageCode": "example", "quoteCurrencies": [ "example" ], "promotionalCodes": [ "example" ] }, "propertyId": "example", "urlName": "example", "include": [ "INVENTORY_SCOPE_UNSPECIFIED" ], "imageFormat": "example" }'Prices for one property
Returns every offer bookable at this property for the requested dates and occupancy, cheapest first.
Identify the property by property_id or by url_name — set exactly one.
Billed as min(itinerary nights, 90) hotel-day units, whether or not anything is available.
Authorizations
Section titled “Authorizations”Request Bodyrequired
Section titled “Request Bodyrequired”Request for GetPropertyInventory.
At most one of propertyId, urlName is set (protobuf oneof property).
object
Traveller context: dates, occupancy, language and the exchange rates you want back. Required.
object
Stay dates and occupancy. Required — searches without an itinerary cannot be priced.
object
Check-in date, ISO-8601 “YYYY-MM-DD”. Required. Must be today or later – a past date is rejected with INVALID_ARGUMENT.
Number of nights. Billing caps this at 90; a longer stay is rejected with INVALID_ARGUMENT.
The room being searched for, and how many of it. Required.
SINGULAR. This was once a list, on the promise that several occupancies could be priced in one call —
two adults in one room and a family of four in another. The platform never delivered it: every
consumer read the FIRST entry and the room count came from the list’s SIZE, so a second entry was
priced as though it did not exist while still moving the count that volume discounts and availability
key off. Ask for several identical rooms with RoomConfiguration.quantity; ask for several DIFFERENT
occupancies as separate calls, and assemble them in your own cart.
object
Adults in this room. Defaults to 1.
Children in this room, by age. Ages are REQUIRED, not optional detail: properties price children in age bands, and an unpriceable age produces a rate the guest cannot actually book.
A number of children of the same age.
object
Age at check-in, in years. Zero is valid — infants are priced differently from older children, not ignored.
How many children of this age. Defaults to 1.
How many identical rooms of this configuration. Defaults to 1.
ISO 639-1 language, e.g. “en”. Affects the little text this surface returns; for descriptions and other prose, pass the language to the Content service instead. Defaults to “en”.
ISO 4217 currencies you want an exchange rate into, e.g. [“EUR”, “JPY”]. At most 10 distinct codes, each exactly three letters. Optional.
The platform currency (USD) is ALWAYS quoted in addition to these, so a caller reporting to an analytics
tool in USD needs to ask for nothing. Asking for the property’s own currency is allowed and yields a rate
of 1. A currency we hold no usable rate for is LEFT OUT of that property’s fx_quotes rather than failing the call,
so check the list for the currency you need. Only a missing USD rate fails the call, with NOT_FOUND.
Promotional codes to apply. Codes your account is not entitled to are ignored, not rejected.
Stable property identifier, as returned by Search and Lookup.
Url-safe slug, as returned by Search.
Transactional inventory to price alongside the room offers. Leave it empty — the default — and you get room offers only, which is the smallest response and what most booking flows need.
Each scope you add prices one more class of bookable item. Unrecognised values are ignored rather than rejected, so a client built against a newer version of this file still works.
This does NOT change what you are billed: the meter counts hotel-days from the itinerary, not sections of the response.
IANA media type for image delivery URLs: image/jpeg (the default), image/png, image/webp, or image/* to let the CDN negotiate per client from its Accept header. Empty means image/jpeg.
An unrecognised value is rejected with INVALID_ARGUMENT rather than quietly falling back, so a typo
like “image/jpg” (not a media type) fails loudly instead of serving you JPEG while you believe you
asked for something else. Whatever is served is echoed back on MediaUrlVariant.format.
Responses
Section titled “Responses”The RPC completed with gRPC status OK.
Response for GetPropertyInventory.
object
The property and its offers.
object
Stable property identifier. Your key for everything, including Content lookups.
Url-safe slug, stable and safe to build links from.
The property’s globally unique short code.
When this property’s record last changed, ISO-8601 “YYYY-MM-DDThh:mm:ss” in UTC. Use it as the cache key for your Content responses — the same value Search returns.
True if AT LEAST ONE offer is bookable for the requested stay. When false, room_types still carries
the unavailable offers rather than being empty, so you can show why.
Commission rate your account earns on this property, as a fraction — 0.15 for 15%. A property-channel term, so it holds for every offer below.
A ready-to-use delivery URL for a single representative image of the PROPERTY (as opposed to
RoomTypeOffers.hero_image_url, one per room), scaled to 1024px wide (Cloudinary’s medium variant —
c_scale, dpr_auto, f_auto, q_auto:best, w_1024). The lowest-sorted published image; unset when the
property has none.
Offers grouped by room type. room_types[0].rates[0] is the best available price, the same figure
Search would have shown for this property.
The offers for one room type, with the room’s own details carried once.
This grouping mirrors the pricing engine’s own shape (a room type holding its price configurations) and
the way a rate table is actually rendered. It exists because name and hero_image_url describe the
ROOM, not a rate: carried on RoomRate they were repeated across every rate x occupancy combination, which
on the Inventory surface is the whole cross-product for the property.
object
The room type. Join to Content for the full gallery, amenities and long-form descriptions.
The room type’s name, in the requested language where a translation exists.
A ready-to-use delivery URL for a single representative image of the room, scaled to 1024px wide (Cloudinary’s medium variant — c_scale, dpr_auto, f_auto, q_auto:best, w_1024). The lowest-sorted published image; unset when the room has none.
A plain URL rather than the full Multimedia object deliberately: this is a rates surface, not a content surface, and shipping the full media object – id, descriptions, every delivery-URL variant – for every room type on every rate response would make the payload about images instead of about rates. The complete gallery is in Content.
Every offer for this room, BOOKABLE FIRST and cheapest first within each group. On Search that is the single best offer; on Inventory it is every rate and occupancy combination for the requested dates.
Unavailable offers sort last, never first: they report 0 across all amounts, so ordering purely by price would put “not priced” ahead of the cheapest real rate.
One priced, bookable offer for the itinerary.
Everything here either is a price or changes which offer a traveller would choose. The prose behind the
identifiers — rate plan names, perk descriptions, cancellation policy wording — lives in Content, reached
via room_rate_id.
HOW THE PRICE IS BUILT, and what each field means:
- The property’s price for the room over these dates, with occupancy surcharges (an extra adult, an
extra child), any single-occupancy reduction, and any rate PREMIUM already applied. That is
unit_strikethrough_price— the “was” price, for ONE room. - Discounts come off it: promotions, and your channel or membership terms. The result is
unit_price— the cost of ONE ROOM, everything applied. The “now” price. - Fixed charges attached to the rate plan are added — a one-time cleaning fee and the like. Never
discounted, so they are reported separately as
unit_extra_chargesrather than folded in. - Multiply by
quantity, the number of identical rooms you asked for.
total = (unit_price + unit_extra_charges) x quantity
WHY THE SURCHARGES SIT INSIDE THE STRIKETHROUGH. A strikethrough is a promise that the traveller is
saving the difference, so the two prices must differ ONLY by things that are actually discounts. Put an
extra-adult surcharge outside it and the “was” price falls BELOW the “now” price for any room with a
third guest — which is why unit_strikethrough_price >= unit_price ALWAYS holds here, and why you can
render the strikethrough unconditionally instead of testing which number is larger first.
discount_percent is published rather than left to you, because there is exactly one correct rounding
of it and three plausible ones. The absolute saving is deliberately NOT published: it is
unit_strikethrough_price - unit_price, and a stored field that must always equal a subtraction is a
field that can one day disagree with it.
PER UNIT vs TOTAL. Fields prefixed unit_ are for ONE room over the WHOLE stay. total is the only
amount covering every room, and it is the number to charge. Nothing here is per NIGHT — divide by
room_nights if you need a nightly figure, and expect it to be an average, since rates vary by date.
MONEY CONVENTION. Every monetary field is an INTEGER IN MINOR UNITS of the enclosing property’s
PropertyCurrency.currency_code — divide by 10^currency_exponent to get the real value. The currency and
its exponent are stated once per property instead of on every amount, so a property’s amounts are directly
addable with exact integer arithmetic and no rounding.
The same vocabulary means the same things on the ancillary side — see AncillaryOffer.
object
Stable identifier for this offer, and the id you carry into a booking.
This IS the master rate identifier — it maps from roomRateIdentifier, whose own schema calls it
“Specified master rate identifier”. The name says room_rate for historical reasons and is kept rather
than duplicated: publishing a second master_rate_id field with the same value would be two
representations of one thing that can disagree, which this API forbids elsewhere for prices.
The rate plan this offer sells under. Resolve it with Content’s GetRatePlan; a rate plan is shared by many offers, so it is referenced rather than repeated on each one.
True if THIS offer, at the requested occupancy, is bookable for the itinerary. Narrower than
PropertySearchResult.available, which is true when ANY offer at the property is. An unavailable offer is
still returned rather than dropped, so you can show the traveller why it cannot be booked.
Check-in date, ISO-8601 “YYYY-MM-DD”.
Check-out date, ISO-8601 “YYYY-MM-DD”.
Nights covered by this rate.
Adults the rate is priced for, after the property’s occupancy policy is applied.
Children the rate is priced for, after the property’s occupancy policy is applied.
ONE room, whole stay, BEFORE discounts — step 1 above. The “was” price: render it struck through next
to unit_price.
GUARANTEED >= unit_price. Occupancy surcharges and rate premiums are already inside it, so the gap
between the two is only ever the discounts. Equal to unit_price when nothing is discounted, in which
case show no strikethrough.
ONE room, whole stay, after every adjustment — step 2 above. The “now” price, and the figure the discount is measured against. Excludes the rate plan’s fixed charges.
Not named net or source_total: everywhere else in this platform “source” means the PROPERTY’S OWN
CURRENCY, and this figure is in the caller’s. Reusing the word would have the next mapper author reach
for the domain’s getSourceTotal() and quote the wrong currency.
ONE room, whole stay — fixed charges attached to the rate plan, a one-time cleaning fee and the like. Reported separately so a traveller sees them as their own line rather than as an unexplained difference. NEVER discounted, which is why they sit outside both prices above. Zero when the rate plan has none.
What the guest pays for every room: (unit_price + unit_extra_charges) x quantity. This is the number
to show and the number to charge.
Only meaningful when available is true. An unavailable offer reports 0 across every amount, which
means “not priced” rather than “free” — show the offer with its reason, never with a price of zero.
Meals included in the rate. These change which offer a traveller picks, so they are here rather than behind an identifier.
object
Breakfast included.
Brunch included.
Lunch included.
Dinner included.
All meals included.
All meals plus alcohol included.
True if the stay can be cancelled without charge before the policy’s deadline.
How many days from TODAY the traveller has left to cancel free of charge, and -1 when the rate is not refundable at all. Zero means the free-cancellation window closes today.
Derived as (days until check-in) − (the policy’s days-before-arrival deadline), floored at 0 — the same
calculation RoomTypeBestPriceForDateRange publishes. It is relative to the moment of the response and
will drift as the stay approaches, so treat it as display data, not something to cache.
Cancellation policy identifier. Join to Content for the full terms and wording.
Identifiers of the perks bundled with this rate’s master rate. Every id resolves through
Content.GetProperty with CONTENT_SCOPE_PERK: match on the perk id for names and descriptions.
PropertyInventory.perks is not populated by Inventory.
There is deliberately no monetary “perk value” on this surface.
Identifiers of the add-ons purchasable ALONGSIDE this offer, resolved against
PropertyInventory.add_ons in the same response — the same dictionary pattern as perk_ids. Each id is
also an AddOnContent.id (Content.GetProperty, CONTENT_SCOPE_ADD_ON), which carries the add-on’s name,
descriptions and media; the priced rows here carry none of that.
Add-ons hang off the OFFER, not the property: the pricing engine matches them per room configuration
(RoomConfigurationPrice.addOnOffers), so which add-ons are available genuinely differs between two
rates on the same room. Flattening them to a property-level list would discard that.
INVENTORY ONLY, and only when you ask for it. Empty unless the request carried
INVENTORY_SCOPE_ADD_ON, and always empty on Search, which does not price add-ons at all.
How many identical rooms this offer is priced for — the quantity you set on the matching
RoomConfiguration. At least 1. What the PROPERTY has left is rooms_left, which is a different
number entirely.
It is echoed back because it is a MULTIPLIER on total, not merely a restatement of your request:
without it you cannot tell whether total covers one room or five. It can also change the unit price
itself, since a property may attach a rate to a room-count range — book three and every room may be
cheaper than booking one.
The saving as a fraction of unit_strikethrough_price — 0.15 for 15%. Zero when nothing is
discounted.
Published rather than left to you because there is one correct rounding of it and several plausible ones, and a paid API where two integrators compute “18%” and “17%” from the same response is a support burden rather than a saving. Quantity-invariant: discounting every room equally does not change the percentage.
Every adjustment that moved this rate away from the property’s plain price, one entry per kind, for showing a traveller WHY the number is what it is. Empty when nothing applied.
These reconcile exactly: the signed amounts sum to unit_price minus the property’s unadjusted price.
They are per-unit, like the prices they explain.
One reason this rate differs from the property’s plain price.
SIGNED, and the sign is the whole point: unit_amount is negative for anything that lowered the price
and positive for anything that raised it, so a caller can render a list of lines and add them up without
knowing which types are discounts. Amounts are per-unit minor units, matching the prices they explain.
The surcharges and premiums here are already inside unit_strikethrough_price, and the discounts are
the gap between it and unit_price. This message explains the arithmetic; it does not add to it.
object
What kind of adjustment this is.
How much it moved the price for ONE room over the whole stay, in minor units. Negative lowered the price, positive raised it. Never zero — an adjustment that changed nothing is not returned.
The property’s own wording for the promotion or premium in force, in English plus your requested language. Empty when neither applies.
At the RATE level, not on each adjustment, because that is how the platform stores it — one list of
descriptions covering the special rates that fired. Attaching a label to each PriceAdjustment would
have meant publishing a field nothing could ever fill.
A short piece of localized text.
The API returns descriptions already filtered to the language on your request, so you will normally see
exactly one entry per collection. language_code is still present so you can tell which language you got when
the requested one was unavailable and the property’s default was substituted.
object
Short label for this description.
The description body.
ISO 639-1 language code of name and description.
Promotional codes that were actually ACCEPTED and are reflected in the price above — not the codes you sent. A code your account is not entitled to is ignored rather than rejected, so comparing this against what you submitted is how you tell which ones bit.
Extras sold with THIS rate — in-room ancillaries. Empty when the rate sells none.
To book one, quote its id as BookingAncillaryRequest.offer_id and this rate’s room_rate_id as
inventory_id. Unlike every other ancillary the inventory id is the RATE, because the gate for
these is keyed by the master rate rather than by an entity.
One priced, bookable row of non-room inventory — a spa treatment, a table sitting, a tour departure.
PRICES ARE PER UNIT, and there is no total. RoomRate can publish one because the room count is in your
request; here the quantity is a choice the traveller has not made yet. An ancillary is quoted for ONE of
whatever pricing_type says it is sold by, and YOU multiply. Publishing a “total” that assumed one unit
— or assumed the whole party — would be a number that is wrong for most bookings but looks
authoritative.
HOW TO COMPUTE THE TOTAL. Take unit_price, then multiply by the quantity the traveller chooses and by
whatever pricing_type says it repeats over:
PER_STAY, PER_USE total = unit_price x quantity PER_NIGHT, PER_DAY total = unit_price x quantity x nights PER_HOUR total = unit_price x quantity x hours PER_PERSON, PER_ADULT, PER_CHILD total = unit_price x that headcount PER_PERSON_PER_NIGHT total = unit_price x headcount x nights PER_ADULT_PER_NIGHT total = unit_price x adults x nights PER_CHILD_PER_NIGHT total = unit_price x children x nights PER_PERSON_PER_HOUR total = unit_price x headcount x hours PER_ADULT_PER_HOUR total = unit_price x adults x hours PER_CHILD_PER_HOUR total = unit_price x children x hours
nights is the itinerary you asked to be priced; the occupancy is likewise yours to apply. Honour
min_pax/max_pax before quoting: a row is not bookable outside them.
MONEY. Minor units of the enclosing property’s PropertyCurrency.currency_code, exactly as on RoomRate, so ancillary and room
amounts are addable without conversion.
object
Stable identifier for this priced row. Distinct from AncillaryInventory.inventory_id: one
restaurant sells several sittings, and this identifies the sitting.
The row’s name, as the property wrote it — “1 hour session”, “Sunset departure”. Untranslated; the localized descriptions are on Content.
What one unit of unit_price buys, and therefore how to multiply it up. See the table above.
Price of ONE unit BEFORE discounts — the “was” price. Show it struck through next to unit_price.
GUARANTEED >= unit_price, exactly as on RoomRate, and equal to it when nothing is discounted.
Price of ONE unit after the property’s discount and your channel terms. This is the figure to
multiply up. Means the same thing RoomRate.unit_price means — the post-adjustment price of the thing
itself — and there is deliberately no total here, because the quantity is yours to choose.
Smallest party this row can be booked for; 0 when the property set no minimum.
Largest party this row can be booked for; 0 when the property set no maximum.
Promotional code that produced the discount, when one did. Empty when the price is simply the property’s own discounted rate.
The saving as a fraction of unit_strikethrough_price — 0.15 for 15%. Zero when nothing is
discounted. Same definition and the same reason for existing as RoomRate.discount_percent.
Quantity-invariant, which is what makes it safe here even though this message has no total: the percentage a traveller sees does not change with how many they buy.
True when the property requires this offer on every room it books. You do not add it and cannot remove
it: the platform adds it to each room at CreateBooking, whatever ancillaries carries, so a request
that omits it is charged for it all the same, and an entry you send for it is replaced.
IT IS A SEPARATE LINE, NOT PART OF THE ROOM PRICE. RoomRate.unit_price and its totals stay room-only;
show a mandatory offer beside the room as a required extra and add it to the total you display.
THE QUANTITY IS PER ROOM AND FOLLOWS pricing_type, not a choice you make:
PER_STAY, PER_NIGHT, PER_DAY, PER_USE, PER_HOUR quantity = 1 per room PER_PERSON, PER_PERSON_PER_NIGHT, PER_PERSON_PER_HOUR quantity = guests in that room (adults + children) PER_ADULT, PER_ADULT_PER_NIGHT, PER_ADULT_PER_HOUR quantity = adults in that room PER_CHILD, PER_CHILD_PER_NIGHT, PER_CHILD_PER_HOUR quantity = children in that room (never less than 1)
ADULTS AND CHILDREN ARE COUNTED BY THE PROPERTY’S CHILD AGE CUT-OFF, not by how you labelled the guests:
a guest older than the property’s children_minimum_age counts as an ADULT, one at or below it as a
CHILD, exactly as the room itself is priced. A property that declares no cut-off counts only the
room’s adults. Send every child’s real age in child_ages: it decides the quantity.
then multiply by nights as the table above says. With N identical rooms the charge is repeated N times. The stay runs from the property’s check-in time on the arrival date (14:00 when it declares none) to the same time on the departure date, so nights are whole nights. An hourly mandatory offer is priced for a single one-hour window starting at that check-in time, since there is no time window at booking.
Room types this offer may be sold with, as RoomTypeOffers.room_type_id. EMPTY MEANS EVERY ROOM TYPE.
An offer list is per property, not per room, so it carries restrictions you apply yourself: show an
offer beside a room only when this is empty or lists that room’s room_type_id.
CreateBooking answers INVALID_ARGUMENT, naming the offer, when you send one on a room it excludes. It
does not drop the offer: that would change the price you showed.
Rate plans this offer may be sold with, as RoomRate.rate_plan_id. EMPTY MEANS EVERY RATE PLAN.
Combine with room_type_ids: an offer must pass both. Same filtering and same refusal at CreateBooking.
The add-on this row prices, as AddOnContent.id from Content.GetProperty with CONTENT_SCOPE_ADD_ON. That
is where its name, descriptions and media are; this message carries only the price.
Set on the offers under PropertyInventory.add_ons AND on the priced rows of the other ancillaries
(restaurants, spas and so on): a venue’s rows are built from the add-ons categorised under it, so the row’s
id is the add-on id and this repeats it as the explicit join key. A venue’s own inventory_id is NOT an
add-on id; this field is what resolves a row to its package copy. EMPTY only when a row was not built from
an add-on.
Localized promotion copy the price calculator attached when a discount or surcharge applied, in English
plus your requested language. Empty when none applies. This is the OFFER’s own promotion text, not the
add-on’s description (that is on Content), and not promotion, which is a promo code.
A short piece of localized text.
The API returns descriptions already filtered to the language on your request, so you will normally see
exactly one entry per collection. language_code is still present so you can tell which language you got when
the requested one was unavailable and the property’s default was substituted.
object
Short label for this description.
The description body.
ISO 639-1 language code of name and description.
How many rooms the property still has for the WHOLE stay — the “only 2 left” number.
The SMALLEST nightly allotment across the stay, not the first night’s and not an average. A three-night stay whose middle night has two rooms free has two rooms left, however many are free either side: you cannot sell a third room a guest would be evicted from on night two. The check-out date is excluded, since nobody occupies a room that night.
NOT the same as quantity, which is how many rooms YOU asked for. The relationship is
rooms_left >= quantity whenever available is true — an offer that cannot house the party you
requested is not bookable and says so. Reading quantity as “what the property has” overstates
availability, which is why they are named as differently as they are.
Zero means none are free for these dates, and only ever appears alongside available = false. It is
a real answer, not a missing one.
This is the property’s true position, not a banded or capped figure. Treat it as commercially sensitive: it is exact enough to reconstruct a property’s booking curve if sampled over time.
The property’s own display order for its room types, ascending, when it has expressed one; 0 when it has not. This describes the ROOM, which is why it is here rather than on each rate – the domain defines it as “how the room types should be sorted and displayed”.
It is a merchandising hint, not the delivered order: room_types is already ordered by price.
NOT POPULATED by this RPC; always empty. Perks are static content: resolve each RoomRate.perk_ids entry
through Content.GetProperty with CONTENT_SCOPE_PERK, matching on the perk id. Every perk id on a
rate is a perk bundled with a master rate, and Content lists those, so every id resolves there.
Kept (number, name and type unchanged) so existing clients still parse; do not read it.
A perk bundled with a rate.
Lives here rather than in partner_content.proto because Search returns a deduplicated dictionary of these
alongside its rates (PropertySearchResult.perks): inlining the perk on every rate would repeat a handful of
distinct perks across every rate of every room type of every property in a search result. Inventory does NOT
return the dictionary (PropertyInventory.perks is deprecated and empty); resolve its perk_ids through
Content.
descriptions carries the display name and body together — Description has both.
object
Stable perk identifier. Matches an entry in RoomRate.perk_ids.
Localized name and description, in English plus your requested language.
A short piece of localized text.
The API returns descriptions already filtered to the language on your request, so you will normally see
exactly one entry per collection. language_code is still present so you can tell which language you got when
the requested one was unavailable and the property’s default was substituted.
object
Short label for this description.
The description body.
ISO 639-1 language code of name and description.
Whether the perk is guaranteed rather than subject to availability.
Perk tier; higher is more valuable.
Display order, ascending.
Every add-on referenced by any RoomRate.add_on_ids above, deduplicated the same way perks are.
Empty unless the request carried INVENTORY_SCOPE_ADD_ON. Unlike the collections below, add-ons are
NOT property-scoped: they are matched per room configuration, which is why the association lives on
each rate and only the priced rows are gathered here.
One non-room item the property sells, and its priced rows.
Deliberately thin. inventory_id is the SAME identifier Content returns as InventoryItemContent.id
for this item, so the name, descriptions, gallery, address, amenities and opening hours are one cached
Content call away and are never repeated here. What this message adds is the part Content cannot know:
what it costs, on your channel, right now.
object
Stable identifier of the item — the restaurant, spa, activity or add-on itself. Join to Content’s
InventoryItemContent.id for everything descriptive.
Which kind of item this is, so a single flat list can be grouped without inspecting ids.
True if this item earns your account commission.
Deprecated: only an add-on is sold, so only an add-on carries it. It is left unset for the venue types (restaurant, spa, meeting room, activity, attraction, place), which are static content. The field is kept so the wire contract does not break.
Commission rate on this item, as a fraction — 0.15 for 15%. Zero when commissionable is false.
Deprecated: unset for the venue types, as commissionable above. Kept so the wire contract does not break.
The bookable, priced rows for this item, CHEAPEST FIRST by unit_price. An item with no rows at all
is not returned, since it cannot be sold through this surface — Content is where you learn the
property merely HAS a spa.
Ordered for the same reason room rates are: a caller rendering “from ฿X” takes the first row, and an arbitrary order would make that figure depend on the property’s data-entry sequence.
One priced, bookable row of non-room inventory — a spa treatment, a table sitting, a tour departure.
PRICES ARE PER UNIT, and there is no total. RoomRate can publish one because the room count is in your
request; here the quantity is a choice the traveller has not made yet. An ancillary is quoted for ONE of
whatever pricing_type says it is sold by, and YOU multiply. Publishing a “total” that assumed one unit
— or assumed the whole party — would be a number that is wrong for most bookings but looks
authoritative.
HOW TO COMPUTE THE TOTAL. Take unit_price, then multiply by the quantity the traveller chooses and by
whatever pricing_type says it repeats over:
PER_STAY, PER_USE total = unit_price x quantity PER_NIGHT, PER_DAY total = unit_price x quantity x nights PER_HOUR total = unit_price x quantity x hours PER_PERSON, PER_ADULT, PER_CHILD total = unit_price x that headcount PER_PERSON_PER_NIGHT total = unit_price x headcount x nights PER_ADULT_PER_NIGHT total = unit_price x adults x nights PER_CHILD_PER_NIGHT total = unit_price x children x nights PER_PERSON_PER_HOUR total = unit_price x headcount x hours PER_ADULT_PER_HOUR total = unit_price x adults x hours PER_CHILD_PER_HOUR total = unit_price x children x hours
nights is the itinerary you asked to be priced; the occupancy is likewise yours to apply. Honour
min_pax/max_pax before quoting: a row is not bookable outside them.
MONEY. Minor units of the enclosing property’s PropertyCurrency.currency_code, exactly as on RoomRate, so ancillary and room
amounts are addable without conversion.
object
Stable identifier for this priced row. Distinct from AncillaryInventory.inventory_id: one
restaurant sells several sittings, and this identifies the sitting.
The row’s name, as the property wrote it — “1 hour session”, “Sunset departure”. Untranslated; the localized descriptions are on Content.
What one unit of unit_price buys, and therefore how to multiply it up. See the table above.
Price of ONE unit BEFORE discounts — the “was” price. Show it struck through next to unit_price.
GUARANTEED >= unit_price, exactly as on RoomRate, and equal to it when nothing is discounted.
Price of ONE unit after the property’s discount and your channel terms. This is the figure to
multiply up. Means the same thing RoomRate.unit_price means — the post-adjustment price of the thing
itself — and there is deliberately no total here, because the quantity is yours to choose.
Smallest party this row can be booked for; 0 when the property set no minimum.
Largest party this row can be booked for; 0 when the property set no maximum.
Promotional code that produced the discount, when one did. Empty when the price is simply the property’s own discounted rate.
The saving as a fraction of unit_strikethrough_price — 0.15 for 15%. Zero when nothing is
discounted. Same definition and the same reason for existing as RoomRate.discount_percent.
Quantity-invariant, which is what makes it safe here even though this message has no total: the percentage a traveller sees does not change with how many they buy.
True when the property requires this offer on every room it books. You do not add it and cannot remove
it: the platform adds it to each room at CreateBooking, whatever ancillaries carries, so a request
that omits it is charged for it all the same, and an entry you send for it is replaced.
IT IS A SEPARATE LINE, NOT PART OF THE ROOM PRICE. RoomRate.unit_price and its totals stay room-only;
show a mandatory offer beside the room as a required extra and add it to the total you display.
THE QUANTITY IS PER ROOM AND FOLLOWS pricing_type, not a choice you make:
PER_STAY, PER_NIGHT, PER_DAY, PER_USE, PER_HOUR quantity = 1 per room PER_PERSON, PER_PERSON_PER_NIGHT, PER_PERSON_PER_HOUR quantity = guests in that room (adults + children) PER_ADULT, PER_ADULT_PER_NIGHT, PER_ADULT_PER_HOUR quantity = adults in that room PER_CHILD, PER_CHILD_PER_NIGHT, PER_CHILD_PER_HOUR quantity = children in that room (never less than 1)
ADULTS AND CHILDREN ARE COUNTED BY THE PROPERTY’S CHILD AGE CUT-OFF, not by how you labelled the guests:
a guest older than the property’s children_minimum_age counts as an ADULT, one at or below it as a
CHILD, exactly as the room itself is priced. A property that declares no cut-off counts only the
room’s adults. Send every child’s real age in child_ages: it decides the quantity.
then multiply by nights as the table above says. With N identical rooms the charge is repeated N times. The stay runs from the property’s check-in time on the arrival date (14:00 when it declares none) to the same time on the departure date, so nights are whole nights. An hourly mandatory offer is priced for a single one-hour window starting at that check-in time, since there is no time window at booking.
Room types this offer may be sold with, as RoomTypeOffers.room_type_id. EMPTY MEANS EVERY ROOM TYPE.
An offer list is per property, not per room, so it carries restrictions you apply yourself: show an
offer beside a room only when this is empty or lists that room’s room_type_id.
CreateBooking answers INVALID_ARGUMENT, naming the offer, when you send one on a room it excludes. It
does not drop the offer: that would change the price you showed.
Rate plans this offer may be sold with, as RoomRate.rate_plan_id. EMPTY MEANS EVERY RATE PLAN.
Combine with room_type_ids: an offer must pass both. Same filtering and same refusal at CreateBooking.
The add-on this row prices, as AddOnContent.id from Content.GetProperty with CONTENT_SCOPE_ADD_ON. That
is where its name, descriptions and media are; this message carries only the price.
Set on the offers under PropertyInventory.add_ons AND on the priced rows of the other ancillaries
(restaurants, spas and so on): a venue’s rows are built from the add-ons categorised under it, so the row’s
id is the add-on id and this repeats it as the explicit join key. A venue’s own inventory_id is NOT an
add-on id; this field is what resolves a row to its package copy. EMPTY only when a row was not built from
an add-on.
Localized promotion copy the price calculator attached when a discount or surcharge applied, in English
plus your requested language. Empty when none applies. This is the OFFER’s own promotion text, not the
add-on’s description (that is on Content), and not promotion, which is a promo code.
A short piece of localized text.
The API returns descriptions already filtered to the language on your request, so you will normally see
exactly one entry per collection. language_code is still present so you can tell which language you got when
the requested one was unavailable and the property’s default was substituted.
object
Short label for this description.
The description body.
ISO 639-1 language code of name and description.
Bookable non-room inventory at this property, one entry per item — a restaurant, a spa, a meeting
room. Each carries only its identifiers and its priced rows; everything describing the item comes
from Content, joined on inventory_id.
Contains only the types you asked for in include, and is empty when you asked for none.
One non-room item the property sells, and its priced rows.
Deliberately thin. inventory_id is the SAME identifier Content returns as InventoryItemContent.id
for this item, so the name, descriptions, gallery, address, amenities and opening hours are one cached
Content call away and are never repeated here. What this message adds is the part Content cannot know:
what it costs, on your channel, right now.
object
Stable identifier of the item — the restaurant, spa, activity or add-on itself. Join to Content’s
InventoryItemContent.id for everything descriptive.
Which kind of item this is, so a single flat list can be grouped without inspecting ids.
True if this item earns your account commission.
Deprecated: only an add-on is sold, so only an add-on carries it. It is left unset for the venue types (restaurant, spa, meeting room, activity, attraction, place), which are static content. The field is kept so the wire contract does not break.
Commission rate on this item, as a fraction — 0.15 for 15%. Zero when commissionable is false.
Deprecated: unset for the venue types, as commissionable above. Kept so the wire contract does not break.
The bookable, priced rows for this item, CHEAPEST FIRST by unit_price. An item with no rows at all
is not returned, since it cannot be sold through this surface — Content is where you learn the
property merely HAS a spa.
Ordered for the same reason room rates are: a caller rendering “from ฿X” takes the first row, and an arbitrary order would make that figure depend on the property’s data-entry sequence.
One priced, bookable row of non-room inventory — a spa treatment, a table sitting, a tour departure.
PRICES ARE PER UNIT, and there is no total. RoomRate can publish one because the room count is in your
request; here the quantity is a choice the traveller has not made yet. An ancillary is quoted for ONE of
whatever pricing_type says it is sold by, and YOU multiply. Publishing a “total” that assumed one unit
— or assumed the whole party — would be a number that is wrong for most bookings but looks
authoritative.
HOW TO COMPUTE THE TOTAL. Take unit_price, then multiply by the quantity the traveller chooses and by
whatever pricing_type says it repeats over:
PER_STAY, PER_USE total = unit_price x quantity PER_NIGHT, PER_DAY total = unit_price x quantity x nights PER_HOUR total = unit_price x quantity x hours PER_PERSON, PER_ADULT, PER_CHILD total = unit_price x that headcount PER_PERSON_PER_NIGHT total = unit_price x headcount x nights PER_ADULT_PER_NIGHT total = unit_price x adults x nights PER_CHILD_PER_NIGHT total = unit_price x children x nights PER_PERSON_PER_HOUR total = unit_price x headcount x hours PER_ADULT_PER_HOUR total = unit_price x adults x hours PER_CHILD_PER_HOUR total = unit_price x children x hours
nights is the itinerary you asked to be priced; the occupancy is likewise yours to apply. Honour
min_pax/max_pax before quoting: a row is not bookable outside them.
MONEY. Minor units of the enclosing property’s PropertyCurrency.currency_code, exactly as on RoomRate, so ancillary and room
amounts are addable without conversion.
object
Stable identifier for this priced row. Distinct from AncillaryInventory.inventory_id: one
restaurant sells several sittings, and this identifies the sitting.
The row’s name, as the property wrote it — “1 hour session”, “Sunset departure”. Untranslated; the localized descriptions are on Content.
What one unit of unit_price buys, and therefore how to multiply it up. See the table above.
Price of ONE unit BEFORE discounts — the “was” price. Show it struck through next to unit_price.
GUARANTEED >= unit_price, exactly as on RoomRate, and equal to it when nothing is discounted.
Price of ONE unit after the property’s discount and your channel terms. This is the figure to
multiply up. Means the same thing RoomRate.unit_price means — the post-adjustment price of the thing
itself — and there is deliberately no total here, because the quantity is yours to choose.
Smallest party this row can be booked for; 0 when the property set no minimum.
Largest party this row can be booked for; 0 when the property set no maximum.
Promotional code that produced the discount, when one did. Empty when the price is simply the property’s own discounted rate.
The saving as a fraction of unit_strikethrough_price — 0.15 for 15%. Zero when nothing is
discounted. Same definition and the same reason for existing as RoomRate.discount_percent.
Quantity-invariant, which is what makes it safe here even though this message has no total: the percentage a traveller sees does not change with how many they buy.
True when the property requires this offer on every room it books. You do not add it and cannot remove
it: the platform adds it to each room at CreateBooking, whatever ancillaries carries, so a request
that omits it is charged for it all the same, and an entry you send for it is replaced.
IT IS A SEPARATE LINE, NOT PART OF THE ROOM PRICE. RoomRate.unit_price and its totals stay room-only;
show a mandatory offer beside the room as a required extra and add it to the total you display.
THE QUANTITY IS PER ROOM AND FOLLOWS pricing_type, not a choice you make:
PER_STAY, PER_NIGHT, PER_DAY, PER_USE, PER_HOUR quantity = 1 per room PER_PERSON, PER_PERSON_PER_NIGHT, PER_PERSON_PER_HOUR quantity = guests in that room (adults + children) PER_ADULT, PER_ADULT_PER_NIGHT, PER_ADULT_PER_HOUR quantity = adults in that room PER_CHILD, PER_CHILD_PER_NIGHT, PER_CHILD_PER_HOUR quantity = children in that room (never less than 1)
ADULTS AND CHILDREN ARE COUNTED BY THE PROPERTY’S CHILD AGE CUT-OFF, not by how you labelled the guests:
a guest older than the property’s children_minimum_age counts as an ADULT, one at or below it as a
CHILD, exactly as the room itself is priced. A property that declares no cut-off counts only the
room’s adults. Send every child’s real age in child_ages: it decides the quantity.
then multiply by nights as the table above says. With N identical rooms the charge is repeated N times. The stay runs from the property’s check-in time on the arrival date (14:00 when it declares none) to the same time on the departure date, so nights are whole nights. An hourly mandatory offer is priced for a single one-hour window starting at that check-in time, since there is no time window at booking.
Room types this offer may be sold with, as RoomTypeOffers.room_type_id. EMPTY MEANS EVERY ROOM TYPE.
An offer list is per property, not per room, so it carries restrictions you apply yourself: show an
offer beside a room only when this is empty or lists that room’s room_type_id.
CreateBooking answers INVALID_ARGUMENT, naming the offer, when you send one on a room it excludes. It
does not drop the offer: that would change the price you showed.
Rate plans this offer may be sold with, as RoomRate.rate_plan_id. EMPTY MEANS EVERY RATE PLAN.
Combine with room_type_ids: an offer must pass both. Same filtering and same refusal at CreateBooking.
The add-on this row prices, as AddOnContent.id from Content.GetProperty with CONTENT_SCOPE_ADD_ON. That
is where its name, descriptions and media are; this message carries only the price.
Set on the offers under PropertyInventory.add_ons AND on the priced rows of the other ancillaries
(restaurants, spas and so on): a venue’s rows are built from the add-ons categorised under it, so the row’s
id is the add-on id and this repeats it as the explicit join key. A venue’s own inventory_id is NOT an
add-on id; this field is what resolves a row to its package copy. EMPTY only when a row was not built from
an add-on.
Localized promotion copy the price calculator attached when a discount or surcharge applied, in English
plus your requested language. Empty when none applies. This is the OFFER’s own promotion text, not the
add-on’s description (that is on Content), and not promotion, which is a promo code.
A short piece of localized text.
The API returns descriptions already filtered to the language on your request, so you will normally see
exactly one entry per collection. language_code is still present so you can tell which language you got when
the requested one was unavailable and the property’s default was substituted.
object
Short label for this description.
The description body.
ISO 639-1 language code of name and description.
Announcements the property is running for the REQUESTED STAY — already filtered against your
itinerary, so everything here is meant to be shown. Empty unless the request carried
INVENTORY_SCOPE_ANNOUNCEMENT.
A message the property is running for the dates you asked to be priced.
Already resolved against your itinerary: a property’s announcements each carry either a date window or an “always show” flag, and both are evaluated server-side. Everything returned here is meant to be displayed, so there is nothing to re-check. The window and the flag are published for DISPLAY only – “valid until 31 March” – and filtering on them yourself would invite getting an inclusive bound subtly wrong.
object
Stable announcement identifier.
Localized title and body together, in English plus your requested language. Whether the title should
be rendered is show_title.
A short piece of localized text.
The API returns descriptions already filtered to the language on your request, so you will normally see
exactly one entry per collection. language_code is still present so you can tell which language you got when
the requested one was unavailable and the property’s default was substituted.
object
Short label for this description.
The description body.
ISO 639-1 language code of name and description.
True when the property wants the title shown above the body; false to render the body alone.
True when the announcement has no date window and is shown whatever the stay dates. When true,
start_date and end_date are empty.
First day the announcement applies, ISO-8601 “YYYY-MM-DD”. Empty when show_always is true.
Last day the announcement applies, inclusive, ISO-8601 “YYYY-MM-DD”. Empty when show_always is true.
The currency every amount in this property is in, and the exchange rates out of it.
object
ISO 4217 code the property’s amounts are denominated in, e.g. “THB”.
How many decimal places currency_code has: 2 for THB and USD, 0 for JPY, KRW and VND. Amounts are
integers in MINOR UNITS, so the real value is amount / 10^currency_exponent. Published rather than
assumed because a fixed divide-by-100 is wrong for a real slice of Wink’s inventory – a JPY stay priced
at 15000 is ¥15,000, not ¥150.
Exchange rates out of currency_code: the platform currency first, then each of
user_session.quote_currencies in the order you sent them, each once. A requested currency with no usable rate is
omitted.
Find a rate by its currency_code, never by position: when the platform rate is missing, the first entry is
a requested currency, not USD.
In a page of search results, a property whose currency has no usable platform rate is returned with the quotes
that do exist, possibly none, rather than failing the whole page: its amounts are in currency_code and are
correct either way, so treat an empty or short list as “no rate to convert with”, never as a zero rate. The
calls that price ONE property or a batch of them (Inventory.GetPropertyInventory, Inventory.GetRatePeriod and
Inventory.ListPropertyInventory, which is atomic) return NOT_FOUND instead.
An exchange rate from a property’s currency into one other currency.
object
ISO 4217 code this rate converts INTO.
Units of currency_code per ONE unit of the property’s currency, as a decimal string such as “0.0275”.
A string rather than a double so the digits you receive are exactly the digits we hold. Always positive.
When this rate was recorded, ISO-8601 in UTC, e.g. “2026-10-19T11:00:00Z”.
ISO 639-1 language the names in this response are in.
Example
{ "property": { "roomTypes": [ { "rates": [ { "adjustments": [ { "type": "ADJUSTMENT_TYPE_UNSPECIFIED" } ], "roomTypeAncillaries": [ { "pricingType": "PRICING_TYPE_UNSPECIFIED" } ] } ] } ], "addOns": [ { "type": "INVENTORY_SCOPE_UNSPECIFIED", "offers": [ { "pricingType": "PRICING_TYPE_UNSPECIFIED" } ] } ], "ancillaries": [ { "type": "INVENTORY_SCOPE_UNSPECIFIED", "offers": [ { "pricingType": "PRICING_TYPE_UNSPECIFIED" } ] } ] }}