{
  "openapi": "3.0.1",
  "info": {
    "title": "Booking.com Hotel Scraper – Prices, Room Rates & Reviews",
    "description": "Scrape Booking.com hotel prices, room rates and availability via the mobile API. Full per-room rate table with itemised taxes and fees, family pricing by child age, guest reviews and property details — one actor, not four. Search by destination, GPS radius or hotel id. No API key needed.",
    "version": "1.0",
    "x-build-id": "e3LcIHyckliaiyV6B"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/rl1987~booking-api-scraper/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-rl1987-booking-api-scraper",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/acts/rl1987~booking-api-scraper/runs": {
      "post": {
        "operationId": "runs-sync-rl1987-booking-api-scraper",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor and returns information about the initiated run in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/runsResponseSchema"
                }
              }
            }
          }
        }
      }
    },
    "/acts/rl1987~booking-api-scraper/run-sync": {
      "post": {
        "operationId": "run-sync-rl1987-booking-api-scraper",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "inputSchema": {
        "type": "object",
        "properties": {
          "mode": {
            "title": "What this run does",
            "enum": [
              "search",
              "rooms",
              "rates",
              "reviews",
              "reviewScores",
              "surroundings",
              "details",
              "attractions",
              "attractionDetails",
              "attractionAvailability"
            ],
            "type": "string",
            "description": "Pick Search for the normal flow, or an enrichment mode to re-check properties you already have ids for without searching again. *Watch rates* is the one keyed on individual rates rather than whole properties, for price and availability monitoring.",
            "default": "search"
          },
          "checkin": {
            "title": "Check-in",
            "pattern": "^(\\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$|^(\\d+)\\s*(day|week|month|year)s?$",
            "type": "string",
            "description": "Check-in date. Pick a day, or switch the picker to a relative value like \"30 days\" — relative is the one to use in a saved task or a schedule, since a fixed date eventually falls into the past and Booking only quotes future stays. Empty = 30 days from today.\n\n**Used by more than one mode:** a Search prices this stay, *Room pricing & availability* re-prices it, and *Guest reviews* uses it only when no `ufi` is given."
          },
          "checkout": {
            "title": "Check-out",
            "pattern": "^(\\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$|^(\\d+)\\s*(day|week|month|year)s?$",
            "type": "string",
            "description": "Departure date, absolute or relative the same way as check-in. Defaults to check-in + 1 night."
          },
          "windowDays": {
            "title": "Flexible dates: search a window of days",
            "minimum": 1,
            "maximum": 60,
            "type": "integer",
            "description": "Ask \"any stay starting in the next N days\" instead of one fixed stay. Set this together with **Nights per stay** below.\n\nThe window starts today, or at **Check-in** if you set one. Each day in it is a separate search of its own, **billed separately** — a 30-day window costs roughly 30× a single-date run — so keep it as short as the question allows and set **Max accommodations**, which is a budget across the whole sweep, not per day.\n\nCapped at 60 days. Only applies to the *Search* and *Room pricing & availability* modes: the others return the same answer whatever the dates."
          },
          "stayNights": {
            "title": "Flexible dates: nights per stay",
            "minimum": 1,
            "type": "integer",
            "description": "How long each stay in the window is. `1` asks for single nights — \"the cheapest single night in the next 30 days\". Ignored unless **Search a window of days** is set."
          },
          "rooms": {
            "title": "Rooms",
            "minimum": 1,
            "type": "integer",
            "description": "Number of rooms to book for.",
            "default": 1
          },
          "adults": {
            "title": "Adults",
            "minimum": 1,
            "type": "integer",
            "description": "Number of adult guests.",
            "default": 2
          },
          "childrenAges": {
            "title": "Children ages",
            "type": "array",
            "description": "Ages of the children in the party, one per line (e.g. 5 then 9). Booking prices family stays by child age (free cots, kids-stay-free rules), so set this for correct prices and to surface family rooms.",
            "items": {
              "type": "string"
            }
          },
          "currency": {
            "title": "Currency",
            "enum": [
              "HOTEL",
              "USD",
              "EUR",
              "GBP",
              "AUD",
              "CAD",
              "CHF",
              "CNY",
              "JPY",
              "INR",
              "BRL",
              "MXN",
              "SEK",
              "NOK",
              "DKK",
              "PLN",
              "AED",
              "SGD",
              "ZAR",
              "TRY",
              "RUB",
              "AFN",
              "ALL",
              "AMD",
              "ANG",
              "AOA",
              "ARS",
              "AWG",
              "AZN",
              "BAM",
              "BBD",
              "BDT",
              "BGN",
              "BHD",
              "BIF",
              "BMD",
              "BND",
              "BOB",
              "BSD",
              "BTN",
              "BWP",
              "BYN",
              "BZD",
              "CDF",
              "CLP",
              "COP",
              "CRC",
              "CUP",
              "CVE",
              "CZK",
              "DJF",
              "DOP",
              "DZD",
              "EGP",
              "ETB",
              "FJD",
              "GEL",
              "GHS",
              "GMD",
              "GNF",
              "GTQ",
              "GYD",
              "HKD",
              "HNL",
              "HTG",
              "HUF",
              "IDR",
              "ILS",
              "IQD",
              "IRR",
              "ISK",
              "JMD",
              "JOD",
              "KES",
              "KGS",
              "KHR",
              "KMF",
              "KRW",
              "KWD",
              "KYD",
              "KZT",
              "LAK",
              "LBP",
              "LKR",
              "LRD",
              "LSL",
              "LYD",
              "MAD",
              "MDL",
              "MGA",
              "MKD",
              "MMK",
              "MNT",
              "MOP",
              "MRU",
              "MUR",
              "MVR",
              "MWK",
              "MYR",
              "MZN",
              "NAD",
              "NGN",
              "NIO",
              "NPR",
              "NZD",
              "OMR",
              "PAB",
              "PEN",
              "PGK",
              "PHP",
              "PKR",
              "PYG",
              "QAR",
              "RON",
              "RSD",
              "RWF",
              "SAR",
              "SBD",
              "SCR",
              "SDG",
              "SOS",
              "SRD",
              "SSP",
              "STN",
              "SVC",
              "SYP",
              "SZL",
              "THB",
              "TJS",
              "TND",
              "TOP",
              "TTD",
              "TWD",
              "TZS",
              "UAH",
              "UGX",
              "UYU",
              "UZS",
              "VES",
              "VND",
              "VUV",
              "WST",
              "XAF",
              "XCD",
              "XOF",
              "XPF",
              "YER",
              "ZMW"
            ],
            "type": "string",
            "description": "Currency for displayed prices.\n\n**Property's own currency** is the first choice and matches Booking's own picker: prices come back in whatever the property quotes in — MYR in Kuala Lumpur, THB in Bangkok — with no conversion. Every row states its own `currency`, so you always know which you got.\n\nOtherwise pick one of **147** currencies. That is every ISO-4217 code Booking honours on this surface, established by requesting a property once per code and reading back the currency it priced in, with bogus codes as the control.\n\nThe nine it refuses are ERN, FKP, GIP, KPW, SHP, SLE, TMT, VED and ZWG. They are refused outright rather than falling back silently, so you cannot ask for one by accident.\n\nBooking's *app* only offers 49 of the 147 in its own menu, and its exchange-rate table lists 188 — including pre-euro currencies and gold. The 147 here is the set measured to actually price a room.\n\nEvery price in a row follows this setting, including the cancellation ladder and the prepayment schedule. **One documented exception:** `details.damageDeposit.amount` stays in the property's own currency, because a deposit is collected at the desk in local money — its `currency` says so.",
            "default": "USD"
          },
          "language": {
            "title": "Language / locale",
            "enum": [
              "en-us",
              "en-gb",
              "de",
              "fr",
              "es",
              "it",
              "nl",
              "pt",
              "pt-br",
              "pl",
              "ru",
              "ja",
              "zh",
              "zh-tw",
              "ko",
              "ar",
              "tr",
              "sv",
              "da",
              "no",
              "fi"
            ],
            "type": "string",
            "description": "Locale for localized text (names, facilities, reviews).",
            "default": "en-us"
          },
          "searchBy": {
            "title": "Search by (sub-mode)",
            "enum": [
              "auto",
              "destination",
              "hotelIds",
              "cityId",
              "regionId",
              "point",
              "area"
            ],
            "type": "string",
            "description": "Pick how to find properties, or leave on Auto. Ignored unless this run is a Search.",
            "default": "auto"
          },
          "search": {
            "title": "Search by: destination name",
            "type": "string",
            "description": "Place name (city, region, landmark) resolved via Booking's own autocomplete — the easiest way to start, e.g. \"Amsterdam\".\n\nAutocomplete matches what Booking has a destination for, which is not always what you would guess: \"Queens\" resolves to Queenstown in New Zealand, and \"Manhattan\", \"Newark\" and \"Long Island City\" resolve to nothing at all. The run logs what it resolved to — check it. For a metro area Booking has no destination for, use mode 5 (custom area) instead.\n\nNaming one specific hotel here also works: it is detected and fetched directly, the same as mode 2."
          },
          "hotel_ids": {
            "title": "Search by: specific properties (Hotel IDs)",
            "type": "array",
            "description": "Scrape specific properties by numeric Booking ID, one per line, e.g. 10004. **Set this and every other sub-mode is ignored.** These ids are also what the enrichment modes work on, so the same list re-checks rooms, reviews, scores or nearby places without searching.\n\nThis must be the internal numeric id, not a URL slug or a property name. To find it, open the hotel's Booking.com page, view the page source and search for `hotelId`.\n\nUnlike a search, this always returns a row per id — including one saying why there is no price (sold out, minimum stay, closed, unknown id).",
            "items": {
              "type": "string"
            }
          },
          "cityId": {
            "title": "Search by: City ID (a number, not a name)",
            "type": "integer",
            "description": "Exact numeric destination id (ufi), e.g. -2140479 for Amsterdam. **This field takes a number**: typing \"Amsterdam\" here is rejected — use mode 1 for a name.\n\nOnly useful if you already have the id from a previous run's output."
          },
          "regionId": {
            "title": "Search by: Region ID (a number, not a name)",
            "type": "integer",
            "description": "Exact numeric region id. **This field takes a number**, not a region name — use mode 1 for a name. Only useful if you already have the id."
          },
          "latitude": {
            "title": "Search by: latitude (point)",
            "type": "number",
            "description": "Sub-mode **Point and radius**: search outward from one GPS point, nearest first — \"within N km of this venue\" rather than \"in this city\". Latitude and Longitude are required together; Radius is optional."
          },
          "longitude": {
            "title": "Longitude",
            "type": "number",
            "description": "Longitude paired with Latitude."
          },
          "radius": {
            "title": "Search radius (km)",
            "type": "number",
            "description": "Only keep properties within this many kilometres of the point. **Applied after the results come back**, not by Booking — so a tight radius spends your Max accommodations budget on properties that are then discarded. Leave empty for nearest-first with no cut-off."
          },
          "swLat": {
            "title": "Search by: bounding box — south-west latitude",
            "type": "string",
            "description": "Sub-mode **Bounding box**: sweep an arbitrary rectangle instead of a destination. Use it for ground Booking has no single destination for — New York's city destination holds ~520 properties, while a box over the same area also reaches Jersey City, Hoboken and North Bergen.\n\n**All four corners are required together.** A box costs far more requests per property than a destination search, so keep it tight and set Max accommodations.\n\n**Coverage reporting differs.** A destination search can say how complete it was (\"5,822 of 5,844 properties, 99.6%\") because Booking reports a total; a box cannot, so the log reports whether every cell was fully reached instead of a percentage."
          },
          "swLon": {
            "title": "Bounding box — south-west longitude",
            "type": "string",
            "description": "West edge of the search rectangle."
          },
          "neLat": {
            "title": "Bounding box — north-east latitude",
            "type": "string",
            "description": "North edge of the search rectangle."
          },
          "neLon": {
            "title": "Bounding box — north-east longitude",
            "type": "string",
            "description": "East edge of the search rectangle."
          },
          "deepSearch": {
            "title": "Deep search (auto-enables above 1,000 Max accommodations)",
            "type": "boolean",
            "description": "Booking's search stops returning new properties at roughly 1,000 per destination. With this on, the actor splits a large destination into geographic shards and merges the results, reaching several times more properties (measured: 1,104 -> 6,170 for Tokyo). Costs proportionally more requests, and each property is still returned - and billed - only once. Left unset, it turns on automatically when Maximum results is above 1,000."
          },
          "accommodationTypes": {
            "title": "Accommodation types",
            "type": "array",
            "description": "Limit results to these property types. Leave empty for all types. Pick from the list here; via the API this field takes Booking’s numeric type ids as strings (e.g. \"201\" for Apartments). An id sent as a number, or a type name, is rejected before the run starts. To pass names case-insensitively (\"Apartments\", \"villa\"), or ids as numbers, use the snake_case spelling \"accommodation_types\" instead. See the README for the full id → name table.",
            "items": {
              "type": "string",
              "enum": [
                "201",
                "203",
                "204",
                "205",
                "206",
                "208",
                "209",
                "210",
                "212",
                "213",
                "214",
                "215",
                "216",
                "218",
                "219",
                "220",
                "221",
                "222",
                "223",
                "224",
                "225",
                "226",
                "227",
                "228"
              ],
              "enumTitles": [
                "Apartments",
                "Hostels",
                "Hotels",
                "Motels",
                "Resorts",
                "Bed and Breakfasts",
                "Ryokans",
                "Farm Stays",
                "Resort Villages",
                "Villas",
                "Campgrounds",
                "Boats",
                "Guesthouses",
                "Inns",
                "Condo Hotels",
                "Vacation Homes",
                "Lodges",
                "Homestays",
                "Country Houses",
                "Luxury tents",
                "Capsule Hotels",
                "Love Hotels",
                "Riads",
                "Chalets"
              ]
            },
            "default": []
          },
          "minReviewScore": {
            "title": "Minimum review score",
            "minimum": 1,
            "maximum": 10,
            "type": "integer",
            "description": "Only return properties with at least this guest review score (1–10). Applied client-side."
          },
          "minPrice": {
            "title": "Minimum price (per stay)",
            "minimum": 0,
            "type": "integer",
            "description": "Only return properties at or above this total stay price, in the selected currency. Applied client-side.\n\nThis runs on the price set by **Price basis**: by default Booking's displayed price, which is net of taxes and fees that are charged on top. On a property with excluded charges the all-in cost is higher, so a budget set here can be overshot — set Price basis to *all-in* to filter on what the guest actually pays."
          },
          "maxPrice": {
            "title": "Maximum price (per stay)",
            "minimum": 0,
            "type": "integer",
            "description": "Only return properties at or below this total stay price, in the selected currency. Applied client-side.\n\nThis runs on the price set by **Price basis**: by default Booking's displayed price, which is net of taxes and fees that are charged on top. On a property with excluded charges the all-in cost is higher, so a budget set here can be overshot — set Price basis to *all-in* to filter on what the guest actually pays."
          },
          "priceBasis": {
            "title": "Price basis for filters and sorting",
            "enum": [
              "displayed",
              "all_in"
            ],
            "type": "string",
            "description": "Which price <b>Minimum price</b>, <b>Maximum price</b> and <b>Cheapest first</b> are measured against.\n\n<b>Booking's displayed price</b> (the default) is the figure Booking shows in its own result list. It is net of any tax or fee charged on top — city tax, VAT, a resort fee — so the stay can cost more than the number you filtered on. Pick this to match what you see on the website.\n\n<b>All-in price</b> is the displayed price plus every charge Booking reports as excluded, i.e. what the guest pays. Pick this when a budget or a \"cheapest\" ranking has to be true of the final bill. Each row carries both figures either way, as <code>price.total</code> and <code>price.total_with_charges</code>.",
            "default": "displayed"
          },
          "sortBy": {
            "title": "Sort results by",
            "enum": [
              "price",
              "review_score",
              "stars_desc",
              "stars_asc",
              "distance",
              "homes_first",
              "popularity",
              "none"
            ],
            "type": "string",
            "description": "How Booking orders the results. **Cheapest first** is the default. These are Booking's own seven orderings - the same menu its app shows - and the order is applied by Booking itself, so a small <b>Max items</b> still gives you the real cheapest (or best-reviewed) properties for the whole destination rather than the cheapest of the first page.\n\n<b>Entire homes & apartments first</b> promotes apartments and aparthotels above hotels, which is Booking's own \"upsort\" ordering rather than a filter - hotels still appear, lower down. Use the property-type filter if you want only homes.\n\n**Most expensive first was withdrawn on 2026-10-21.** Booking has no such ordering on this surface, so it had to be ranked here over an over-fetched candidate set, which made it silently approximate whenever the over-fetch did not reach far enough. Asking for it now fails with a message instead. Sort the dataset yourself after the run - every row carries its own price.\n\n**Cheapest first** orders by the price set by <b>Price basis</b>. With the default (Booking's displayed price) the order is Booking's own; with <i>all-in</i> the ranking is done here, over an over-fetched candidate set, because Booking has no all-in sorter.",
            "default": "price"
          },
          "sortDirection": {
            "title": "Sort direction (deprecated)",
            "enum": [
              "ascending",
              "descending"
            ],
            "type": "string",
            "description": "**Mostly ignored — the direction is part of <b>Sort results by</b> now.** Pick \"Cheapest first\", \"Most stars first\" and so on. This field is kept so existing saved tasks and schedules keep validating.\n\n<b>\"Descending\" is refused, not ignored, on every order Booking only provides one way</b> — cheapest, most/fewest stars, nearest, homes-first. The run stops with a message naming the alternative instead of quietly serving a different order under your label. Only <i>Best reviewed</i> genuinely has both directions.\n\nTo get the reverse of an order Booking does not offer, sort the dataset after the run: every row carries its own price, review score and star rating.",
            "default": "ascending"
          },
          "includeRooms": {
            "title": "Include rooms & products",
            "type": "boolean",
            "description": "Add both room sections: the per-rate `products` list (board, cancellation, deals, price) AND the distinct room-type `rooms` list (name, occupancy, availability, lowest price). Costs one extra request per property in search mode.",
            "default": false
          },
          "onlyFittingProducts": {
            "title": "Only rates that fit the party (needs Include rooms)",
            "type": "boolean",
            "description": "When rooms are included, keep only rates Booking would actually sell to your party (its `is_block_fit` flag) in `products`, and compute `rooms[].minPrice` / `productCount` from those. Turn off to get every rate of every occupancy (each still carries `fitsParty`).",
            "default": true
          },
          "includeDetails": {
            "title": "Include static details",
            "type": "boolean",
            "description": "Add a `details` object per property: name, type, star rating, review score/count, location, main photos, named facilities, languages spoken and address. Costs one extra request per property in search mode.",
            "default": false
          },
          "includeSurroundings": {
            "title": "Include nearby places",
            "type": "boolean",
            "description": "Add nearby landmarks, public transport, dining and airports with distances, under \"details.surroundings\". Costs one extra API request per property and is billed as its own \"Nearby places\" event. Off by default: until 1.0.50 this was fetched automatically with \"Include details\" and not charged.",
            "default": false
          },
          "includeReviews": {
            "title": "Include reviews",
            "type": "boolean",
            "description": "Add a `reviews` array per property (author, score, title, pros/cons, date). Costs one request per 10 reviews per property.",
            "default": false
          },
          "maxReviews": {
            "title": "Max reviews per property (needs Include reviews)",
            "minimum": 1,
            "type": "integer",
            "description": "How many reviews to fetch per property (only when \"Include reviews\" is on). Capped by the property's total review count.",
            "default": 10
          },
          "includeReviewScores": {
            "title": "Include review scores & summary",
            "type": "boolean",
            "description": "Add per-category review subscores (staff, cleanliness, location, value, comfort, facilities, wifi) and the aggregated review summary, under \"reviewSubscores\" and \"reviewSummary\". Costs one extra API request per property regardless of how many reviews you fetch, and is billed as its own \"Review scores\" event. Off by default: until 1.0.50 this rode along free with \"Include reviews\".",
            "default": false
          },
          "includeExtraCharges": {
            "title": "Include extra-charge breakdown",
            "type": "boolean",
            "description": "Add the `extra_charges` price breakdown (taxes/fees) to each result and product.",
            "default": true
          },
          "hotelIdsRooms": {
            "title": "Hotel IDs",
            "type": "array",
            "description": "The properties to run **Room pricing & availability** on, one numeric Booking ID per line, e.g. 10004. This must be the internal numeric id, not a URL slug or a property name: open the hotel's Booking.com page, view source and search for `hotelId`.",
            "items": {
              "type": "string"
            }
          },
          "roomsOnlyFitting": {
            "title": "Only rates that fit the party (Rooms mode)",
            "type": "boolean",
            "description": "Keep only rates Booking would actually sell to the party in **Stay details** below. Turn off for every rate of every occupancy. This is the Rooms-mode copy; it overrides the shared *Only rates that fit the party* field.",
            "default": true
          },
          "roomsExtraCharges": {
            "title": "Itemise taxes and fees (Rooms mode)",
            "type": "boolean",
            "description": "Break each tax and fee out of the rate instead of a blended total. This is the Rooms-mode copy; it overrides the shared *Include extra-charge breakdown* field.",
            "default": true
          },
          "productIds": {
            "title": "Product IDs",
            "type": "array",
            "description": "The individual rates to re-check, one product ID per line, e.g. `25768602_431556758_2_0_0`. These are the `products[].id` values from an earlier *Search* or *Room pricing & availability* run.\n\nThe property is worked out from the ID, so nothing else has to identify it — but a rate exists only for a given stay and party, so **Stay details** still applies and should match the run the IDs came from. One row comes back per ID you ask for, including rates that are no longer on sale.",
            "items": {
              "type": "string"
            }
          },
          "hotelIdsReviews": {
            "title": "Hotel IDs",
            "type": "array",
            "description": "The properties to run **Guest reviews** on, one numeric Booking ID per line, e.g. 10004. This must be the internal numeric id, not a URL slug or a property name: open the hotel's Booking.com page, view source and search for `hotelId`.",
            "items": {
              "type": "string"
            }
          },
          "ufi": {
            "title": "Destination ufi (reviews mode only)",
            "type": "string",
            "description": "Booking's reviews endpoint refuses to return anything without the destination's `ufi`, so the reviews mode needs one. Take it from an earlier search run's output and paste it here to skip a lookup; leave it empty and one extra request per property derives it. A ufi identifies a destination, not a property, so one value covers every property in that destination."
          },
          "countryCode": {
            "title": "Country code (reviews mode only)",
            "type": "string",
            "description": "ISO 2-letter code for the property's country, paired with the ufi above. Optional — Booking tolerates it being empty."
          },
          "reviewsMax": {
            "title": "Max reviews per property (Reviews mode)",
            "minimum": 1,
            "type": "integer",
            "description": "Capped by the property's own review count. This is the Reviews-mode copy; it overrides the shared *Max reviews per property* field.",
            "default": 10
          },
          "hotelIdsScores": {
            "title": "Hotel IDs",
            "type": "array",
            "description": "The properties to run **Review score summary** on, one numeric Booking ID per line, e.g. 10004. This must be the internal numeric id, not a URL slug or a property name: open the hotel's Booking.com page, view source and search for `hotelId`.",
            "items": {
              "type": "string"
            }
          },
          "hotelIdsSurroundings": {
            "title": "Hotel IDs",
            "type": "array",
            "description": "The properties to run **Nearby places** on, one numeric Booking ID per line, e.g. 10004. This must be the internal numeric id, not a URL slug or a property name: open the hotel's Booking.com page, view source and search for `hotelId`.",
            "items": {
              "type": "string"
            }
          },
          "hotelIdsDetails": {
            "title": "Hotel IDs",
            "type": "array",
            "description": "The properties to run **Static property details** on, one numeric Booking ID per line, e.g. 10004. This must be the internal numeric id, not a URL slug or a property name: open the hotel's Booking.com page, view source and search for `hotelId`.",
            "items": {
              "type": "string"
            }
          },
          "outputFields": {
            "title": "Output fields (leave empty for the full record)",
            "type": "array",
            "description": "Leave empty for the full record. Selecting Nearby places without Static details keeps a `details` object holding only `surroundings`.",
            "items": {
              "type": "string",
              "enum": [
                "price",
                "availability",
                "property",
                "rooms",
                "details",
                "reviews",
                "reviewScores",
                "surroundings"
              ],
              "enumTitles": [
                "Pricing",
                "Availability",
                "Property facts",
                "Rooms & rates",
                "Static details",
                "Guest reviews",
                "Review scores & subscores",
                "Nearby places"
              ]
            },
            "default": []
          },
          "maxItems": {
            "title": "Max accommodations",
            "minimum": 1,
            "type": "integer",
            "description": "Maximum number of accommodations to return.\n\nAbove 1,000 this turns Deep search on by itself, because Booking's search stops returning new properties at roughly that point.\n\n**This limit is the usual reason a run returns less than the destination holds.** Every run writes a `RUN_SUMMARY` record to its key-value store saying how many properties were available, how many were delivered, and which limit stopped it — so you can tell a complete result from a truncated one without guessing. The run's status message says the same thing in words when a run was cut short.",
            "default": 100
          },
          "maxConcurrency": {
            "title": "Max concurrency",
            "minimum": 1,
            "maximum": 50,
            "type": "integer",
            "description": "Maximum parallel requests for product/detail fetches.",
            "default": 10
          },
          "units": {
            "title": "Units",
            "enum": [
              "metric",
              "imperial"
            ],
            "type": "string",
            "description": "Unit system for distances - distance from the city centre and the nearby landmarks, transport and airports returned with includeDetails. Booking localises distances by language (metres for German, feet for US English); this normalises them to the system you pick.",
            "default": "metric"
          },
          "bookerCountry": {
            "title": "Booker country code",
            "type": "string",
            "description": "ISO 2-letter country of the booker. Affects some localized pricing."
          },
          "exportExcel": {
            "title": "Export an Excel workbook (.xlsx)",
            "type": "boolean",
            "description": "Also write an Excel workbook to this run's storage, with a sheet per section (Properties, and Rates/Rooms/Reviews when those are included) instead of nested JSON in a cell. Download it from the run's **Storage** tab. Does not change the dataset or what you are charged.\n\n**In enrichment modes only that mode's sheet fills**: Rates and Rooms for *Room pricing*, Reviews for *Guest reviews*. The Properties sheet holds the id alone, and nearby places, review scores and static details have no sheet — they reach you through the dataset instead.",
            "default": false
          },
          "excelFileName": {
            "title": "Excel file name",
            "type": "string",
            "description": "Name for the workbook in the key-value store.",
            "default": "OUTPUT.xlsx"
          },
          "exportKml": {
            "title": "Export a map file (.kml)",
            "type": "boolean",
            "description": "Also write a KML file with one placemark per property, ready to open in Google Earth, Google My Maps or QGIS. Each pin carries the price, review score, address and a link to the property, plus a photo when **Include details** is also on. Properties without coordinates are skipped.\n\n**Only produces pins in Search mode.** The enrichment modes return rows without coordinates, so a map built from them has 0 placemarks. For a map of properties you already have ids for, use Search with the *Specific properties* sub-mode.",
            "default": false
          },
          "kmlFileName": {
            "title": "KML file name",
            "type": "string",
            "description": "Name for the KML file in the key-value store.",
            "default": "OUTPUT.kml"
          },
          "kmlColorBy": {
            "title": "Colour map pins by",
            "enum": [
              "none",
              "price",
              "review_score"
            ],
            "type": "string",
            "description": "Shade the placemarks so the map reads at a glance. **Price** uses this run's own quartiles (cheapest green, dearest red); **review score** uses fixed bands (9+ green, 8-9 amber, 7-8 orange, below 7 red).",
            "default": "none"
          },
          "proxyConfiguration": {
            "title": "Proxy configuration",
            "type": "object",
            "description": "Proxy settings. Apify Proxy recommended.",
            "default": {
              "useApifyProxy": true
            }
          },
          "attractionsQuery": {
            "title": "Place or product name",
            "type": "string",
            "description": "Where to look, as free text — a city (`Amsterdam`), a landmark, or a product name. Booking resolves it server-side. Give this, or **Destination ufi**, or a latitude/longitude pair."
          },
          "attractionsUfi": {
            "title": "Destination ufi (a number, not a name)",
            "type": "integer",
            "description": "Booking's own numeric destination id — the same `ufi` the property search uses, e.g. `-2140479` for Amsterdam. More precise than free text when you already have it."
          },
          "attractionsLatitude": {
            "title": "Latitude",
            "type": "string",
            "description": "Search around a point instead of a destination. Needs **Longitude** too."
          },
          "attractionsLongitude": {
            "title": "Longitude",
            "type": "string",
            "description": "Needs **Latitude** too."
          },
          "attractionsRadiusKm": {
            "title": "Radius (km)",
            "type": "integer",
            "description": "Only with a latitude/longitude pair. Caps how far from the point results may be."
          },
          "attractionsSortBy": {
            "title": "Sort by",
            "enum": [
              "none",
              "trending",
              "price",
              "attraction_rating"
            ],
            "type": "string",
            "description": "Sorted server-side by Booking. Leave on the default unless you need a specific order — an unrecognised value is ignored upstream rather than refused.",
            "default": "none"
          },
          "attractionsMinRating": {
            "title": "Minimum rating",
            "enum": [
              "1",
              "1.5",
              "2",
              "2.5",
              "3",
              "3.5",
              "4",
              "4.5",
              "5"
            ],
            "type": "string",
            "description": "Keep only attractions rated at or above this many stars. Booking rates attractions out of <b>5</b>, not out of 10 like hotels — so \"4.5\" here is strict, and a 1-10 score such as 8 matches nothing."
          },
          "attractionsTypes": {
            "title": "Product types",
            "type": "array",
            "description": "Pick one or more of Booking's own product categories. Filtered by Booking, not client-side. These seven are the whole taxonomy — the same set and the same ids came back for Amsterdam, Rome and London, so they are global rather than per-destination. Leave empty for everything.",
            "items": {
              "type": "string",
              "enum": [
                "PTCMCuXtHgrC",
                "PTCMCcodv3rh",
                "PTCMCLfBZQwO",
                "PTCMCowbPYbZ",
                "PTCMCgNr9fWb",
                "PTCMCi9PTIPL",
                "PTCMClxjwGcP"
              ],
              "enumTitles": [
                "Tours",
                "Museums, arts & culture",
                "Nature & outdoor",
                "Services & rentals",
                "Entertainment & tickets",
                "Food & drink",
                "Workshops & classes"
              ]
            }
          },
          "attractionsLabels": {
            "title": "Labels",
            "type": "array",
            "description": "Booking exposes exactly one label filter on this surface today. Kept as a list because the upstream field takes several.",
            "items": {
              "type": "string",
              "enum": [
                "free_cancellation"
              ],
              "enumTitles": [
                "Free cancellation"
              ]
            }
          },
          "attractionsIncludeUnavailable": {
            "title": "Include products with no availability",
            "type": "boolean",
            "description": "Off by default, so you only get what Booking will actually sell right now. Turn on to see the full catalogue including sold-out or out-of-season products.",
            "default": false
          },
          "attractionsPageSize": {
            "title": "Results per request",
            "maximum": 100,
            "type": "integer",
            "description": "How many products to ask for per request while paging. Does not change the total — that is **Max accommodations**.",
            "default": 30
          },
          "attractionIds": {
            "title": "Attraction IDs",
            "type": "array",
            "description": "The attractions to look up, one id per line, e.g. `PRz8B1W8aYsI`. Used by **Attraction details** and **Attraction availability**. You get these ids from a run in mode **Attractions** (the `attractionId` field).",
            "items": {
              "type": "string"
            }
          },
          "attractionsStartDate": {
            "title": "Window start",
            "pattern": "^(\\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$|^(\\d+)\\s*(day|week|month|year)s?$",
            "type": "string",
            "description": "First date of the availability window. Used by **Attraction availability**, and also narrows **Attractions** to products bookable in the window. Accepts a relative value like \"2 weeks\"."
          },
          "attractionsEndDate": {
            "title": "Window end",
            "pattern": "^(\\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$|^(\\d+)\\s*(day|week|month|year)s?$",
            "type": "string",
            "description": "Last date of the availability window, absolute or relative. Booking returns no product at all for one with nothing bookable inside the window — the run reports that as `bookableInWindow: false` rather than dropping it."
          },
          "attractionsIncludeTimeslots": {
            "title": "Also fetch timeslots",
            "type": "boolean",
            "description": "Off by default. Adds the bookable timeslots for each available date — start time, ticket type, tickets left and price. Booking serves timeslots **one date per request**, so this costs one extra request per available date; **Timeslot days** caps how many.",
            "default": false
          },
          "attractionsTimeslotDays": {
            "title": "Timeslot days (cap)",
            "maximum": 14,
            "type": "integer",
            "description": "How many available dates to fetch timeslots for, at one request each. Only used with **Also fetch timeslots**. The run logs how many dates it skipped.",
            "default": 3
          }
        }
      },
      "runsResponseSchema": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "actId": {
                "type": "string"
              },
              "userId": {
                "type": "string"
              },
              "startedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "finishedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "status": {
                "type": "string",
                "example": "READY"
              },
              "meta": {
                "type": "object",
                "properties": {
                  "origin": {
                    "type": "string",
                    "example": "API"
                  },
                  "userAgent": {
                    "type": "string"
                  }
                }
              },
              "stats": {
                "type": "object",
                "properties": {
                  "inputBodyLen": {
                    "type": "integer",
                    "example": 2000
                  },
                  "rebootCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "restartCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "resurrectCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "computeUnits": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "options": {
                "type": "object",
                "properties": {
                  "build": {
                    "type": "string",
                    "example": "latest"
                  },
                  "timeoutSecs": {
                    "type": "integer",
                    "example": 300
                  },
                  "memoryMbytes": {
                    "type": "integer",
                    "example": 1024
                  },
                  "diskMbytes": {
                    "type": "integer",
                    "example": 2048
                  }
                }
              },
              "buildId": {
                "type": "string"
              },
              "defaultKeyValueStoreId": {
                "type": "string"
              },
              "defaultDatasetId": {
                "type": "string"
              },
              "defaultRequestQueueId": {
                "type": "string"
              },
              "buildNumber": {
                "type": "string",
                "example": "1.0.0"
              },
              "containerUrl": {
                "type": "string"
              },
              "usage": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "integer",
                    "example": 1
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_UNBLOCKER_UNITS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "usageTotalUsd": {
                "type": "number",
                "example": 0.00005
              },
              "usageUsd": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "number",
                    "example": 0.00005
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_UNBLOCKER_UNITS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}