Apify Toolkits

Watched rates

One row per watched product id: whether that exact rate is still on sale for your stay, and at what price. A rate that has gone still occupies a full row, with status and reason saying why.

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.

Modes: rates
Dataset view: rates — ?view=rates on the items API, or the matching tab in the Console.

One row per watched product id: whether that exact rate is still on sale for your stay, and at what price. A rate that has gone still occupies a full row, with status and reason saying why.

27 columns, in the order the view shows them.

#ColumnLabelTypeDescriptionExample
1scrapedAtScraped atstring, nullableWhen the response that produced this row came back - the PRICED response where the row has a price, not when the row was pushed, which is batched and later by seconds to minutes. ISO-8601 UTC, to milliseconds. Note Booking’s own date strings in the same row are NOT ISO (freeCancellationUntil is “2026-10-28 23:59:59 +0000”), so do not point one parser at both. Prices are fetched live - nothing in this Actor is cached or reused - so this is the age of the price, and a dataset read a week later does not refresh it.2026-10-27T12:53:42.118Z
2scrapedAtUnixScraped at (epoch)integer, nullableThe same instant as scrapedAt, in epoch SECONDS, for consumers that diff timestamps arithmetically. Two keys rather than one selectable format, so a column’s type never moves with a run setting and two datasets always join.1793105622
3productIdProduct IDstring, nullableRates mode: the watched rate. Shaped room_id_rateplan_occupancy_board_0, and only meaningful together with the stay it came from - the id encodes no dates.17622630_89064837_1_0_0
4hotelIdHotel IDinteger | number, nullableRates mode: the property the watched rate belongs to, derived from the product id.176226
5roomNameRoomstring, nullableRates mode: the room type the rate sells.Standard Twin Room
6statusStatusstring, nullableRates mode: “available”, “not_offered” or “lookup_failed” - the last means we could not ask, the middle means Booking answered and the rate is not on sale. attractionAvailability mode: null means dates were bookable inside your window; no_availability_in_window is the real negative - days were checked and none were bookable; window_outside_calendar means the attraction is real but your window falls outside the span Booking publishes, so nothing was checked at all and calendarFrom/calendarTo give that span; not_found means the id resolved to nothing. That last one is INFERRED rather than reported: Booking answers an unknown id with a successful but empty calendar rather than an error, so an empty calendar is the discriminator - and it also covers an attraction withdrawn from sale. Telling the three negatives apart matters, because only the middle one means the attraction has no dates for you.available, not_offered
7availableAvailableboolean, nullableRates mode: true when status is “available”.
8reasonReasonstring, nullableRates mode: why a watched rate is not available, in words.
9checkinCheck-instring, nullableStay start, yyyy-mm-dd, echoed from the input.2026-10-31
10checkoutCheck-outstring, nullableStay end, yyyy-mm-dd, echoed from the input.2026-11-01
11adultsAdultsinteger | number, nullableAdults the rate was priced for (rates mode).1
12roomsRequestedRooms requestedinteger | number, nullableRooms the rate was priced for (rates mode). Named roomsRequested and not “rooms” because rooms is the room-type list on every other row kind.1
13currencyCurrencystring, nullableISO 4217 code every money figure on the row is in.EUR
14pricePriceobject | number, nullableOn a property row, the price object: base, book/total, per_night and extra_charges. On a rate or attraction row, a plain number. Read total_with_charges to match what the buyer is shown - it is right on every path, which total is not. Where Booking excludes taxes from its display price, total understates: measured on a search row, display EUR 246.28 with EUR 82.50 of excluded charges beside it. Where the taxes are already inside gross_amount, the two are the same number and both match the screen: measured on a room-list rate, MYR 151 on the app’s price sheet against MYR 151.305 on the wire. discount_percent is not Booking’s badge: it is computed from Booking’s own figures and moves with the point of sale, so use it to rank savings within a run rather than to reproduce what a browser shows.
15priceWithChargesPrice with chargesnumber, nullableRates mode: the all-in amount for the watched rate. The one to diff between runs, not price.81
16pricePerNightPrice per nightnumber, nullableRates mode: price divided by nights.
17cancellationTypeCancellationstring, nullablefree_cancellation, non_refundable or partially_refundable. partially_refundable means only part of the stay is charged after the deadline; those rates used to read as fully refundable, which overstated the refund.free_cancellation, non_refundable
18freeCancellationUntilFree cancellation untilstring, nullableWhen free cancellation ends. Not ISO-8601 - Booking returns “2026-10-28 23:59:59 +0000”.2026-10-28 23:59:59 +0000
19prepaymentPolicyPrepayment policystring, nullableBooking’s own policy string, passed through rather than mapped to a closed set. Observed: full_prepayment, partial_prepayment, no_prepayment_at_all, no_prepayment_until.full_prepayment, no_prepayment_until
20paymentTypesPayment timingarray, nullableWhen the guest pays: pay_now, pay_later and/or pay_at_property. Decided by the policy, not by any single flag.[“pay_now”, “pay_later”]
21mealPlanMeal planstring, nullableBoard included in the rate. no_plan does not mean no breakfast: it means none is included, and 40% of blocks measured sell one for a fee - read breakfastAvailableForFee for that.no_plan, breakfast_included
22breakfastAvailableForFeeBreakfast for a feeboolean, nullableWhether breakfast can be bought with this rate but is not included in it. True on 145 of 366 blocks measured, every one of which mealPlan calls no_plan.true, false
23breakfastPriceBreakfast pricenumber, nullableWhat breakfast costs on top of this rate, as the property quotes it: a unit price per person per night, not a stay total (A/B’d invariant across 2 and 4 adults, 2 and 4 nights). Read it with breakfastCurrency, never with the row’s currency. Null on the hotel_ids path, which publishes the offer but not the amount.18.9
24breakfastCurrencyBreakfast currencystring, nullableThe currency breakfastPrice is quoted in - the property’s own, which can differ from the row’s currency. A Cape Town property returns 120 ZAR on a run priced in EUR; pairing that amount with EUR overstates it twentyfold.EUR, ZAR
25provenanceProvenanceobject, nullableWhat produced this row’s price: the proxy exit it was fetched through, the generation of that exit, the currency you asked for and the endpoint that priced it. The point of sale follows the proxy exit country, not any input - bookerCountry was removed in this build because nothing read it.
26recordTypeRecord typestring, nullableWhich kind of row this is. Absent on property rows (search, rooms, details, surroundings, reviews, reviewScores); “rate” in mode rates; “attraction”, “attractionDetail” or “attractionAvailability” in the attractions modes. One dataset can hold several kinds, so filter on this before reading kind-specific columns.rate, attraction
27productFull rateobject, nullableRates mode: the whole rate object, for everything the hoisted columns omit.

