# Booking.com Hotel Price Monitor & Availability (`aleksandrbooking/booking-hotel-price-monitor`) Actor

Nightly price and availability for every check-in date of a Booking.com hotel, up to a year ahead: cheapest rate, sold-out flag, rooms left, free cancellation deadline, tax-inclusion note. One row per hotel and date. Not affiliated with Booking.com.

- **URL**: https://apify.com/aleksandrbooking/booking-hotel-price-monitor.md
- **Developed by:** [Aleksandr Kobtsev](https://apify.com/aleksandrbooking) (community)
- **Categories:** Travel, Real estate, E-commerce
- **Stats:** 1 total users, 1 monthly users, 72.7% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 hotel dates

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

Track the nightly price and availability of any Booking.com hotel for every check-in date up
to a year ahead. One row per hotel and date: the cheapest rate for the stay and per night,
whether the date is sold out, how many rooms are left at that price, the free-cancellation
deadline, the meal plan, and whether the price already includes local taxes.

You bring the hotel page URLs. No login, no Booking.com account, no partner API. Run it once,
put it on a schedule, or call it from the API, and send the rows to Google Sheets, Slack,
Make, Zapier or an MCP client.

### Who is this Booking.com price monitor for?

| You are | What you run | What you get |
|---|---|---|
| A revenue manager watching a comp set | 10 hotel URLs, 90 dates | 900 rows: who is priced where, and who is full, for the next quarter |
| A short-let or small hotel owner | your own property, 180 dates, weekly | a rate calendar half a year out, refreshed every Monday |
| A deal hunter or travel planner | one hotel, 365 dates | the cheapest night in the year, and the sold-out weeks to avoid |
| Building a rate-shopping or BI product | any number of URLs through the API | a clean price series per hotel and date |

If you do not have the hotel links yet, the companion
[Booking.com Scraper](https://apify.com/aleksandrbooking/booking-scraper) lists the hotels of a
city, region or airport with their prices, and the `hotelUrl` of each row goes straight into this
Actor.

If you need every rate a hotel offers on a date, with room types, cancellation and prepayment
terms for each of them, use the companion Actor
[Booking.com Hotel Price Calendar Scraper](https://apify.com/aleksandrbooking/booking-rate-calendar).
It reads the full room table and costs more per row, because it drives a browser. This Actor
reads one price per date over plain HTTP, which is why it runs in 512 MB and finishes a year
of dates in about six minutes.

If you want what guests wrote about a hotel, the companion
[Booking.com Reviews Scraper](https://apify.com/aleksandrbooking/booking-reviews-scraper) returns
its reviews, one row per review. It takes the same hotel links, or the hotel numbers of this Actor.

### What makes this different from other Booking.com scrapers?

- **It monitors, it does not search.** Most Booking.com Actors answer "which hotels are in this
  city". This one answers "what does this hotel cost on the 10th, the 11th, the 12th, and is it
  still open", for the hotels you name.
- **It verifies before it bills.** Every answer is checked against what you asked for: the
  property itself, matched on the URL you pasted and not on a similar name in another town; the
  check-in and check-out dates the answer echoes back; and the currency of the price. A date
  that fails any of those is rejected and never charged.
- **It tells sold out from broken.** A date with no rooms comes back with `soldOut: true`, a
  real answer that people monitor availability for. A blocked request, a missing property or a
  malformed answer produces no row and no charge, and a hotel that cannot be read is named in
  the `FAILED_HOTELS` record so you can re-run exactly that.
- **It says whether taxes are in the price.** Booking.com quotes with or without local tax
  depending on where the request comes from. Every row carries `pricesIncludeTaxes`, so a
  calendar built on Monday compares correctly with one built on Tuesday.
- **A run that collects nothing fails.** It does not finish green with an empty dataset.

### What data does it return?

One row per hotel and check-in date.

| Field | What it is |
|---|---|
| **hotelUrl**, **hotelId**, **hotelName** | The property, as Booking.com identifies it. `hotelId` is the stable join key. |
| **checkIn**, **checkOut**, **nights** | The stay this row priced. |
| **adults**, **rooms**, **childrenAges** | The occupancy it was priced for. Change it and the prices change. |
| **currency** | Confirmed against the answer, not just requested. |
| **soldOut** | `true` when the property has nothing bookable for that stay. A billed row. |
| **totalPrice** | The cheapest rate for the whole stay. `null` when sold out. |
| **pricePerNight** | `totalPrice` divided by `nights`. |
| **priceBeforeDiscount** | The crossed-out price, when Booking.com shows one. |
| **pricesIncludeTaxes** | `true`, `false`, or `null` when the answer carried no tax information. See below. |
| **cheapestRoomId** | Booking.com's id of the room behind the cheapest rate. |
| **roomsLeft** | "Only N rooms left at this price" as a number; `null` when there is no such message. |
| **freeCancellationUntil** | When the cheapest rate stops being refundable; `null` if it never was. |
| **mealPlan** | Meal plan on the cheapest rate, when Booking.com prints one. |
| **scrapedAt** | When it was collected, ISO-8601 UTC. |

```json
{
  "hotelUrl": "https://www.booking.com/hotel/ge/radisson-blu-batumi.html",
  "hotelId": "320991",
  "hotelName": "Radisson Blu Hotel Batumi",
  "checkIn": "2026-11-04",
  "checkOut": "2026-11-05",
  "nights": 1,
  "adults": 2,
  "rooms": 1,
  "childrenAges": [],
  "currency": "USD",
  "soldOut": false,
  "totalPrice": 227.57,
  "pricePerNight": 227.57,
  "priceBeforeDiscount": null,
  "pricesIncludeTaxes": true,
  "cheapestRoomId": "32099112",
  "roomsLeft": null,
  "freeCancellationUntil": null,
  "mealPlan": null,
  "scrapedAt": "2026-09-23T12:00:00.000Z"
}
```

**What to do with it.** Put the run on a weekly or daily schedule, send the dataset to Sheets
or BigQuery, and key rows on `hotelId` plus `checkIn`. A change in `pricePerNight` is a rate
move; a change in `soldOut` is the market filling up; `roomsLeft` dropping is the last warning
before it does.

### Do Booking.com prices include tax?

Booking.com decides whether to quote with or without local tax from the country it infers for
the request. From the default Apify datacenter addresses the quote is tax-exclusive and the row
says `pricesIncludeTaxes: false`; from most residential addresses it is tax-inclusive and the
row says `true`. Measured on one Georgian property, same date, minutes apart: 190 USD from a
datacenter address, 220 USD from a home address, the gap being local VAT.

Both are genuine prices. What breaks a calendar is mixing them without noticing, which is why
every row states the regime. Whatever address you run from, compare rows with the same value
of `pricesIncludeTaxes`. To get the tax-inclusive quote, run through your own proxies in the
country you want to buy from.

### How much does it cost?

**One billed row is one hotel on one check-in date.** Thirty dates are thirty rows on every
hotel, always, so the bill is predictable before you start.

| Event | Price | When |
|---|---|---|
| Hotel date | $0.0015, falling to $0.001 by plan | Per row that reaches the dataset |
| Actor start | $0.00005 | Once per run; at 512 MB it is a single start event |
| Result | $0.00001 | Per stored row, about one cent per thousand rows |

The hotel-date price steps down with your Apify plan: $0.0015 on the free plan and on Bronze,
$0.00125 on Silver, and $0.001 on Gold and above.

What that means for real jobs, all three charges included:

| Job | Rows | Free plan | Gold and above |
|---|---|---|---|
| One hotel, a month ahead | 30 | **$0.05** | **$0.03** |
| One hotel, a quarter ahead | 90 | **$0.14** | **$0.09** |
| One hotel, a year ahead | 365 | **$0.55** | **$0.37** |
| Ten competitors, a quarter ahead | 900 | **$1.36** | **$0.91** |
| One property, 60-date window, refreshed daily for a month | 1,800 | **$2.72** | **$1.82** |

Sold-out dates are charged, because "no rooms on that date" is a real answer. Nothing else
is: not a blocked request, not an answer for the wrong property, wrong dates or wrong currency,
and not a malformed answer.

Set **Maximum total charge** on the run to cap spending. The Actor stops cleanly at the budget
and names the hotels it did not reach in the `FAILED_HOTELS` key-value store record.

### How to monitor Booking.com hotel prices

1. Open the hotel on Booking.com and copy the page URL. Anything like
   `https://www.booking.com/hotel/ge/radisson-blu-batumi.html` works. Dates, language and
   tracking parameters already in the link are ignored.
2. Paste one or more URLs into **Hotel page URLs**.
3. Choose the first check-in date, how many dates to cover, nights per stay, guests and
   currency.
4. Run it. Download the dataset as JSON, CSV or Excel, or read it through the API.
5. For a price history, put the run on a schedule and keep every run's dataset.

| Field | Default | What it does |
|---|---|---|
| `hotelUrls` | none | Booking.com hotel page links, one per line. A link carries no hotel number, so the hotel is looked up by the name in it; about two links in three are found. A link that is not found is listed in `FAILED_HOTELS` and not billed. |
| `hotelIds` | none | Booking.com's own hotel numbers, such as `320991`: the `hotelId` column of the [Booking.com Scraper](https://apify.com/aleksandrbooking/booking-scraper) output. Exact, no lookup. Use it for the links that are not found. Can be combined with `hotelUrls`. |
| `startDate` | tomorrow | First check-in date, `YYYY-MM-DD` (UTC). Today is allowed; a past date is moved to today. |
| `days` | 90 | Number of consecutive check-in dates, **per hotel**. Each hotel and date pair is one billable row. Up to 365. |
| `nights` | 1 | Length of stay for every check-in date. |
| `adults` | 2 | Adult guests. Occupancy changes prices, so compare like for like. |
| `childrenAges` | `[]` | Age of each child, e.g. `[5, 9]`. Booking.com prices children by age. |
| `rooms` | 1 | Rooms to price. |
| `currency` | `USD` | Verified in every answer. A wrong currency means the date is rejected and not charged. |
| `concurrency` | 2 | Dates requested at the same time. |
| `proxy` | Apify datacenter | Measured on Booking.com: 635 of 635 requests answered. Apify residential groups are refused before any row is charged. For a specific country, or a residential address, choose Custom proxies and supply your own. The country decides `pricesIncludeTaxes`. |
| `dryRun` | `false` | Owner-only switch for the maintainer's own monitoring. Ignored for everyone else. |

### How fast is it, and what does it not do?

Measured from the default Apify datacenter proxy: about one second per date, a year of dates
for one hotel in six minutes, nine hotels over a month each in under five.

- One price per date: the cheapest rate Booking.com shows for your occupancy. For every rate
  with its room type and terms, use the companion Actor linked above.
- Prices are what Booking.com showed at scrape time. They move constantly; scrape on a schedule
  for a trend.
- `priceBeforeDiscount`, `roomsLeft`, `freeCancellationUntil` and `mealPlan` are `null` when
  Booking.com prints nothing for them.
- Member-only Genius discounts need a logged-in session and are not included.
- It does not collect reviews, photos, guest names or any personal data.

### FAQ

**Can I monitor several hotels at once?** Yes, one URL per line. Each hotel is handled in
turn, from its own proxy address.

**How far ahead can it price?** Up to 365 check-in dates per hotel per run. Many hotels open
bookings less than a year ahead: past that point Booking.com has no rooms to show, and those
dates come back as `soldOut: true`, the same as a sold-out night. Measured on a Lisbon hotel in
October 2026: bookable until 31 March 2027, then 187 dates in a row with no rooms. A seasonal
resort's closed weeks look the same way.

**Do I need a Booking.com account or an API key?** No.

**What if a hotel URL is dead?** A property that cannot be found by its URL is skipped and
named in the `FAILED_HOTELS` record. Nothing is charged for it.

**Why is the price different from the hotel page I see in my browser?** Almost always the tax
regime: see the section above and the `pricesIncludeTaxes` field. Occupancy is the other
reason; two adults and a family with children are priced differently.

**Is it legal to scrape Booking.com?** This is an unofficial tool, not affiliated with,
endorsed by or connected to Booking.com or Booking Holdings Inc. It reads publicly visible
prices and collects no personal data. Booking.com's terms of service restrict automated
access, and you are responsible for making sure your use complies with those terms and with
the law in your jurisdiction. See
[Apify's stance on web scraping legality](https://blog.apify.com/is-web-scraping-legal/).

### Found a problem?

Open an issue on the Issues tab with the hotel URL and the run ID. Reports get read.

# Changelog

This Actor's version history is a separate document: https://apify.com/aleksandrbooking/booking-hotel-price-monitor/changelog.md

# Actor input Schema

## `hotelUrls` (type: `array`):

Links to Booking.com hotel pages, one per line, e.g. https://www.booking.com/hotel/ge/radisson-blu-batumi.html. Dates, language and tracking parameters already in the link are ignored; the Actor sets its own. Booking.com does not put the hotel's number in a link, so the Actor looks the hotel up by the name in the link: about two links in three are found, and one that is not is listed in FAILED_HOTELS and not billed. Give such a hotel by number below.

## `hotelIds` (type: `array`):

Booking.com's own numbers of hotels, like 320991: the "hotelId" column of the Booking.com Scraper Actor's output. A number is exact and needs no lookup. Can be combined with links.

## `startDate` (type: `string`):

YYYY-MM-DD (UTC). Leave empty to start from tomorrow. Today is allowed; a date in the past is moved to today.

## `days` (type: `integer`):

Number of consecutive check-in dates, per hotel. Each hotel and date pair is one billable row: 5 hotels over 90 dates is 450 rows. Booking answers up to a year ahead.

## `nights` (type: `integer`):

Length of stay used for every check-in date. The row carries the total for the stay and the price per night.

## `adults` (type: `integer`):

Number of adult guests. Occupancy changes the price, so compare like for like.

## `childrenAges` (type: `array`):

Age of each child as a JSON list, e.g. \[5, 9]. Booking prices children by age, so a plain count is not enough. Leave empty for adults only.

## `rooms` (type: `integer`):

Number of rooms to price for the same stay.

## `currency` (type: `string`):

Prices are verified to come back in this currency. If Booking.com answers in a different one, the date is rejected and not charged.

## `concurrency` (type: `integer`):

How many dates are requested at the same time. Measured stable at 2 from one address; higher is faster but more likely to be throttled.

## `proxy` (type: `object`):

Apify datacenter proxies are the default and what this Actor is priced on: measured on Booking.com, 635 of 635 requests answered. Apify residential groups are not available here; a run that asks for one is refused in the first seconds, before any row is charged. For a specific country, or a residential address, choose Custom proxies and supply your own. The country decides whether Booking quotes prices with or without local tax, which the row reports as pricesIncludeTaxes.

## `dryRun` (type: `boolean`):

Produce data without charging events. Honored only for runs started by the Actor owner; ignored for everyone else.

## Actor input object example

```json
{
  "hotelUrls": [
    "https://www.booking.com/hotel/ge/radisson-blu-batumi.en-gb.html"
  ],
  "hotelIds": [],
  "days": 30,
  "nights": 1,
  "adults": 2,
  "childrenAges": [],
  "rooms": 1,
  "currency": "USD",
  "concurrency": 2,
  "proxy": {
    "useApifyProxy": true
  },
  "dryRun": false
}
```

# Actor output Schema

## `priceCalendar` (type: `string`):

No description

## `failedHotels` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "hotelUrls": [
        "https://www.booking.com/hotel/ge/radisson-blu-batumi.en-gb.html"
    ],
    "days": 30
};

// Run the Actor and wait for it to finish
const run = await client.actor("aleksandrbooking/booking-hotel-price-monitor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "hotelUrls": ["https://www.booking.com/hotel/ge/radisson-blu-batumi.en-gb.html"],
    "days": 30,
}

# Run the Actor and wait for it to finish
run = client.actor("aleksandrbooking/booking-hotel-price-monitor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "hotelUrls": [
    "https://www.booking.com/hotel/ge/radisson-blu-batumi.en-gb.html"
  ],
  "days": 30
}' |
apify call aleksandrbooking/booking-hotel-price-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,aleksandrbooking/booking-hotel-price-monitor"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/l4OrZsGlih7U12IzN/builds/oqeyuX6XLBs9rpU0M/openapi.json
