# Booking.com Market Supply & Facet Census (`apt_marble/booking-market-supply-census`) Actor

Profile the accommodation supply of any Booking.com market in one pass: star ratings, review-score bands, property types, neighbourhoods, brands, facilities, meal plans and cancellation policies, each with the number of properties behind it and its share of the market.

- **URL**: https://apify.com/apt\_marble/booking-market-supply-census.md
- **Developed by:** [Hamza](https://apify.com/apt_marble) (community)
- **Categories:** Travel, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.30 / 1,000 results

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/platform/actors/running/actors-in-store#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 Market Supply & Facet Census

Get a complete, structured picture of the accommodation supply in any Booking.com
market. Give it a city, a region, a district, an airport, a landmark or a
coordinate pair, and you get back every labelled option Booking.com publishes for
that market — star ratings, review-score bands, property types, neighbourhoods,
brands, facilities, room features, meal plans, reservation policies, payment
options, sustainability certification and accessibility — each one with the number
of properties behind it and its share of the market. It is the market-structure
dataset that hotel revenue teams, short-let operators, investors and analysts
normally assemble by hand, and it lands in one pass per market.

### What you can do with it

- **Size a market before you enter it.** How many 4-star hotels, apartments and
  vacation homes compete in Austin — and what fraction of the market each segment
  actually is.
- **Benchmark quality and price positioning.** Compare the review-score band
  distribution of 50 cities at once, and see which markets are dominated by
  8-and-above supply.
- **Map brand and chain penetration.** Which hotel brands are present in each
  market, and how many properties each of them runs there.
- **Track supply over time.** Schedule it weekly with the same stay window and
  watch new supply, neighbourhood shifts and the free-cancellation share move.
- **Find where the supply actually is.** Neighbourhood and distance-from-centre
  breakdowns show you which parts of a city carry the inventory.
- **Build filters and taxonomies for your own product.** Every option arrives with
  the exact identifier Booking.com uses for it, so you can drive your own
  segmented searches from this dataset.

### What you get

Two kinds of row. One per option in every group:

```json
{
  "recordType": "facetOption",
  "destination": "Austin",
  "destinationType": "CITY",
  "checkIn": "2026-09-10",
  "checkOut": "2026-09-12",
  "nights": 2,
  "leadTimeDays": 42,
  "adults": 2,
  "rooms": 1,
  "children": 0,
  "facetGroup": "class",
  "facetCategory": "class",
  "facetTitle": "Property rating",
  "optionId": "class=4",
  "optionValue": "4",
  "optionLabel": "4 stars",
  "count": 371,
  "countNotAutoextended": 0,
  "sharePctOfMarket": 57.43,
  "discoveredUnder": null,
  "destinationTotalProperties": 646,
  "currency": "USD",
  "actorRunId": "PBs4Xk2mZq7vN9Ld",
  "scrapedAt": "2026-09-01T09:14:22.531Z"
}
```

And one rollup row per market, from the same pass, abridged here:

```json
{
  "recordType": "marketSummary",
  "destination": "Austin",
  "destinationType": "CITY",
  "checkIn": "2026-09-10",
  "checkOut": "2026-09-12",
  "totalProperties": 646,
  "facetGroupsReturned": 24,
  "facetOptionsReturned": 130,
  "emptyFacetGroups": ["price"],
  "starClassMix": [
    { "optionId": "class=3", "label": "3 stars", "count": 143, "sharePct": 22.14 },
    { "optionId": "class=4", "label": "4 stars", "count": 371, "sharePct": 57.43 },
    { "optionId": "class=5", "label": "5 stars", "count": 10, "sharePct": 1.55 }
  ],
  "reviewScoreBands": [
    { "optionId": "review_score=90", "label": "Wonderful: 9+", "count": 221, "sharePct": 34.21 },
    { "optionId": "review_score=80", "label": "Very Good: 8+", "count": 363, "sharePct": 56.19 }
  ],
  "propertyTypeMix": [
    { "optionId": "ht_id=204", "label": "Hotels", "count": 204, "sharePct": 31.58 },
    { "optionId": "ht_id=220", "label": "Vacation Homes", "count": 236, "sharePct": 36.53 }
  ],
  "districtMix": [
    { "optionId": "di=4838", "label": "South Austin", "count": 89, "sharePct": 13.78 }
  ],
  "freeCancellationShare": 90.87,
  "freeCancellationCount": 587,
  "availableOnlyCount": 646,
  "brandsReturned": 20,
  "topBrands": [
    { "chainCode": "1851", "brandName": "Hampton by Hilton", "count": 9 },
    { "chainCode": "2117", "brandName": "Hilton Garden Inn", "count": 8 }
  ],
  "currency": "USD",
  "scrapedAt": "2026-09-01T09:14:22.531Z"
}
```

### Input reference

| Setting | Type | Default | What it does |
| --- | --- | --- | --- |
| Destinations | list of text | `["Austin"]` | The markets to profile. Place names (Austin, Lisbon, Bali) or coordinate pairs (`30.2672,-97.7431`). Up to 200 entries. |
| Destination type | choice | `City` | How to read your place names: City, Region, Country, District, Airport, Landmark or Coordinates. A coordinate pair is always read as coordinates. |
| Check-in date | date | 45 days out | First night of the stay the market is profiled for. Left empty, it rolls forward from the day the run starts, so a schedule never drifts into the past. |
| Check-out date | date | check-in + 2 days | Last day of the stay. |
| Adults | integer | `2` | Party size the counts are conditioned on. Max 30. |
| Rooms | integer | `1` | Rooms the counts are conditioned on. Max 30. |
| Children | integer | `0` | Children in the party. Max 10. |
| Children's ages | list of text | `[]` | One age per child, 0–17. Booking.com decides availability by a child's age, so fill this in whenever Children is above zero. |
| Only these option groups | list of text | `[]` (all) | Leave empty for every group Booking.com publishes. Narrow the dataset by naming groups such as `class`, `review_score`, `ht_id`, `di`, `chaincode`, `hotelfacility`, `mealplan`, `fc`. Up to 24. |
| Expand truncated option lists | boolean | `false` | Booking.com trims long lists to their leading entries. Switch this on to profile each market again inside the segments below, which brings more of the tail into view. |
| Segments used to expand the tail | list of text | `["class=3","class=4","class=5"]` | Segments each market is re-profiled inside, written as `group=value`. Up to 12. |
| Add a rollup row per market | boolean | `true` | Adds the one-row market summary shown above. It comes out of the same pass, so it is free. |
| Maximum markets per run | integer | `100` | Safety cap on how many markets one run profiles. Max 500. |
| Maximum lookups per run | integer | `600` | Hard budget for the run. One market needs one lookup, plus one more per segment when tail expansion is on. The run stops cleanly once the budget is spent. Max 6000. |
| Markets in parallel | integer | `4` | How many markets are profiled at the same time. Max 8. |
| Currency | text | `USD` | Three-letter code the market is read in. |
| Language | text | `en-us` | Language of the option labels. Labels outside English come back as Booking.com writes them. |
| Browse from country | text | `""` | Country to appear to browse from, as a two-letter code. |

### Output fields

#### Option rows (`recordType: "facetOption"`)

| Field | Type | Meaning |
| --- | --- | --- |
| `destination` | text | The market as you entered it |
| `destinationType` | text | How it was read (CITY, REGION, LATLONG …) |
| `checkIn`, `checkOut`, `nights`, `leadTimeDays` | date / number | The stay window the counts are conditioned on |
| `adults`, `rooms`, `children` | number | The party the counts are conditioned on |
| `facetGroup` | text | Group identifier, e.g. `class`, `ht_id`, `chaincode` |
| `facetCategory` | text | Booking.com's own grouping label |
| `facetTitle` | text | Human-readable group name, e.g. "Property rating" |
| `optionId` | text | The exact identifier for the option, e.g. `chaincode=1851` |
| `optionValue` | text | Just the value part, e.g. `1851` |
| `optionLabel` | text | The published label, e.g. "Hampton by Hilton" |
| `count` | number | Properties in this market carrying the option |
| `countNotAutoextended` | number | Booking.com's stricter count for the same option, published as-is |
| `sharePctOfMarket` | number | `count` as a percentage of the market total; `null` when the total is unknown |
| `discoveredUnder` | text | `null` for the market-wide pass; otherwise the segment that surfaced the option, and the count is a count within that segment |
| `destinationTotalProperties` | number | Properties listed in this market when the market was read |
| `currency`, `actorRunId`, `scrapedAt` | text | Run provenance |

#### Market rollup rows (`recordType: "marketSummary"`)

| Field | Type | Meaning |
| --- | --- | --- |
| `totalProperties` | number | Properties listed in the market for your stay window |
| `facetGroupsReturned`, `facetOptionsReturned` | number | How much structure the market published |
| `facetGroupNames`, `emptyFacetGroups` | list | Every group present, and which of them published no options |
| `starClassMix` | list | Star rating → count and share |
| `reviewScoreBands` | list | Review-score band → count and share |
| `propertyTypeMix` | list | Hotels, apartments, vacation homes … → count and share |
| `districtMix` | list | Neighbourhood → count and share |
| `bedroomCountMix`, `mealPlanMix` | list | Bedroom counts and meal arrangements → count and share |
| `freeCancellationShare`, `freeCancellationCount`, `freeCancellationOptionId` | number / text | Share of the market offering free cancellation, and the option it was measured from |
| `availableOnlyCount`, `availableOnlyOptionId` | number / text | Properties Booking.com counts as available for your dates |
| `brandsReturned`, `topBrands` | number / list | Brands present, biggest first, with their identifier and property count |
| `tailPreFiltersApplied` | list | Segments used for tail expansion on this market |
| `currency`, `actorRunId`, `scrapedAt` | text | Run provenance |

### Pricing

Pay per result. There is one charge, and it applies each time a row is written to
the dataset — an option row or a market rollup row, at the same price. There is no
second, more expensive tier, because there is nothing behind one that would cost
more: an entire market's structure, roughly 110 to 130 rows of it, is produced in
a single pass. Single-tier pricing is deliberate here, and it makes this the
cheapest row in the family.

| Charge | Applies when | Price |
| --- | --- | --- |
| Result row | Any option row or market rollup row is written | **$0.0003** per row ( **$0.30** per 1,000 rows ) |

**Worked example.** 100 markets, roughly 110 rows each, is about 11,000 rows —
**around $3.30** for a full 100-city supply census. Turning on tail expansion with
three segments profiles each market four times, so budget accordingly.

### Limits and what this actor cannot do

- **Counts are conditioned on the dates and the party you choose.** Booking.com
  only counts properties that can actually host your stay, so a census for a
  peak-season weekend is a different (and smaller) market than the same city
  mid-week. Keep the stay window fixed when you compare markets or track one over
  time.
- **Booking.com's own market total moves between identical searches.** We have
  seen the same city report 632, 646 and 647 within minutes. Treat
  `totalProperties` as a snapshot observation, not an audited figure — and treat
  small movements between runs as noise.
- **Long option lists are truncated by Booking.com.** Brands, neighbourhoods and
  property types are published as a leading slice, not a full list. Tail expansion
  recovers more of it, but it **cannot be proven exhaustive** — no complete list of
  every brand in a market is available.
- **No price bands.** Price is a slider on Booking.com, not a list of options, so
  the group is published with nothing in it. It is reported in `emptyFacetGroups`
  and produces no rows. If you need prices, use a price-oriented actor instead.
- **Brand identifiers are market-local.** They were confirmed in one market only,
  so join on `brandName` and treat `chainCode` as a convenience within that
  market.
- **This profiles markets, not single properties.** There is no way to point it at
  one hotel: a single-property lookup on Booking.com returns an identity and no
  market structure at all, so that destination type is deliberately not offered.
- **A market that has no availability for your dates publishes nothing.** That is
  reported in the log as an empty market, and it is a real answer, not a failure.
- **Speed depends on the size of the job and on how quickly Booking.com answers.**
  No fixed throughput is promised.
- **Booking.com's terms restrict automated access.** You are responsible for using
  this data lawfully and in line with the source site's own terms.

### FAQ

**Do I need a Booking.com account?**
No. No account, no login, no personal details — set your destinations and run it.

**How many rows should I expect per market?**
Around 110 to 130 for a mid-sized city, plus one rollup row. Small markets publish
fewer groups and fewer options, large ones publish more.

**Can I schedule it?**
Yes. Pin the check-in and check-out dates if you want a like-for-like series, or
leave them empty and every run will profile the same rolling window (45 days out,
two nights), which is usually what you want for a supply trend.

**Is the data complete?**
The set of groups is complete — you get every group Booking.com publishes for the
market. The options **inside** the long groups are not: Booking.com trims them.
Tail expansion recovers more of the tail and labels where each option came from,
but neither we nor anyone else can prove a brand or neighbourhood list is
exhaustive.

**Why is a share sometimes `null` instead of `0`?**
Because a `0` would look like a finding. When the market total is unknown or zero,
the share is genuinely unknowable, and the field says so rather than inventing a
number.

**What does `discoveredUnder` mean?**
It is `null` for the market-wide pass, where `count` is a count across the whole
market. When it names a segment, that row was found while profiling that segment
and its `count` is a count **within** the segment — the two are not
interchangeable.

**Can I profile a whole country?**
Yes — set the destination type to Country. Bear in mind that the counts are still
availability-conditioned, and that very large markets truncate their long option
lists more aggressively than cities do.

# Actor input Schema

## `destinations` (type: `array`):

The markets to profile. Type place names (Austin, Lisbon, Bali) or coordinate pairs (30.2672,-97.7431). One row set is produced per entry.

## `destinationType` (type: `string`):

How to read your place names. City covers most needs; use Region for a state or province, District for a neighbourhood, and Coordinates when you are passing lat,lon pairs. Coordinate pairs are always read as coordinates whatever you choose here.

## `checkIn` (type: `string`):

First night of the stay the market is profiled for, as YYYY-MM-DD. Leave empty to use a date 45 days from the day the run starts, which keeps scheduled runs from drifting into the past.

## `checkOut` (type: `string`):

Last day of the stay, as YYYY-MM-DD. Leave empty for a two-night stay starting on the check-in date.

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

Adults the stay is priced and counted for. Bigger parties reduce how many properties qualify.

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

Rooms the stay is counted for.

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

Children in the party. Booking.com decides availability by a child's age, so fill in Children's ages as well.

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

One age per child, 0 to 17. Required for accurate counts whenever Children is above zero.

## `includeFacetGroups` (type: `array`):

Leave empty to report every group Booking.com publishes for the market. To narrow the dataset, list group names such as class, review\_score, ht\_id, di, chaincode, hotelfacility, roomfacility, mealplan, fc, distance, popular\_activities or accessible\_facilities.

## `expandTail` (type: `boolean`):

Booking.com trims long lists (brands, neighbourhoods, property types) to the leading entries. Switch this on to profile each market again inside the segments below, which brings more of the tail into view. It multiplies the work per market and can never be proven complete.

## `tailPreFilters` (type: `array`):

Segments each market is re-profiled inside when tail expansion is on, written as group=value (for example class=5 for five-star properties or ht\_id=201 for apartments). Rows found this way are labelled with the segment that surfaced them and their counts are counts within that segment.

## `emitDestinationSummary` (type: `boolean`):

Adds one summary row per market with the total number of properties, the star mix, review-score bands, property-type and neighbourhood mixes, the free-cancellation share and the brands present. It comes out of the same lookup, so it adds no extra work.

## `maxDestinations` (type: `integer`):

Safety cap on how many markets one run will profile. Entries beyond it are reported in the log and skipped.

## `maxRequests` (type: `integer`):

Hard budget for the run. One market needs one lookup, plus one more per segment when tail expansion is on. The run stops cleanly once the budget is spent and says so in the log.

## `maxConcurrency` (type: `integer`):

How many markets are profiled at the same time. Four is a good balance; raise it for long lists.

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

Three-letter currency code the market is read in, for example USD, EUR or GBP.

## `language` (type: `string`):

Language of the option labels, for example en-us, en-gb, de or fr. Labels outside English are returned as Booking.com writes them.

## `proxyCountry` (type: `string`):

Country to appear to browse from, as a two-letter code such as us, gb or de. Leave empty for the default.

## Actor input object example

```json
{
  "destinations": [
    "Austin",
    "Lisbon",
    "30.2672,-97.7431"
  ],
  "destinationType": "CITY",
  "checkIn": "",
  "checkOut": "",
  "adults": 2,
  "rooms": 1,
  "children": 0,
  "childrenAges": [
    "4",
    "9"
  ],
  "includeFacetGroups": [
    "class",
    "review_score",
    "ht_id",
    "di",
    "chaincode"
  ],
  "expandTail": false,
  "tailPreFilters": [
    "class=5",
    "ht_id=201",
    "review_score=90"
  ],
  "emitDestinationSummary": true,
  "maxDestinations": 100,
  "maxRequests": 600,
  "maxConcurrency": 4,
  "currency": "USD",
  "language": "en-us",
  "proxyCountry": ""
}
```

# Actor output Schema

## `dataset` (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 = {
    "destinations": [
        "Austin"
    ],
    "destinationType": "CITY",
    "adults": 2,
    "rooms": 1,
    "children": 0,
    "expandTail": false,
    "tailPreFilters": [
        "class=3",
        "class=4",
        "class=5"
    ],
    "emitDestinationSummary": true,
    "maxDestinations": 100,
    "maxRequests": 600,
    "maxConcurrency": 4,
    "currency": "USD",
    "language": "en-us"
};

// Run the Actor and wait for it to finish
const run = await client.actor("apt_marble/booking-market-supply-census").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 = {
    "destinations": ["Austin"],
    "destinationType": "CITY",
    "adults": 2,
    "rooms": 1,
    "children": 0,
    "expandTail": False,
    "tailPreFilters": [
        "class=3",
        "class=4",
        "class=5",
    ],
    "emitDestinationSummary": True,
    "maxDestinations": 100,
    "maxRequests": 600,
    "maxConcurrency": 4,
    "currency": "USD",
    "language": "en-us",
}

# Run the Actor and wait for it to finish
run = client.actor("apt_marble/booking-market-supply-census").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "destinations": [
    "Austin"
  ],
  "destinationType": "CITY",
  "adults": 2,
  "rooms": 1,
  "children": 0,
  "expandTail": false,
  "tailPreFilters": [
    "class=3",
    "class=4",
    "class=5"
  ],
  "emitDestinationSummary": true,
  "maxDestinations": 100,
  "maxRequests": 600,
  "maxConcurrency": 4,
  "currency": "USD",
  "language": "en-us"
}' |
apify call apt_marble/booking-market-supply-census --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=apt_marble/booking-market-supply-census",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

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