What this surface cannot give you, with the measurement that says so - starting with roomList returning bookable room types rather than the property catalogue.
Describes build 1.0.173 of rl1987/booking-api-scraper, deployed 2026-10-29. The column set last changed 2026-10-29. Earlier builds can return a different one.
What this surface cannot give you, with the measurement that says so. Every limit here is documented somewhere else too — inside a field description, in a code comment, in a closed issue — and the point of this page is that a buyer should not have to assemble the list themselves.
Each entry is tagged, because the first thing worth knowing about a limitation is who could fix it:
An entry with no measurement does not belong here: unevidenced, a limitation is indistinguishable from a guess, and the value of this page is that you can check it. When one stops being true it is deleted rather than edited into a history — Upgrading on the Store page is where change lives.
roomList returns bookable room types, not the catalogue — upstream
The room list gives the room types with availability for the stay you asked for, not every room type the property has. Measured 2026-10-22 against six property pages:
| property | room types on Booking’s page | returned by roomList |
|---|---|---|
| &Here Tokyo Ueno | 14 | 4 (the same 4 for 2 adults and for 4) |
| Pfefferbett Hostel | 7 | 4 |
| Richmond Kyoto | 12 | 8 |
And the shortfall is not detectable from the payload — soldout_rooms was
[] on all eight properties probed. So three claims this data cannot support:
rooms[] is a minimum over available types, not over
the property.rooms[] is not “no rooms exist”, and a productCount that shrinks
between runs may be availability rather than a catalogue change.This was documented the wrong way round until 2026-10-22 — the code comment said
roomList returned the full catalogue. Build 1.0.93 and earlier carry that claim.
Genius and loyalty-tier prices are unobtainable anonymously — upstream
genius_discount_percentage was present and exactly 0 on 1,385 of 1,385
blocks across 72 properties in six markets; 0 of 180 search rows carried a
Genius badge. All three of the app’s Genius flags change nothing, and no request
parameter sets a level — it comes from the authenticated profile. See also the
deliberate entry on logging in, which is why this is a proven absence rather
than an untested gap.
No booker-country parameter exists — upstream
The point of sale follows the proxy exit IP, not an input. Measured
2026-10-27 as a two-arm run differing only in exit country: on the hotel_ids
path a GB exit returned tax inside the display price and a US exit returned
it excluded, reproducing the flip exactly. bookerCountry was an input that
claimed to decide this and was read by nothing — removed in #70 — so each row
now reports what it actually got in provenance.exitCountry, confirmed on 15 of
15 accommodation rows in the 2026-10-28 sweep.
One caveat on that column: a residential proxy pool without a session rotates
per request, and five samples gave five countries, so where the exit is not
stable within a proxy generation the field is null rather than a confident
guess.
The search payload’s charge lines cannot be thickened — upstream
ChargeDisplayIrene has exactly four fields. 43 candidate field names were put
to the server on 2026-10-22 and every one was rejected; chargesInfo was null
and taxExceptions empty on 6 of 6 live rows. Charge names, percentages and
per-unit amounts need includeRooms — a plain search row cannot have them at
any price.
There is no charge-type id → name table in the app — upstream
ARB’s clean negative, 2026-10-22 (job 20261014-153430-com.booking, queue
item 142):
TOURISMFEE, MUNICIPALITYFEE and HERITAGECHARGE appear zero times across
24 dex files. The charge identity is Booking’s own localised name; unmapped
ids stay TYPE_<n> and the map grows only by observation.
Under incalculable, the all-in figure is a floor and nothing closes it — upstream
chargesCompleteness: "incalculable" is Booking saying more may apply without
quantifying it — on 288 of 1,035 room-list blocks measured, whole markets at
a time (Kuala Lumpur and Tokyo entirely, Amsterdam and London not at all). Rate
level only: search and hotelPage carry no equivalent field. See
the price stack
.
Pricing in the property’s own currency costs a lookup, and it can fail — upstream
Booking’s rate endpoints do not accept “price this in whatever the property
quotes”. Left empty, currency_code makes mobile.roomList answer with an
object carrying no keys at all — 5 of 5 properties in four markets, measured
2026-10-29 with the arms run in both orders — and mobile.hotelPage return 3
keys where an explicit code returns 64. (The same empty parameter does mean
what it says on the GraphQL search surface, which is why Property’s own
currency works for a search and not for a rate.)
So the Actor resolves the code instead: it reads each property’s own currency
from its content, once per property per run, and asks for that code by name.
Where a property’s content does not state one, that property’s rates are lost
— not the property: the row arrives without its room list, the run logs a
warning naming the Currency field, and own_currency_unresolved appears in the
run summary. Naming an explicit currency skips the lookup and the failure mode
with it.
Destination search stops at roughly 1,000 properties — upstream
Past roughly 1,000 per destination Booking keeps answering with full
pages of properties it has already sent, and never says it has stopped. Every
scraper on this API inherits the ceiling. deepSearch works around it by
bisecting the destination into slices that each fit under it, and because
Booking reports the destination total a run states its own coverage rather than
guessing — e.g. 5,822 of 5,844 properties (99.6%) in the log. The slices have
a ceiling of their own, so 100% is not promised.
starRating does not say whether the stars are official — upstream
Booking awards some properties an official classification and lets others
self-estimate. The mobile surface returns symbol: "STARS" for both, with
no second field distinguishing them, so a 3 can be either and ranking by it
mixes the two. Null is common and legitimate — hostels and many apartments carry
no rating at all. reviewScore can likewise differ by 0.1 from the partner API
for the same property on the same day, measured 2026-10-21 against Booking’s own
connector — both their numbers, from different surfaces.
Property URLs are not derivable — upstream
Slugs are server-side; unavailable ones come back null rather than guessed.
Read url, do not rebuild it. Validating them yourself is unreliable too: 2 of
14 real pages needed up to three attempts, so a single-shot check calls a
working link dead.
A catalogue attraction row has no coordinate of its own — upstream
cityLatitude / cityLongitude are the destination centroid, so every product
in a city shares one point: 21 products across three cities returned three
pairs, one per city, and 100 Rome catalogue rows on 2026-10-28 returned 2. The
product’s own coordinates exist only on an attractionDetails row, and fill on
roughly half the products measured (12 of 36). Plotting a catalogue stacks the
whole city on one pin.
shortDescription is a catalogue field — upstream
The attractionDetails surface returns null for it on every product measured
(0 of 6 Rome products that filled it on their catalogue rows). The detail row
carries the full untruncated text as description instead.
paymentTerms.requestToBookRequired is null outside vacation rentals — upstream
Null on most properties, and that is the answer rather than missing data: it appears only on the vacation-rental segment, where Booking offers Request to Book at all, and null elsewhere means the question does not apply. Identified 2026-10-23 in #54, where it had read as a null column across an entire hotel-shaped sample before the segment was identified.
dueDate is absent on some prepayment rates and has no fallback — upstream
Missing on 5 of 17 no_prepayment_until rates. Both obvious substitutes are
the cancellation date, which is a different thing (#55).
The accepted-methods list does not distinguish at-property from via-Booking — upstream
houseRules.paymentMethods.acceptedCreditCards gives the list and no field
beside it splits at-property from via-Booking. ARB confirmed the shape from the
app, 2026-10-22: the two property queries that carry it select
{id, name, nameEn} and nothing more. So “accepts Visa” cannot be narrowed to
“accepts Visa online” from this surface.
Booking states no cache age — upstream
Every row carries scrapedAt and scrapedAtUnix — the instant the response
that priced it came back, not the end of the run: a 60-row run measured
2026-10-27 spanned 16 seconds over 12 distinct values, and RUN_SUMMARY.window
bounds the whole run. What is not available is anything about Booking’s own
freshness: the upstream states no cache age, and the responses carry no header
that gives one. Prices are fetched live per run, so a later dataset read does
not refresh them.
Gaps we could close. Each carries its issue number, so you can see whether it has moved since this page was written.
Attraction rows do not say which market priced them — ours (#83)
Measured 2026-10-28: 0 of 209 attraction rows carried a provenance object,
where every accommodation row did. Attraction rows have prices, and the point of
sale follows the proxy exit IP, so a priced row that cannot name its market has
the same gap #70 closed for accommodations.
An error row carries no timestamp — ours (#83)
An unresolvable attraction id produces a four-key row — recordType,
attractionId, error, status — with no scrapedAt. Measured 2026-10-28: 0
of 2 stub rows carried one, against 124 of 124 ordinary rows in the same sweep.
A consumer filtering a feed by scrapedAt silently loses exactly the rows that
record a failure.
No login, ever — deliberate
Member rates, loyalty tiers and anything else behind an account are out of reach by choice. “Needs an account” closes a request here rather than queueing it, which is also what makes the Genius answer above a proven absence.
No prices in this documentation — deliberate
The Pricing tab is generated and plan-tiered, so a figure in prose would be a second copy nobody updates. What each mode bills, and what one unit is, is in what each mode bills .