Apify Toolkits

Known limitations

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.

The one that costs most, because nobody thinks to ask

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:

propertyroom types on Booking’s pagereturned by roomList
&Here Tokyo Ueno144 (the same 4 for 2 adults and for 4)
Pfefferbett Hostel74
Richmond Kyoto128

And the shortfall is not detectable from the payload — soldout_rooms was [] on all eight properties probed. So three claims this data cannot support:

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.

Prices and charges

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.

Coverage and identity

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.

Conditional fields, which are not missing data

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.

Timing

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.

Ours

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.

Deliberate

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 .