How to monitor hotel rate parity
Published August 26, 2026 · Updated August 28, 2026
Short answer: Monitoring hotel rate parity means running identical API requests with
only proxy_country varied, repeated several times per market, and comparing spreads
rather than single readings. FlightPowers' Booking.com endpoints take proxy_country as
a two-letter code; a controlled repeat-sampled run found real but modest,
property-dependent gaps, not a single fixed percentage.
Rate parity is the principle (and usually the contractual expectation) that the same room, on the same dates, should cost the same wherever and however it is sold. Hotels promise it to distribution partners; OTAs enforce it in their agreements; and in practice it drifts, because pricing systems segment by market: the rate a booking site quotes can depend on the country the visitor appears to browse from.
That drift is why revenue managers watch parity. A room quietly selling cheaper to one market than another is margin leaking in a direction nobody chose: it can undercut the hotel's own direct channel, breach a partner agreement, or hand a competitor an opening. But watching it manually means opening the same booking page through VPN endpoints in three countries and eyeballing the numbers: nobody sustains that daily.
This guide shows the programmatic version: identical API requests with one parameter varied, repeated enough times that the answer means something.
How does proxy_country work?
Booking.com shows different rates depending on where the visitor is browsing from.
Every endpoint on the
Booking Live API
accepts proxy_country, a two-letter
lowercase code (us, de, il) that routes that request through a residential
proxy in that country, so the response is what a real visitor from that market
sees, not what your server's location sees. Leave it out and the request goes through
the global pool.
A parity check is therefore the same request, with only proxy_country changed, run
several times per market:
import requests
HEADERS = {
"Content-Type": "application/json",
"x-rapidapi-host": "booking-live-api.p.rapidapi.com",
"x-rapidapi-key": RAPIDAPI_KEY,
}
SAMPLES = 3 # one reading per market is not a comparison
ranges = {}
for country in ["de", "jp"]:
prices = []
for _ in range(SAMPLES):
r = requests.post(
"https://booking-live-api.p.rapidapi.com/hotel_by_name",
headers=HEADERS,
json={
"hotel_name": "Rixos Sungate",
"area": "Antalya",
"checkin_date": "2026-10-05",
"checkout_date": "2026-10-10",
"currency": "USD",
"proxy_country": country,
},
)
hotel = r.json()
if hotel["available"]:
prices.append(hotel["price"])
ranges[country] = (min(prices), max(prices)) if prices else None
print(country, ranges[country])
de, jp = ranges["de"], ranges["jp"]
if de and jp and (de[1] < jp[0] or jp[1] < de[0]):
print("gap held across every sample")
else:
print("ranges overlap: movement, not a parity break")
The loop is the whole method, and the SAMPLES line is the part most write-ups skip.
Hold the property and the dates fixed, ask each market more than once, and compare the
ranges rather than two single readings.
/hotel_by_name takes the property name a human would type (plus an optional area
to disambiguate) and returns one headline rate with the room type, so every response is
directly comparable to every other. Naming the property matters: comparing "the first
result" of a destination search compares result ordering, which churns between identical
requests, not pricing. For a room-by-room comparison there is /hotel, which returns the
full room list for a property.
A repeat-sampled run, captured
On 2026-08-28 we ran exactly that check under controlled
conditions: three Rome properties, three markets, 3
identical requests per market, only proxy_country varied.
| Property | Germany "de" | Japan "jp" | United States "us" |
|---|---|---|---|
| Favola Romana - Guest House | 243 | 234 | 242–258 |
| Suite della Pigna | 486 | 467 | 482–523 |
| Suites 51 | 477 | 460 | 473–512 |
3 identical requests per market. One number means every request came back the same; a range means the market moved on its own, by up to 9% here.
Two things fall out of it, and only one of them is the headline.
Japan came in under Germany on all three properties, by about the same proportion each time, and both markets returned an identical number on every one of the three requests. That is a difference you can act on: the ranges do not overlap and they did not move.
The US market did not hold still. On repeat runs of the same property it came back at different numbers between identical requests, and the size of that movement was larger than the Germany–Japan gap. Anchor a comparison on a market that behaves like this, take one reading from each side, and you can produce a "gap" that is entirely an artefact of when you asked.
So the practical rule is short: rates move, so sample each country a few times before
you call a gap real. A gap counts when one market's whole range sits below the other's.
Overlapping ranges are movement, not a parity break. (The raw numbers ship in this site's
source as a fixture, src/lib/fixtures/hotel-geo-repeat-rome.json, so they are checkable
rather than decorative.)
Two methods that look fine and are not
- One request per country. The most common write-up of this check, and the reason phantom gaps get reported. It cannot distinguish a market being quoted differently from a rate that happened to move between two calls.
- Comparing "the first result" of a destination search. Result ordering churns between
identical requests, so a first-result delta measures ordering, not pricing. Name the
property with
/hotel_by_name, or match on the property and room type before you compare anything.
The honest part: often nothing is drifting
Do not build the pitch deck off one screenshot. A single-reading capture from 2026-08-26 (Kremlin Palace, three markets, same dates) came back at $1319 / US$1,318 / US$1,318: near-identical quotes, differing by a rounding artefact. And a separate held-constant run on a chain hotel on 2026-08-28 returned the identical price from every market we tried: chain properties under parity contracts can show no gap at all.
Gaps are property-dependent. Some properties segment by market and some simply do not, and a monitor has to report both honestly rather than manufacture drama.
The same caution applies to the readings that look exciting. A capture from
2026-08-26 (Rixos Sungate - The Land of Legends Access,
Marine Room, same dates) came back at
$1,771 from us against
US$1,966 from de and il. It is a real capture and
it ships as a fixture (src/lib/fixtures/hotel-geo-rixos.json, booking links included), so
the numbers are checkable. But it is one request per market, anchored on the market our
repeat runs showed moving on its own, so what it establishes is "re-sample this property",
not "this property breaks parity by X%". That is the whole reason the sampling loop above
has a SAMPLES line.
The operational consequence: alert on gaps that survive sampling, don't assume them. A parity monitor that runs daily and stays silent for weeks is working, not broken. The value is the morning it isn't silent, with enough samples behind the number that the conversation is about the rate rather than about your method.
How do you schedule it?
A parity check is a naturally periodic job. Two common shapes:
Cron. Wrap the loop above in a script, run it daily, and send yourself a message when
one market's whole sampled range clears a threshold you choose against another's (absolute
or percentage: percentage travels better across properties). Persist every raw response,
not just the verdict: when you escalate a parity breach, the evidence is the set of
responses, each with its price_string, room_type and booking link.
n8n or another workflow tool. Schedule trigger → several HTTP requests per market → compare the ranges → notify. The same pattern as a fare watch, which is written up step by step in Using a flight API in n8n: swap the flights node for the hotel request and the verdict condition for a non-overlapping-range test.
Two practical notes for scheduled sweeps:
- Rate limits on the hotels API are much lower than on flights: see /pricing for the current per-plan numbers. A comp set of properties across several markets is a queue, not a burst; space the requests.
- Compare like with like. The response includes
room_type: check it matches across markets before alerting on the price. A cheaper quote for a different room is not a parity breach, it is inventory. - Budget for the repeats. Sampling multiplies the request count: two markets at three samples each, across ten properties, is sixty requests per run. That is the cost of a number you can defend, and it is what the larger tiers are for.
Beyond parity: the same loop, other questions
The identical mechanism, sampled the same way, answers adjacent revenue questions: geo-pricing analysis
(which markets is a competitor discounting into?), competitive-set tracking
(/hotel_by_name across your comp set, on the same schedule), and market-entry
research. The Geo-Pricing page documents the endpoint
parameters in full, and the free
Hotel Price by Country tool runs the check in your
browser, sampling each of two markets three times, so you can see the response shape
before writing any code.
Related
- Geo-Pricing API: the endpoint documentation
- Hotel Price by Country: the free in-browser version of this check
- Using a flight API in n8n: the scheduling pattern, step by step
One parameter, a few samples, an answer you can defend
Live Booking.com rates priced from any market with proxy_country. Free tier on RapidAPI, no card to try.
Free tier: 10 requests/month. No card to try.