Inside the nested columns

Some columns above carry an object, or a list of objects, with documented members of their own. Each table below lists the members documented at ONE level; a member that is itself an object names the table it continues in, and [] in a path means a list.

row
├─ price
├─ provenance
└─ product
   ├─ policies
   │  ├─ meal_plan
   │  │  └─ offers[]
   │  ├─ payment
   │  └─ deposit
   └─ price

price — Price

Parent chain: row → price

On a property row, the price object: base, book/total, per_night and extra_charges. On a rate or attraction row, a plain number. Read total_with_charges to match what the buyer is shown - it is right on every path, which total is not. Where Booking excludes taxes from its display price, total understates: measured on a search row, display EUR 246.28 with EUR 82.50 of excluded charges beside it. Where the taxes are already inside gross_amount, the two are the same number and both match the screen: measured on a room-list rate, MYR 151 on the app’s price sheet against MYR 151.305 on the wire. discount_percent is not Booking’s badge: it is computed from Booking’s own figures and moves with the point of sale, so use it to rank savings within a run rather than to reproduce what a browser shows.

#FieldLabelTypeDescriptionExample
1baseBasenumber, nullableNet rate before any listed charge.
2bookBooknumber, nullableWhat you pay for the stay, as Booking displays it.
3totalTotalnumber, nullableDisplay price BEFORE the listed extra charges.
4total_with_chargesTotal with chargesnumber, nullableThe all-in amount: total plus everything Booking reports as charged on top. This is the figure that matches the app.
5per_nightPer nightnumber, nullableTotal divided by nights.
6before_discountBefore discountnumber, nullableBooking’s struck-through price.
7before_discount_with_chargesBefore discount, all-innumber, nullableThe struck-through price on the same basis as total_with_charges.
8discountDiscountnumber, nullableTotal saving against before_discount.
9discount_percentDiscount %number, nullablediscount / before_discount from Booking’s own figures. Not the badge the web page shows - the point of sale and the comparator both move it. Use it to rank savings within a run.
10mobile_discountMobile discountnumber, nullableThe mobile-only cut itself.
11without_mobile_discountWithout mobile discountnumber, nullableThe same rate without the mobile-only cut - what a desktop browser shows.
12discountsDiscount linesarray, nullableEvery discount with its code, name and amount.
13extra_chargesExtra chargesobject, nullableTaxes and fees. details[] itemises each one; excludedTotal / includedTotal / unknownTotal / depositTotal roll them up. chargesCompleteness says whether the itemisation is all of it: under “incalculable” the all-in figure is a floor, not a total. inclusion and mode are two different vocabularies and both contain “conditional” - inclusion is whether the guest owes it (included, excluded, conditional, deposit; anything else becomes unknown, with Booking’s own token kept in inclusionRaw), while mode is how it is computed (percentage, incalculable, not_applicable, variable_amount, conditional, per_stay, per_person_per_stay, per_night, per_person_per_night, per_item, per_item_per_period - passed through raw). A line reading “conditional” means nothing until you know which field it came from. There is no conditional bucket and no conditionalTotal - removed 2026-10-28 after it proved empty on every surface this Actor reads: 0 of 4,934 roomList charge lines and 0 of 405 search lines. The VALUE survives where it is true - a charge line’s own inclusion still reads conditional where Booking says so - but the always-empty array and always-zero rollup are gone from every level. If such a line ever arrives it groups with excluded, which is conservative (the guest may owe it) and never with unknown, which would assert Booking said nothing. depositTotal stays, and the asymmetry is the whole reason: grouping a conditional line into excluded is true, while grouping a refundable deposit there would overstate the bill. Measured empty too - 0 of 4,934, including 615 lines from 50 vacation rentals and entire homes across five markets, the segment a damage deposit comes from - but kept, because ARB found the app really does pass DEPOSIT at a call site (queue item 149) and renders it in the checkout price summary, which is reached only after a room is selected. A deposit line, if one ever arrives, is the damage deposit - the same figure as details.damageDeposit reached by a second route (the app maps BpPriceAdditionalInfo.damageDeposit onto the DEPOSIT inclusion), not a further charge to add to it.

