Apify Toolkits

The price stack

What is inside the amount a guest pays, what Booking adds on top, and what sits outside it entirely - with the field holding each, and the one column to reconcile against.

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.

Three questions get asked about this data more than any other, all of them arithmetic:

“Are included charges already inside total? Which excluded total_amount values must be added exactly once? Does missing or empty excluded mean zero extras, or incomplete evidence?”

The answers are below the diagram, but the diagram is the answer: the mistake these guard against is vertical, so the fields are stacked in the order the money is.

What the guest pays, and which field holds it

rate level - mode `rooms` or `rates`, with Include extra charges on

    ┌──────────────────────────────────────────┐
    │  undiscounted rate                       │  price.before_discount
    └──────────────────────────────────────────┘
       −  deal / campaign                         price.discount
       −  mobile-only cut                         price.mobile_discount
          (what a desktop browser shows:          price.without_mobile_discount)
    ┌──────────────────────────────────────────┐
    │  net, before any tax or fee              │  price.base
    ├──────────────────────────────────────────┤
    │ + charges ALREADY INSIDE the quote       │  extra_charges.included[]
    │   (always 0 on a plain search row)       │  extra_charges.includedTotal
    ╞══════════════════════════════════════════╡
    │ = DISPLAY PRICE, what Booking shows      │  price.total  (= price.book)
    ╞══════════════════════════════════════════╡
    │ + charges BOOKING ADDS ON TOP            │  extra_charges.excluded[]
    │   VAT, city tax, resort / service fee    │  extra_charges.excludedTotal
    ╞══════════════════════════════════════════╡
    │ = ALL-IN, what the guest owes Booking    │  price.total_with_charges
    └──────────────────────────────────────────┘  ← reconcile against THIS one

Reconcile against price.total_with_charges, always. Not because total is wrong — on a room-list rate it is exactly the figure on the app’s price sheet — but because it is only sometimes right. Where Booking excludes tax from its display price, total understates what the guest pays; where the tax is already inside, the two columns are the same number. One column is correct on both paths, so reading it removes the branch.

total_with_charges = total + excludedTotal, added exactly once. Do not sum details[] by hand: inclusion on a search-row charge line is forced to excluded by construction, and summing only the lines you think are excluded is what once dropped an entire VAT line on every property in a VAT-charging country.

One difference between the two paths, and it is the only one that matters here: includedTotal is 0 on a plain search row and can be non-zero at rate level, because inclusion there comes from Booking’s own inclusion_type per item rather than from our normalisation.

What is NOT in that stack

Two different things, and conflating them is how a floor gets read as a bill.

Evidence gaps — money that may exist and cannot be quantified

whatwhere it says so
More charges Booking will not quantify, so the all-in is a floorextra_charges.chargesCompleteness = "incalculable"
A quantified amount beyond the itemised lineschargesCompleteness = "extra_charges", with extra_charges.chargesAmount
Inclusion Booking never stated — not “already paid for”extra_charges.unknown[], extra_charges.unknownTotal

excludedTotal: 0 means “nothing further to pay” only under chargesCompleteness: "all_included". Under incalculable the same zero means Booking declined to say, and a rate can itemise VAT, a tourism fee and a service charge and still be incalculable — the mode is about charges beyond the itemised ones.

Money that is really owed, just not to Booking

whatfield
Refundable damage deposit, taken at the desk, in the property’s currencydetails.damageDeposit (carries its own currency)
Cot or extra bed, paid at the propertychildAndBedPolicy.ageBands[]
A meal bought separatelyproducts[].policies.meal_plan.offers[], with breakfastPrice / breakfastCurrency on the row
Anything booked on arrivalnot on this surface

When it moves — timing, not amount

    booking ─────────── dueDate ─────────── check-in ──────── stay
       │                   │                    │
       amountDueNow        the rest falls       pay_at_property
       null = no stage     due (never the       settles here
       stated, NOT 0       cancellation date)

policies.payment.types is one of pay_now, pay_later, pay_at_property. policies.payment.choosable is true where the guest picks and false where the rate requires it.

Two fields read wrong more often than the rest, which is why timing is drawn separately from amount: