# Booking.com Hotels - Search, Prices & Reviews (`s-r/booking-hotels`) Actor

Search Booking.com or look up individual hotels. Search returns name, price in two currencies, review score, room type and location per property. Detail mode adds the review score broken down by category, full postal address and nearby landmarks.

- **URL**: https://apify.com/s-r/booking-hotels.md
- **Developed by:** [SR](https://apify.com/s-r) (community)
- **Categories:** Travel, E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Booking.com Hotels - Search, Prices & Reviews

Two ways to use it, and you pick by what you give it.

**Search** a destination and get every property back as a structured row: name,
address with coordinates, star rating, guest review score and count, room type,
cancellation policy, and the total price for the stay you asked about.

**Look up hotels one by one** by pasting their page addresses, and get the
review score **broken down by category**, the full postal address and the
landmarks nearby. None of that appears in a search result.

Either way: your dates, your currency. No API key, no login.

### The prices are the ones a browser shows

Worth stating plainly, because a scraper quietly returning a different rate than
the site shows is the failure that costs you money.

The same Amsterdam search was run twice, once through this Actor and once in a
real Chrome window, and the results compared property by property. Of the 19
properties both runs saw:

| field | agreement |
|---|---|
| total price | **19 of 19 identical** |
| review score | 19 of 19 |
| review count | 19 of 19 |
| star rating | 19 of 19 |
| free cancellation | 19 of 19 |
| room type | 19 of 19 |

Not close. Identical, to the cent.

### What you get per property

**Identity and place** — `name`, `url`, `property_id`, `address`, `city`,
`country_code`, `latitude` and `longitude`, plus `district`,
`distance_from_centre`, `public_transport` and `is_centrally_located` as
Booking states them.

**Reputation** — `review_score` out of 10, `review_count`, `review_word`
("Excellent"), and `star_rating`.

A note on those two, because they are not the same kind of number.
`review_score` is built from guest reviews. `star_rating` is **supplied by the
property**, and Booking says so itself: it may be self-assigned rather than
awarded by any authority. Sorting a list by stars is sorting by what hotels
claim about themselves.

**The room** — `room_type`, `room_size_sqm` where published, `beds`, `bedrooms`,
`bathrooms`.

**The booking terms** — `free_cancellation`, `no_prepayment`, `price_includes`
(whether taxes and fees are in the number) and `deal_badges`.

**The stay** — `checkin`, `checkout` and `nights` on every single row, because a
price without its dates is not a price.

### Two prices, two currencies, and no guessing between them

Booking publishes every price **twice**: once in the currency your search asked
for, and once in the property's own. On one Amsterdam result those read
**US$559.93 and €481.39 for the same two nights**, a 16% gap that is entirely
exchange rate and rounding.

Picking whichever came first would build a table that silently mixes currencies
across rows. So both are returned, each with its code:

- `price_total` and `price_currency` — what you asked for
- `price_total_hotel_currency` and `hotel_currency` — the property's own
- `price_per_night` — `price_total` divided by `nights`

Ask for EUR on a European hotel and the second pair is empty, because there is
nothing to convert. Ask for EUR on a Tokyo hotel and you get €239.58 alongside
¥43,000.

The **Search from country** input is recorded on every run for the same reason.
Travel prices are regional, and a run that does not pin where it searched from
cannot be compared with the run before it.

### How many properties you get, and why paging is not the answer

**Booking's paging does not work for this.** Asking for later pages returns the
same properties again: offsets 25, 50 and 75 each returned 22 to 23 of the same
26 hotels, and four pages together yielded 34 distinct properties rather than
104\.

**Sort order does work.** The same search under different orderings surfaces
different slices of the inventory, because each ordering shows a different top
of the list. Measured on one Amsterdam search:

| sort orders used | distinct properties |
|---|---|
| one | 26 |
| three | 73 |
| four | 92 |
| five | 106 |

So **Sort orders to collect** is the real control over coverage. Each order you
add is one more request and roughly 25 more hotels. Every row records
`found_by_sort`, so you can see which ordering surfaced it, and the cheapest
hotels genuinely do arrive under the price ordering rather than the default.

### Detail mode: the review score broken apart

Paste hotel page addresses into **Hotel page addresses** and each one is fetched
directly. That page carries things a search row cannot:

**The category scores.** Booking scores seven things separately, and a search
result gives you none of them:

| | Zoku Amsterdam | The Savoy | Park Hyatt Tokyo |
|---|---|---|---|
| Overall | 8.9 | 9.4 | 9.3 |
| Staff | 9.3 | 9.7 | 9.4 |
| Cleanliness | 9.1 | 9.7 | 9.8 |
| Location | 8.9 | 9.7 | 9.2 |
| Value for money | **8.2** | **8.5** | **8.6** |
| Free wifi | 9.4 | 9.8 | 9.5 |

The spread is the story. All three score worst on value and best on staff or
cleanliness, which is a different fact about each hotel than its headline
number, and it is the one that decides between two properties rated the same.

**The full postal address**, split into street, region, postcode and country,
which is what makes these rows joinable against anything else you hold.

**Nearby landmarks**, the room names offered for your dates, the earliest
check-in time, and Booking's own description.

Two honest notes on that data. `address_area` is **not the city**: Booking files
The Savoy under "Strand" and Zoku under "Weesperstraat 105", so the field is
named for what it contains rather than what you might hope. And Booking scores
out of **10**, not 5; `review_score_scale` says so on every row so nothing has
to be assumed.

Every detail row also carries `subscores_agree_with_overall`, a cross-check that
the category block really belongs to that property.

### Filters

- **Hotel page addresses** — paste these for detail mode; leave empty to search
- **Destination** — city, region, district or landmark, in Booking's own words
- **Check-in / Check-out** — a search without dates has no prices at all, so a
  date is always used; leaving them empty gives a two-night stay 30 days out
- **Adults, Children, Rooms** — these change availability, not just the total
- **Currency** — the code the headline price is shown in
- **Maximum properties** — the ceiling on both time and cost

### Run summary

The search you ran, properties returned, how many carried a price and a review
score, which currencies appeared, and `requestsRetried`.

That last number is worth watching on a schedule. Occasionally a search comes
back without its results and is simply requested again; that is normal and is
handled inside the run. A retry count that climbs run after run is the early
warning that something changed, and it is far more useful than an empty dataset
with no explanation.

### What people use this for

**Rate monitoring.** Run the same destination and dates daily and keep the rows.
Because the stay dates are on every row and the currency is explicit, the series
is comparable without any cleanup.

**Competitor pricing.** For a hotel operator, the same search a guest would run,
with every rival's total for the same two nights beside your own.

**Market research.** Coordinates, star rating, review score and price in one
table is enough to map what a city charges by district and by segment.

**Travel aggregation.** One search, one table, one shape, with the currency
question already settled.

### Notes

Prices and availability are live and change through the day. A run is a
snapshot, which is exactly why the dates, the currency and the country searched
from are all recorded alongside the numbers.

A destination that matches nothing, or a stay with no nights in it, is reported
as exactly that rather than as an empty result you would have to interpret.

# Actor input Schema

## `hotel_urls` (type: `array`):

Paste Booking.com hotel page addresses to look each one up in detail instead of searching. Detail mode returns the review score broken down by category (cleanliness, staff, location, value, wifi, comfort, facilities), the full postal address and nearby landmarks, none of which a search result carries. Leave empty to search a destination instead.

## `destination` (type: `string`):

Where to search, as you would type it into Booking's own search box: a city, region, district or landmark, for example "Amsterdam". Ignored when hotel page addresses are given.

## `checkin` (type: `string`):

Arrival date as YYYY-MM-DD. Leave empty for 30 days from today. A search without dates returns no prices at all, so a date is always used.

## `checkout` (type: `string`):

Departure date as YYYY-MM-DD, after the arrival date. Leave empty for a two-night stay.

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

Number of adults. Prices and availability both depend on it, so this changes the result set, not only the total.

## `children` (type: `integer`):

Number of children in the party.

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

Number of rooms to price.

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

Three-letter code the headline price should be shown in, for example EUR, USD or GBP. Leave empty to accept Booking's own choice. The property's local-currency price is returned alongside it either way, whenever the two differ.

## `sort_orders` (type: `array`):

Booking's paging does not work: asking for later pages returns the same properties again. Different sort orders do return different properties, so each order you add is roughly another 25 hotels. Three orders returned 73 distinct properties where one returned 26.

## `region` (type: `string`):

Two-letter country code the search should appear to come from. Travel prices and availability are regional, so this is recorded with every run rather than left to chance.

## `max_results` (type: `integer`):

Stop after this many distinct properties. Also the cost ceiling for the run.

## Actor input object example

```json
{
  "hotel_urls": [
    "https://www.booking.com/hotel/nl/zoku-amsterdam.html"
  ],
  "destination": "Barcelona",
  "checkin": "2026-12-24",
  "checkout": "2026-12-27",
  "adults": 2,
  "children": 0,
  "rooms": 1,
  "currency": "USD",
  "sort_orders": [
    "top_picks",
    "price",
    "review_score"
  ],
  "region": "us",
  "max_results": 75
}
```

# Actor output Schema

## `properties` (type: `string`):

One row per property (search) or per hotel (detail).

## `summary` (type: `string`):

Search parameters, properties returned, currencies seen and how many requests had to be retried.

## `errors` (type: `string`):

Searches that could not be read.

# 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 = {
    "hotel_urls": [],
    "destination": "Amsterdam",
    "checkin": "2026-10-10",
    "checkout": "2026-10-12"
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/booking-hotels").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 = {
    "hotel_urls": [],
    "destination": "Amsterdam",
    "checkin": "2026-10-10",
    "checkout": "2026-10-12",
}

# Run the Actor and wait for it to finish
run = client.actor("s-r/booking-hotels").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 '{
  "hotel_urls": [],
  "destination": "Amsterdam",
  "checkin": "2026-10-10",
  "checkout": "2026-10-12"
}' |
apify call s-r/booking-hotels --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,s-r/booking-hotels"
        }
    }
}
```

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/axIHpbhmNUm26bElS/builds/ehEVk2DNBRVJmLU0X/openapi.json