provenance — Provenance

Parent chain: row → provenance

What produced this row’s price: the proxy exit it was fetched through, the generation of that exit, the currency you asked for and the endpoint that priced it. The point of sale follows the proxy exit country, not any input - bookerCountry was removed in this build because nothing read it.

#FieldLabelTypeDescriptionExample
1exitCountryExit countrystring, nullableISO country of the proxy exit this row was fetched through, sampled once per proxy generation from Apify’s own request echo. This is the market that priced the row: on the hotel_ids path taxes come back inside the display price from an EU exit and excluded through a US one. Null when the run’s pool rotates per request (a residential proxy with no session), when the probe failed, or past the per-run probe budget - null means not established, not “no market”. Pin country-XX or a session in the proxy configuration to make it exact for every row.US, GB
2proxyGenerationProxy generationinteger, nullableWhich exit fetched this row, counted from 0. The retry ladder draws a fresh exit when one fails - six generations in 30 seconds on one measured run - so two rows of one dataset can honestly carry different tax treatment. RUN_SUMMARY.request.exitCountries lists every generation and its country.0
3currencyRequestedCurrency requestedstring, nullableThe currency you asked for, beside the honoured currency. Not recoverable from the row otherwise: HOTEL resolves to the property’s own code, which the row then states as its own currency - so a row quoting MYR could have been asked for as HOTEL or as MYR.EUR, HOTEL
4surfaceSurfacestring, nullableThe endpoint that produced this row: search (the destination search), roomList (the per-property rate list) or hotelPage (the by-id path). The surfaces disagree in documented ways - roomList carries the structured meal price and hotelPage does not - so a buyer comparing two rows needs to know which answered.roomList, hotelPage

product — Rate

Parent chain: row → product

One bookable rate — the same object an entry of products holds on a property row. Only the members documented at this level are listed below; a rate carries the rest of its columns (price, occupancy, deal, room, fit) as described in the product row of the table above.

