🎉 Developer Free Trial — 1 week of full API access, no credit card required. Claim your trial →
Guide

Member rates vs public rates: why your scraped hotel price isn't the price the user pays

An anonymous scrape of an OTA returns the public rate. A signed-in user is very often shown a different, lower one. If your pipeline only collects the first, every number downstream of it is biased in the same direction — and you cannot see by how much.

The gap is documented by the OTAs themselves

This is not a scraping edge case. It is how the major online travel agencies are designed to work, and they publish the mechanics.

SourceProgrammeWhat the OTA states
Booking.com Genius Level 1 gives “10% discount on the price of your stay before taxes and charges” and requires you to sign in. Level 2, reached after 5 stays in 2 years, gives 10–15% plus breakfast and room upgrades on participating properties. (booking.com/genius.html)
MakeMyTrip MMTBLACK Gold and Platinum tiers both carry a “Min. 10% additional discount on select hotels”, and the programme terms state that you “must be logged in to your MMTBLACK account on MakeMyTrip while making the booking”. (MMTBLACK FAQs)
Agoda Member prices Agoda operates signed-in member pricing on top of its public rates. It does not publish a fixed tier-to-discount table, so the size of the gap has to be measured per property rather than assumed.
The important part is not the headline percentage. It is that the discount is conditional and variable — it depends on the property, the market, the date and the account's tier. There is no constant you can subtract from a public rate to recover the member rate.

Why an anonymous scrape structurally cannot see it

A member rate is not a parameter in the URL. It is a property of the session. The OTA resolves the price against an authenticated account — its tier, its market, sometimes its booking history — and serves a page that a logged-out request will never be given, no matter how good the proxy is.

That has three consequences that matter when you are designing a collector:

What this breaks downstream

Rate parity monitoring

A hotel compares its direct rate against OTA rates collected anonymously, sees parity, and concludes it is compliant. Meanwhile the signed-in guest is shown an OTA price below the direct rate. The parity report is clean and the actual undercutting is invisible.

Price intelligence and repricing

Any model that learns competitor behaviour from anonymous prices learns a shifted distribution. Because the shift is uneven across properties and markets, it does not cancel out — it re-ranks which competitors look expensive.

AI travel agents

An agent that quotes a price and then hands the user a booking URL creates an immediate, visible contradiction: the user lands on the page signed in and sees a different number. The failure is not silent, it is user-facing, and it destroys trust in the agent.

Metasearch reconciliation

Teams reconciling Google Hotels against per-OTA scrapes routinely find the metasearch figure lower than every OTA price they collected, and write it off as staleness. Frequently it is not staleness — it is an authenticated or channel-specific rate they never captured.

How to tell whether your own data has the problem

You do not need a vendor to run this check. You need one afternoon and an account you already own.

1
Pick a fixed basket. 30–50 properties across at least two markets, one check-in date 21–30 days out, a fixed 2-night length of stay and a fixed occupancy. Hold every one of these constant; they all move price on their own.
2
Collect twice, close together. Once logged out, once in a browser signed into your own OTA account, within the same short window so real rate movement does not contaminate the comparison. Capture the same room type on both sides.
3
Normalise before comparing. Put both sides on the same tax and fee basis and the same currency. Booking.com's Genius discount, for example, is stated as applying before taxes and charges — comparing a pre-tax number to a post-tax one will manufacture a gap that isn't there.
4
Report the distribution, not the mean. Look at the share of properties where the two differ at all, and the spread of the difference. A mean hides the fact that the gap is near zero on many properties and large on a few — which is exactly what makes it dangerous to model.
Read the terms before you automate this. Collecting prices from an account is a different question, legally and contractually, from collecting public pages, and the answer depends on the OTA's terms and your jurisdiction. Do the one-off diagnostic manually in a browser. If you decide to run it continuously, get the account arrangement reviewed properly first.

What a price row needs to carry

Once you accept that a hotel price is not a single number, the fix is mostly schema discipline. Any row that does not answer all of the following is not comparable to any other row:

FieldWhy it is not optional
authenticatedWhether this price came from a signed-in session at all. Without it, public and member rates silently pool into one series.
member_price_confirmedSigned in is not the same as served a member rate. Many properties show the member the same price. You need to distinguish "we were logged in" from "a member rate was actually applied".
room_typeThe single largest source of false price differences. Two rows for the same hotel and date are not comparable if they are different rooms.
tax_inclusive / fee basisDetermines whether a 10% gap is real or an artefact of comparing pre-tax to post-tax.
currency + localeOTAs price by market. The same property on the same date has different numbers by point of sale, before any exchange rate is applied.
captured_atRates move. A comparison across a multi-hour collection window is measuring time as much as it is measuring price.
Property identityCovered separately — matching a hotel across OTAs by name is its own failure mode. Prefer a deterministic key; see Place ID lookup.

How ScrapeGuys handles it

Full disclosure, this is our product — but the mechanics are worth stating plainly because most vendors do not offer this at all.

The OTA Hotel Rates scraper prices a stay as a signed-in member on Agoda, Booking.com and MakeMyTrip. You send signed_in: true and we use one of the accounts provisioned for your key; alternatively you can supply your own credential with own_account and we scrape as that account instead. Every result carries the authenticated and member_price_confirmed provenance fields described above, so a member rate is never silently mixed into a public-rate series. Provisioned accounts are enabled per client.

For the comparison side, the Google Hotels scraper returns 12–25 OTAs per hotel with the property resolved by Google Place ID — deterministic, so you are not matching hotels on name strings — and batches up to 20 hotels per call. Full parameters and response schema are in the API docs; job times, rate limits and coverage are in the FAQ.

Where we are not the answer: if you only need public rates for a handful of properties, you do not need authenticated pricing and you should not pay for it. This matters when your users transact on the price you show them, or when a parity claim rests on it.

Questions we get about this

No. A coupon is applied at checkout to a price you can already see. A member rate changes the rate served to the page, and on some properties it changes which room types and supply channels are offered at all. You cannot reconstruct it by subtracting a percentage from the public price.

You can detect that a gap exists — a metasearch result persistently below every public OTA price you collect is a strong signal. You cannot measure it, because the discount varies by property, market, date and tier.

Agoda, Booking.com and MakeMyTrip. Results carry provenance fields so you know whether a row is a public rate or an authenticated one.

It depends on the property, date and tier, so any single headline number is misleading. Booking.com publishes its own floor: Genius Level 1 is 10% and Level 2 is 10–15%, before taxes and charges. Treat that as a lower bound on one source, not an industry average.

Sources