#FieldLabelTypeDescriptionExample
1policiesPoliciesobject, nullable · in product.policiesThe rate’s commercial terms: cancellation, meal plan, payment timing, prepayment and deposit.
2priceRate priceobject, nullable · in product.priceWhat this rate costs for the whole requested stay - the party and the nights you asked for, already expanded, not a per-night or per-person figure. It states its own currency rather than borrowing the row’s (#86), because it does not always match: the total falls back to Booking’s gross_amount_hotel_currency when the ordinary amount is absent, and that field is the PROPERTY’s currency.

product.policies — Policies

Parent chain: row → product → policies

The rate’s commercial terms: cancellation, meal plan, payment timing, prepayment and deposit.

#FieldLabelTypeDescriptionExample
1meal_planMeal planobject, nullable · in product.policies.meal_planWhat the rate includes, and what else can be bought with it.
2paymentPayment policyobject, nullable · in product.policies.paymentWhen the rate is paid, which is timing rather than amount.
3depositDeposit (prepayment) policyobject, nullable · in product.policies.depositThe pay-now / pay-later flag, NOT a damage deposit - despite Booking calling the field deposit_required on the wire. True means prepayment is taken at booking. A real damage deposit is details.damageDeposit, and the two are unrelated: one is when the rate is paid, the other is money left at the desk and refunded. noPaymentDueAtBooking and payment.types answer different questions - the first is whether anything is taken now, the second is when the rate is settled - so a rate can require no payment now and still be pay_now for the balance.

product.policies.meal_plan — Meal plan

Parent chain: row → product → policies → meal_plan

What the rate includes, and what else can be bought with it.

#FieldLabelTypeDescriptionExample
1mealsMeals includedarrayThe meals the rate includes, as a list of breakfast / lunch / dinner. Empty when it includes none.
2planPlanstringBoard INCLUDED in the rate: breakfast_included, half_board, full_board, all_inclusive, or no_plan. no_plan does not mean no breakfast - it means none is included, and 40% of blocks measured sell one for a fee. The vocabulary is deliberately unchanged, so a filter written against no_plan does not change answer; breakfastAvailableForFee is the field that corrects the record.no_plan, breakfast_included
3breakfastAvailableForFeeBreakfast for a feebooleanWhether breakfast can be bought with this rate but is not included in it. True on 145 of 366 blocks measured, every one of which plan calls no_plan. False is an answer rather than a gap: the payload names the rates that offer no meal at all.
4offersOffersarray, nullable · in product.policies.meal_plan.offers[]One entry per meal the rate can carry, included or for a fee. Null on a rate with no meal option at all, rather than an empty array on every such row.
5summarySummarystring, nullableBooking’s own wording for the meal terms, verbatim: localised by language and converted to the run’s currency, ROUNDED (“Breakfast EUR 6” for a 120 ZAR breakfast). It is the only place the per-person-per-night basis is stated, and it is display text - offers[].price is the number to compute on. Null on a rate with no meal option.Continental breakfast costs € 18.90 per person per night.

product.policies.meal_plan.offers[] — Meal offer

Parent chain: row → product → policies → meal_plan → offers[]

One meal this rate can carry, and on what terms.

#FieldLabelTypeDescriptionExample
1mealMealstring, nullablebreakfast, lunch or dinner. Derived from the offer icon, never from its title, because the title is localised. Null for a single lunch-or-dinner entry, where the icon does not say which and nothing else does either - label carries Booking’s own words for it.breakfast
2statusStatusstring, nullableincluded when the rate covers the meal, available_for_fee when it can be bought on top. Decided by the presence of a price and the rate’s own board flags, not by the prose. Null in the unobserved case where neither says.available_for_fee, included
3pricePricenumber, nullableWhat the meal costs on top of the rate, as the property quotes it: a unit price per person per night, not a stay total (A/B’d invariant across 2 and 4 adults, 2 and 4 nights). Null when the meal is included, and null on the hotel_ids path, which publishes the offer but not the amount.18.9
4currencyCurrencystring, nullableThe currency price is quoted in - THE PROPERTY’S OWN, which can differ from the row’s currency. A Cape Town property returns 120 ZAR on a run priced in EUR, so pairing the amount with the row’s currency overstates it twentyfold.EUR, ZAR
5styleStylestring, nullableThe cuisine or service list Booking puts in brackets after the title, passed through verbatim and localised.Continental, Buffet
6labelLabelstring, nullableBooking’s own title for the offer, verbatim and localised. Null on the hotel_ids path, which carries no structured offer to title.Breakfast available (Continental, Buffet)

product.policies.payment — Payment policy

Parent chain: row → product → policies → payment

When the rate is paid, which is timing rather than amount.

#FieldLabelTypeDescriptionExample
1typesPayment typestring, nullableOne of pay_now, pay_later or pay_at_property.
2choosableGuest choosesboolean, nullableTrue where the guest picks the timing, false where the rate requires it.

product.policies.deposit — Deposit (prepayment) policy

Parent chain: row → product → policies → deposit

The pay-now / pay-later flag, NOT a damage deposit - despite Booking calling the field deposit_required on the wire. True means prepayment is taken at booking. A real damage deposit is details.damageDeposit, and the two are unrelated: one is when the rate is paid, the other is money left at the desk and refunded. noPaymentDueAtBooking and payment.types answer different questions - the first is whether anything is taken now, the second is when the rate is settled - so a rate can require no payment now and still be pay_now for the balance.

#FieldLabelTypeDescriptionExample
1prepaymentAtBookingPrepayment at bookingboolean, nullableTrue where Booking takes payment when the rate is booked.
2noPaymentDueAtBookingNothing due at bookingboolean, nullableThe yes/no a consumer should read. Means nothing due today, not nothing due ever.
3depositRequiredRawDeposit required, rawstring | integer, nullableBooking’s own deposit_required value, verbatim.
4termsIdTerms idinteger | string, nullableAn opaque id with no consumer in Booking’s own app either; passed through rather than interpreted.

product.price — Rate price

Parent chain: row → product → price

What this rate costs for the whole requested stay - the party and the nights you asked for, already expanded, not a per-night or per-person figure. It states its own currency rather than borrowing the row’s (#86), because it does not always match: the total falls back to Booking’s gross_amount_hotel_currency when the ordinary amount is absent, and that field is the PROPERTY’s currency.

#FieldLabelTypeDescriptionExample
1currencyCurrencystring, nullableThe currency every amount in this object is in, read from the same money object Booking supplied the total in. Usually the row’s currency, but not guaranteed to be - compare the two rather than assuming. Null where Booking labelled no currency, which is honest rather than a guess.
2totalTotalnumber, nullableThe all-stay price Booking displays for this rate. Same number as book.
3bookBookable pricenumber, nullableThe display price, under the name the older output used. Same number as total.
4baseNetnumber, nullableBefore any tax or fee Booking itemises - net_amount on the wire.
5per_nightPer nightnumber, nullableDerived here by dividing the stay total by the nights: Booking’s own averagePricePerNight comes back zero-filled.
6before_discountBefore discountnumber, nullableThe undiscounted rate. Present only on a discounted rate.
7discountDiscountnumber, nullableThe deal or campaign cut. Present only on a discounted rate.
8mobile_discountMobile discountnumber, nullableThe mobile-only part of the cut. Present only when there is one.
9without_mobile_discountWithout the mobile cutnumber, nullableWhat a desktop browser is shown. Present only when a mobile cut applies.

A real row

Captured from a live run. Lists are cut to two entries and deep nesting is elided — the shape is real, the bulk is not.

{
  "recordType": "rate",
  "productId": "17622630_89064837_1_0_0",
  "hotelId": 176226,
  "checkin": "2026-10-31",
  "checkout": "2026-11-01",
  "adults": 1,
  "roomsRequested": 1,
  "status": "available",
  "available": true,
  "reason": null,
  "roomName": "Standard Twin Room",
  "price": 70.54,
  "priceWithCharges": 81,
  "pricePerNight": 70.54,
  "currency": "EUR",
  "cancellationType": "non_refundable",
  "freeCancellationUntil": null,
  "prepaymentPolicy": "full_prepayment",
  "paymentTypes": [
    "pay_now"
  ],
  "mealPlan": "no_plan",
  "breakfastAvailableForFee": false,
  "breakfastPrice": null,
  "breakfastCurrency": null,
  "product": {
    "id": "17622630_89064837_1_0_0",
    "room": 17622630,
    "roomTypeId": 8,
    "roomTypeCode": "TWIN",
    "pricedPerBed": false,
    "roomName": "Standard Twin Room",
    "children": [],
    "deal": null,
    "isSmoking": false,
    "number_available_at_this_price": 20,
    "number_of_adults": 1,
    "fitsParty": true,
    "fitStatus": 2,
    "occupancy": {
      "adults": 1,
      "children": 0,
      "childrenAges": [],
      "requestedAdults": 1,
      "requestedChildrenAges": []
    },
    "maxOccupancy": 1,
    "bedroomCount": null,
    "bathroomCount": null,
    "privateBathroom": true,
    "policies": {
      "cancellation": "{... 9 keys}",
      "meal_plan": "{... 5 keys}",
      "payment": "{... 8 keys}",
      "deposit": "{... 4 keys}"
    },
    "price": {
      "base": 70.54,
      "book": 70.54,
      "total": 70.54,
      "per_night": 70.54,
      "extra_charges": "{... 12 keys}",
      "total_with_charges": 81
    }
  }
}