# Booking.com MCP Tracker — Hotel Drop Alerts + AI (`harvestlab/booking-scraper`) Actor

RAG-ready Booking.com hotel feed for travel-research agents — prices, reviews, amenities, PDP enrichment. Cross-run rate-history tracks 7/30-day deltas with price-drop, volatile, new-low alerts. 99%+ run success. AI via 5 LLM providers. Built for revenue managers and travel agencies. x402-ready.

- **URL**: https://apify.com/harvestlab/booking-scraper.md
- **Developed by:** [Nick](https://apify.com/harvestlab) (community)
- **Categories:** Travel, Business, MCP servers
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, NaN bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use 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 Scraper - Rate History, Alerts & PDP Enrichment
> 🧩 **Part of the harvestlab MCP suite** — 36 RAG-ready, AI-agent-payment-ready Apify actors covering ecommerce, social, travel, news, jobs, EU B2B, dev-tools, and government data. [See the full suite →](https://apify.com/harvestlab)


Extract hotel listings, nightly prices, guest reviews, star ratings, and amenities from Booking.com search results — plus **cross-run rate-history tracking**, a **rate-alert tier** that fires only when a hotel hits your price-drop / volatility / new-low threshold, and an optional **PDP enrichment mode** that visits each hotel's detail page for descriptions, full amenities, check-in/out times, and house rules.

Whether you need hotel price comparison data for a travel app, competitive analysis for revenue management, or hospitality market research at scale, this actor delivers structured, ready-to-use hotel data from the world's largest travel platform. Re-run it on the same destination and dates — it remembers across runs with no external database required.

### What it does

This actor scrapes Booking.com search results and hotel detail pages, then layers on rate intelligence that standard scrapers never ship. On every run it extracts hotel names, nightly prices, star ratings, guest review scores, amenity lists, and photos. Enable `fetchDetails` and it deep-crawls each hotel's PDP for full descriptions, check-in/out times, house rules, and languages spoken.

The real differentiator is **cross-run rate-history tracking**. Every price is persisted to a named Apify key-value store keyed by `(destination, check-in, check-out)`, so each subsequent run automatically computes 7-day and 30-day price deltas, volatility flags, rolling min/max/avg, and a rate-movement digest that surfaces the biggest movers — with zero external database setup.

Pair that with the **rate-alert tier** and you have a standing price-watch service: set `alertMode` to `price_drop`, `volatile`, or `new_low`, and the actor only charges (and fires a dataset item) when a hotel actually breaches your threshold. You pay per actionable signal, not per page scraped.

Optional **AI market analysis** — backed by OpenRouter, Anthropic, Google AI, OpenAI, or self-hosted Ollama — delivers a structured narrative covering pricing trends, quality distribution, competitive positioning, and booking-window recommendations derived from your rate history. When rate-history is enabled, the AI report gets a dedicated Rate Dynamics section with 7/30-day delta commentary and booking-window advice.

#### How this compares to other Booking.com scrapers on the Apify Store

| Actor | Price model | Rate-history | AI analysis |
|-------|-------------|--------------|-------------|
| This actor | $0.005/hotel + events | Yes — 7/30-day deltas + alerts | Yes — 5-provider LLM |
| voyager/booking-scraper | $0.002/hotel | No | No |
| automation-lab/booking-scraper | $0.005/hotel + $0.05/run fee | No | No |
| scrapepilot/booking-com-hotel-scraper | Subscription | No | No |

This is the only Booking.com scraper on the Store that bundles AI-powered market analysis AND cross-run rate-history tracking. Per-hotel pricing matches automation-lab but without the $0.05 run fee, and there is no subscription lock-in.

#### Pricing

| Event | Price | When charged |
|-------|-------|--------------|
| `hotel-scraped` | $0.005 | Per hotel listing extracted |
| `detail-scraped` | $0.01 | Per hotel PDP enriched (`fetchDetails=true`) |
| `rate-alert-triggered` | $0.02 | Per hotel matching the `alertMode` criterion |
| `ai-analysis-completed` | $0.05 | Per AI market analysis report (one per run) |

**Cost examples:**

| Scenario | Hotels | PDP | AI | Alerts | Total |
|----------|--------|-----|----|--------|-------|
| Quick price check | 10 | No | No | 0 | $0.05 |
| Destination comparison | 50 | No | No | 0 | $0.25 |
| Full market analysis | 50 | No | Yes | 0 | $0.30 |
| Enriched market audit | 50 | Yes | Yes | 0 | $0.80 |
| Daily rate-watch | 50 | No | No | 3 | $0.31 |
| Premium revenue-mgmt stack | 50 | Yes | Yes | 5 | $0.90 |

**vs. commercial alternatives**: OTA Insight charges $200+/mo and RateGain uses subscription pricing for hotel rate intelligence APIs. This actor uses pay-per-event with no subscription: $0.005/hotel and zero monthly fees.

#### Typical runtime

- 10 hotels without reviews: 1–2 minutes
- 50 hotels without reviews: 3–5 minutes
- 50 hotels with reviews: 8–12 minutes
- AI analysis adds ~15–30 seconds per run

### Features

- **Full hotel data extraction** — name, price per night, currency, star rating, guest review score, review count, location, property type, amenities, and photos
- **PDP enrichment mode** — enable `fetchDetails` to deep-crawl each hotel's detail page for `description`, full `amenities_full[]`, `property_type`, `checkin_time`, `checkout_time`, `house_rules[]`, and `languages_spoken[]`. Charged separately at $0.01 per PDP
- **Rate-alert tier** — pick an `alertMode` (`price_drop`, `volatile`, `new_low`, or `all`), set `alertThresholdPct`, and the actor emits a dedicated `hotel_rate_alert` dataset item only when a hotel breaches the threshold. Pay per actionable alert ($0.02 each), not per scrape. Combine with Apify's scheduler for a standing rate-watch service
- **Cross-run rate-history tracking** — persist every run's prices to a named key-value store and emit `delta_pct_7d`, `delta_pct_30d`, `direction`, `is_volatile`, `rate_min_30d`, `rate_max_30d`, `rate_avg_30d` on every hotel. A rate-movement digest item per run surfaces the biggest 7-day hikes, drops, and most volatile properties. Free feature, no extra HTTP calls
- **5 sort options** — sort results by popularity, price (low to high), price (high to low), guest rating, or star rating
- **Guest review scraping** — collect up to 50 reviews per hotel with reviewer name, score, positive/negative comments, and date
- **Lazy-load handling** — automatically scrolls through Booking.com's dynamically loaded search results to capture all listings
- **Flexible search input** — search by destination name or paste a full Booking.com search URL with pre-applied filters
- **Check-in/check-out dates** — get accurate nightly pricing for your specific travel dates
- **AI market analysis with rate dynamics** — optional LLM-powered report covering pricing trends, quality distribution, competitive positioning, strategic recommendations, and when rate-history is enabled, a dedicated Rate Dynamics section
- **Multi-LLM support** — choose between OpenRouter (recommended — 300+ models), Anthropic (Claude), Google AI (Gemini), OpenAI (GPT), or Ollama (self-hosted)
- **Residential proxy support** — built-in configuration for Apify residential proxies, critical for reliable Booking.com scraping

### Use Cases

- **Hotel revenue managers** — track competitor pricing across your market daily. Identify pricing gaps, monitor rate parity, and inform dynamic pricing strategies with real data instead of manual checks. The rate-alert tier sends actionable signals only when a competitor actually moves price, so you are not buried in noise
- **Travel tech companies** — feed structured hotel data into price comparison engines, travel planning apps, and deal alert systems. Build fare trackers and accommodation aggregators without maintaining your own scraping infrastructure
- **Hospitality market researchers** — analyze pricing trends, quality distribution, and competitive positioning across destinations. Generate reports on market saturation and opportunity zones using the AI market analysis report
- **Hotel owners and operators** — understand how your property stacks up against competitors on price, reviews, and amenities. The rate-history tracking gives you a rolling 30-day baseline, so you can see whether competitor pricing is trending up or down over time
- **Real estate investors in hospitality** — evaluate hotel markets before acquiring properties. Compare revenue potential across locations using actual pricing and occupancy signals from Booking.com combined with AI investment analysis
- **OTAs and travel aggregators** — collect structured hotel data at scale. The PDP enrichment mode delivers full descriptions, amenity lists, house rules, and check-in/out windows — everything needed to populate a property listing without separate API contracts
- **Travel bloggers and content creators** — gather accurate, up-to-date accommodation data for destination guides, deal roundups, and travel recommendation content
- **Academic researchers** — collect hospitality pricing datasets for tourism economics studies, market efficiency research, and price discrimination analysis

### Pair with the Hotel Reputation Suite

Booking.com Scraper covers the **price + amenities + rate-history** half of hotel intelligence. Pair it with [`harvestlab/ai-review-analyzer`](https://apify.com/harvestlab/ai-review-analyzer) (publishing in the next quota window) for the **review-quality** half — AI sentiment scoring, SimHash duplicate detection, astroturfing scores, and cross-platform consistency analysis. Together they form a full-stack Hotel Reputation Suite: every property gets paired pricing/availability data and review-authenticity signals from one workflow.

This pairing is the buyer-curated context Apify highlighted in its [Best hotel reputation management tools in 2026](https://blog.apify.com/) blog post (2026-04-17), which positioned scraping plus review-intelligence as the canonical stack for hotel reputation work.

#### Suite use cases

- **Revenue managers** reconcile rate-history shifts from this actor against review-sentiment trends from `ai-review-analyzer` — when a competitor's nightly price drops while their sentiment score deteriorates, you have a defensible signal to hold or raise your own rate instead of chasing the discount.
- **Travel agencies and OTAs** vet hotels before recommending them. Pull pricing and amenities here, then run the review feed through `ai-review-analyzer` to flag astroturfing or duplicate-review clusters. Hotels with clean reputation scores graduate to your recommended list; flagged ones get a second look.
- **Hotel owners and operators** benchmark their own property against direct competitors on both axes. This actor surfaces price gaps and amenity mismatches; `ai-review-analyzer` surfaces sentiment drivers and authenticity scores. The combined view answers "Are competitors winning on price, on perception, or both?"
- **PR and comms teams** track reputation crises with rate-impact analysis. When a hotel hits the news, the rate-alert tier here flags whether competitors are repricing in response, while `ai-review-analyzer` tracks the sentiment shift across platforms — letting communicators correlate narrative and revenue impact in near real time.

The two actors share the same input ergonomics (residential proxy recommended, 5-provider LLM stack, pay-per-event monetization), so existing booking-scraper users can layer the reputation half onto current schedules without retooling.

### Use with AI agents

booking-scraper outputs hotels + 7/30-day rate-history deltas + amenities + AI market analysis as structured JSON — a RAG-ready agent tool for revenue-managers, travel-research agents, and reputation-monitoring workflows. Wrap it as a tool inside a hotel-revenue or travel-planning agent and replace manual rate-shopping or per-call STR/OTA Insight pulls.

**LangChain — `track_booking_hotels` tool (revenue-manager / travel-agency alternative):**

```python
from langchain.tools import Tool
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")

def track_booking_hotels(params: dict) -> list:
    run = client.actor("harvestlab/booking-scraper").call(run_input={
        "destination": params["destination"],
        "checkIn": params.get("checkIn"),
        "checkOut": params.get("checkOut"),
        "fetchDetails": params.get("fetchDetails", True),
        "enableRateHistory": True,
        "enableAiAnalysis": True,
        "proxyConfiguration": {"useApifyProxy": True, "apifyProxyGroups": ["RESIDENTIAL"]},
    })
    return list(client.dataset(run["defaultDatasetId"]).iterate_items())

track_booking_hotels_tool = Tool(
    name="track_booking_hotels",
    description="Scrape Booking.com hotels with 7/30-day rate-history deltas, PDP amenities, and 5-provider AI market analysis. RAG-ready for revenue-management and travel-research agents.",
    func=track_booking_hotels,
)
## agent.invoke({"input": "Track Amsterdam hotels for 2026-06-01 to 2026-06-04 and flag any price drops"})
````

**LangGraph — node in a hotel-revenue graph:**

```python
from langgraph.graph import StateGraph
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")

def hotels_node(state: dict) -> dict:
    run = client.actor("harvestlab/booking-scraper").call(run_input={
        "destination": state["city"],
        "checkIn": state["checkIn"],
        "checkOut": state["checkOut"],
        "maxListings": 50,
        "enableRateHistory": True,
        "alertMode": "price_drop",
        "alertThresholdPct": 10,
    })
    items = list(client.dataset(run["defaultDatasetId"]).iterate_items())
    hotels = [i for i in items if i.get("type") != "hotel_rate_alert" and i.get("type") != "rate_movement_digest"]
    ranked = sorted(hotels, key=lambda h: (h.get("review_score") or 0), reverse=True)
    price_drops = [h for h in hotels if (h.get("delta_pct_7d") or 0) <= -10]
    return {**state, "ranked_hotels": ranked, "price_drop_flagged": price_drops}

graph = StateGraph(dict)
graph.add_node("hotels", hotels_node)
## wire into downstream rate-recommendation / pace-vs-comp / Slack-alert nodes
```

See Apify's [`actor-templates/js-langchain`](https://github.com/apify/actor-templates/tree/master/templates/js-langchain) and [`js-langgraph-agent`](https://github.com/apify/actor-templates/tree/master/templates/js-langgraph-agent) for full reference setups.

### Input

All parameters are optional except for at least one of `searchUrl` or `destination`.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `searchUrl` | string | — | Full Booking.com search URL (overrides destination/date params) |
| `destination` | string | — | City or area name (e.g. "Amsterdam", "Paris 6th arrondissement") |
| `checkIn` | string | — | Check-in date in YYYY-MM-DD format |
| `checkOut` | string | — | Check-out date in YYYY-MM-DD format |
| `currency` | string | EUR | 3-letter ISO 4217 currency code for prices |
| `maxListings` | integer | 25 | Maximum hotels to scrape (1–100) |
| `includeReviews` | boolean | false | Scrape top guest reviews per hotel |
| `maxReviewsPerHotel` | integer | 10 | Reviews to collect per hotel (1–50) |
| `sortBy` | string | popularity | Sort order: `popularity`, `price_low`, `price_high`, `rating`, `stars_high` |
| `enableRateHistory` | boolean | true | Track prices across runs and emit 7/30-day deltas and volatility flags. Free feature |
| `fetchDetails` | boolean | false | Visit each hotel's PDP to enrich with description, full amenities, check-in/out times, house rules, and languages. $0.01 per PDP |
| `alertMode` | string | off | Rate-alert mode: `off`, `price_drop`, `volatile`, `new_low`, or `all`. Requires rate-history. $0.02 per alert fired |
| `alertThresholdPct` | integer | 10 | Percentage threshold for `price_drop` and `volatile` alert modes |
| `enableAiAnalysis` | boolean | false | Generate AI-powered market analysis report |
| `llmProvider` | string | openrouter | LLM provider: `openrouter`, `anthropic`, `google`, `openai`, or `ollama` |
| `llmModel` | string | — | Specific model identifier (leave empty for recommended default) |
| `openrouterApiKey` | string | — | OpenRouter API key |
| `anthropicApiKey` | string | — | Anthropic API key |
| `googleApiKey` | string | — | Google AI API key |
| `openaiApiKey` | string | — | OpenAI API key |
| `ollamaBaseUrl` | string | http://localhost:11434 | Base URL for self-hosted Ollama |
| `proxyConfiguration` | object | — | Proxy settings (residential strongly recommended) |

Provide either `searchUrl` (a full Booking.com search URL with filters pre-applied) or `destination` (a city/area name). When using `destination`, set `checkIn` and `checkOut` for accurate pricing — without dates, Booking.com may show estimated prices or no prices at all.

#### Full example input

```json
{
    "destination": "Amsterdam",
    "checkIn": "2026-06-01",
    "checkOut": "2026-06-04",
    "maxListings": 30,
    "includeReviews": true,
    "maxReviewsPerHotel": 5,
    "sortBy": "rating",
    "enableRateHistory": true,
    "alertMode": "price_drop",
    "alertThresholdPct": 10,
    "fetchDetails": true,
    "enableAiAnalysis": true,
    "llmProvider": "openrouter",
    "openrouterApiKey": "sk-or-...",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": ["RESIDENTIAL"]
    }
}
```

#### CLI aliases

The following hidden fields accept the same values as their canonical counterparts and are accepted via the Apify CLI or API for backward compatibility: `searchQuery`, `query`, `city`, `location` (alias for `destination`); `checkInDate` (alias for `checkIn`); `checkOutDate` (alias for `checkOut`); `maxHotels`, `maxResults`, `maxItems` (aliases for `maxListings`); `includeDetails` (alias for `fetchDetails`).

### Output

Each hotel is pushed as a separate dataset item. Below is a complete example including rate-history and PDP fields:

```json
{
    "name": "Hotel V Nesplein",
    "url": "https://www.booking.com/hotel/nl/v-nesplein.html",
    "price_per_night": 189.0,
    "currency": "EUR",
    "stars": 4,
    "review_score": 9.1,
    "review_count": 4521,
    "location": "Amsterdam City Center",
    "distance_from_center": "0.6 km from downtown",
    "property_type": "Hotel",
    "breakfast_included": true,
    "free_cancellation": true,
    "amenities": ["Free WiFi", "Breakfast included", "Bar", "24-hour front desk"],
    "image_url": "https://cf.bstatic.com/xdata/images/hotel/...",
    "reviews": [
        {
            "reviewer": "John",
            "score": 9.0,
            "positive": "Perfect location, very friendly staff",
            "negative": "Room was a bit small",
            "date": "March 2026"
        }
    ],
    "direction": "up",
    "delta_pct_7d": 7.14,
    "delta_pct_30d": 50.0,
    "is_volatile": false,
    "rate_previous": 145.0,
    "rate_min_30d": 100.0,
    "rate_max_30d": 189.0,
    "rate_avg_30d": 161.25,
    "history_sample_count": 12,
    "description": "Hotel V Nesplein enjoys a central location in the heart of Amsterdam...",
    "amenities_full": ["Free WiFi", "Bar", "Restaurant", "Room service", "24-hour front desk", "Airport shuttle", "Spa", "Fitness centre"],
    "checkin_time": "From 15:00",
    "checkout_time": "Until 11:00",
    "house_rules": ["No pets allowed", "Children under 12 stay free when using existing beds"],
    "languages_spoken": ["English", "Dutch", "German", "French"],
    "details_scraped": true,
    "scraped_at": "2026-04-10T12:00:00+00:00"
}
```

#### Field reference

| Field | Type | Description |
|-------|------|-------------|
| `name` | string | Hotel name as shown on Booking.com |
| `url` | string | Canonical Booking.com hotel page URL (tracking params stripped) |
| `price_per_night` | float | Nightly price in the selected currency (null if dates not provided) |
| `currency` | string | 3-letter ISO 4217 currency code (e.g. `EUR`, `USD`) |
| `stars` | int | Official star rating 1–5 (null for apartments / hostels) |
| `review_score` | float | Guest review score 0.0–10.0 |
| `review_count` | int | Total number of guest reviews |
| `location` | string | Neighborhood or area, falls back to distance from center |
| `distance_from_center` | string | Human-readable distance from downtown |
| `property_type` | string | `Hotel`, `Apartment`, `Hostel`, `Resort`, etc. |
| `breakfast_included` | bool | Whether breakfast is included in the listed price |
| `free_cancellation` | bool | Whether the rate offers free cancellation |
| `amenities` | list\[string] | Up to 10 amenity tags from the search card |
| `image_url` | string | First hero image URL |
| `reviews` | list\[object] | Top guest reviews (only when `includeReviews=true`) |
| `direction` | string | Rate direction vs last scrape: `up`, `down`, `flat`, `new` |
| `delta_pct_7d` | float | % price change vs scrape ~7 days ago (null on first run) |
| `delta_pct_30d` | float | % price change vs scrape ~30 days ago (null on first run) |
| `is_volatile` | bool | True when 7-day stddev/mean exceeds 12% |
| `rate_previous` | float | Most recent prior price from rate history |
| `rate_min_30d` | float | Minimum observed price in the last 30 days |
| `rate_max_30d` | float | Maximum observed price in the last 30 days |
| `rate_avg_30d` | float | Average observed price in the last 30 days |
| `history_sample_count` | int | Number of historical snapshots stored for this hotel |
| `description` | string | Hotel description from PDP (`fetchDetails=true` only) |
| `amenities_full` | list\[string] | Full amenity list from PDP (up to 40 items) |
| `checkin_time` | string | Check-in window (e.g. "From 15:00") — PDP only |
| `checkout_time` | string | Check-out time (e.g. "Until 11:00") — PDP only |
| `house_rules` | list\[string] | Bullet list of hotel policies from PDP (up to 20 items) |
| `languages_spoken` | list\[string] | Languages spoken at reception — PDP only |
| `details_scraped` | bool | True when PDP enrichment was applied to this row |
| `scraped_at` | string | ISO-8601 UTC timestamp of the scrape |

#### Rate-alert items (when `alertMode != "off"`)

A dedicated `hotel_rate_alert` item is pushed for each hotel that matches the threshold criterion. One alert = one `rate-alert-triggered` event at $0.02:

```json
{
    "type": "hotel_rate_alert",
    "alert_mode": "price_drop",
    "alert_threshold_pct": 10,
    "alert_reasons": ["price_drop:18.3%_below_30d_avg", "new_low:at_or_below_30d_min=95.0"],
    "hotel_name": "YOTEL Amsterdam",
    "hotel_url": "https://www.booking.com/hotel/nl/yotel-amsterdam.html",
    "currency": "EUR",
    "rate_now": 95.0,
    "rate_previous": 140.0,
    "rate_avg_30d": 116.3,
    "rate_min_30d": 95.0,
    "rate_max_30d": 150.0,
    "delta_pct_7d": -32.1,
    "delta_pct_30d": -18.3,
    "direction": "down",
    "scraped_at": "2026-04-22T12:00:00+00:00"
}
```

Hotels with zero history (first run for a new slot) never fire alerts — seeding runs are silent. Combine with Apify's scheduler and webhooks to receive email or Slack notifications only when a competitor genuinely moves price.

#### Rate-movement digest

Once per run, a rollup item highlights the biggest 7-day movers and any hotels with volatile pricing:

```json
{
    "type": "rate_movement_digest",
    "destination": "Amsterdam",
    "check_in": "2026-06-01",
    "check_out": "2026-06-03",
    "hotels_with_history": 6,
    "biggest_hikes_7d": [
        {"name": "Hotel V Nesplein", "url": "...", "rate_now": 189.0, "rate_previous": 160.0, "delta_pct_7d": 18.1, "currency": "EUR"}
    ],
    "biggest_drops_7d": [
        {"name": "YOTEL Amsterdam", "url": "...", "rate_now": 112.0, "rate_previous": 140.0, "delta_pct_7d": -20.0, "currency": "EUR"}
    ],
    "volatile_hotels": [
        {"name": "Zoku Amsterdam", "url": "...", "rate_now": 210.0, "rate_avg_30d": 180.0, "rate_min_30d": 120.0, "rate_max_30d": 260.0, "currency": "EUR"}
    ],
    "generated_at": "2026-04-10T12:00:00+00:00"
}
```

#### AI analysis output

When AI analysis is enabled, an additional dataset item is pushed containing a comprehensive market report:

```json
{
    "type": "ai_analysis_report",
    "provider": "openrouter",
    "model": "google/gemini-2.0-flash-001",
    "analysis": "### Market Overview\n\nAnalyzing 30 properties in Amsterdam...",
    "hotels_analyzed": 30,
    "generated_at": "2026-04-10T12:00:00+00:00"
}
```

The AI analysis covers market overview, pricing distribution, quality segmentation, competitive positioning, top value picks, strategic recommendations, and — when rate-history is enabled — a Rate Dynamics section with booking-window advice derived from 30-day rolling price data.

### Quick Start

The simplest run — just provide a destination:

```json
{
    "destination": "Amsterdam",
    "maxListings": 10
}
```

This scrapes the top 10 hotels in Amsterdam sorted by popularity. Set a residential proxy in `proxyConfiguration` for reliable results.

#### Rate-surveillance setup (recommended for revenue managers)

Schedule the actor daily against the same destination and dates to build 30-day rolling history, then layer on alerts:

```json
{
    "destination": "Amsterdam",
    "checkIn": "2026-06-01",
    "checkOut": "2026-06-04",
    "maxListings": 50,
    "enableRateHistory": true,
    "alertMode": "price_drop",
    "alertThresholdPct": 10,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": ["RESIDENTIAL"]
    }
}
```

On the first run every hotel shows `direction: "new"` — it is seeding the history. From the second run onward the deltas and alerts populate.

#### Full revenue-management stack

Combine PDP enrichment, rate-alerts, and AI analysis for the maximum signal-per-run ratio:

```json
{
    "destination": "Amsterdam",
    "checkIn": "2026-06-01",
    "checkOut": "2026-06-04",
    "maxListings": 50,
    "enableRateHistory": true,
    "alertMode": "all",
    "alertThresholdPct": 10,
    "fetchDetails": true,
    "enableAiAnalysis": true,
    "llmProvider": "openrouter",
    "openrouterApiKey": "sk-or-...",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": ["RESIDENTIAL"]
    }
}
```

#### Tips for best results

- Start with 10–15 hotels to verify output before scaling to larger runs
- Sort by `rating` when running competitive analysis to surface the strongest competitors first
- Use AI analysis with 15+ hotels — the market report is significantly more insightful with a larger dataset
- Always provide both `checkIn` and `checkOut` dates for accurate pricing
- Keep `destination`, `checkIn`, and `checkOut` identical across runs for consistent rate-history keys
- The actor uses Playwright (Chromium) to render Booking.com's JavaScript-heavy pages and handles lazy-loaded content by scrolling automatically

### Troubleshooting

#### Actor returns 403 or scrapes 0 hotels despite a valid destination

Booking.com blocks datacenter IP addresses aggressively. Without a residential proxy, almost every request ends in a 403 or an empty result set. Set `proxyConfiguration.useApifyProxy: true` with `apifyProxyGroups: ["RESIDENTIAL"]`. Datacenter proxies are insufficient — residential is a hard requirement.

#### Prices differ from what I see on Booking.com

Booking.com personalizes prices based on login status, Genius loyalty tier, country of origin, and device type. The actor fetches as an anonymous visitor. Use a RESIDENTIAL proxy in the target country to get representative local pricing — a US residential proxy returns USD prices, a UK residential proxy returns GBP. Genius discounts (up to 20%) are not accessible without an authenticated session.

#### Date format rejected or no results returned for specific dates

Dates must be in `YYYY-MM-DD` format (e.g. `2026-06-01`). Formats like `06/01/2026` or `June 1` are silently ignored and the actor falls back to a default near-future date. Verify that the check-out date is at least one day after check-in — same-day values produce no results.

#### No hotels returned even though the destination exists

Booking.com search can return empty results if your dates are too far in the future (beyond ~18 months), the destination string is ambiguous, or there are no available properties meeting Booking's availability criteria for those dates. Try a nearby major city to confirm the actor is working, then narrow to your target. Omitting dates is also a common cause — always provide both `checkIn` and `checkOut`.

#### Actor slows down or times out on large runs with reviews enabled

Review scraping requires visiting each hotel's individual detail page, which multiplies the number of HTTP requests by `maxListings`. Start with `maxReviewsPerHotel: 5` and `maxListings: 20` when testing. For large runs, disable reviews on the first pass, then re-run targeted hotel URLs with reviews enabled.

#### Rate history shows no deltas on the second run

Cross-run history requires the same hotel IDs to appear in both runs. If you change the destination, dates, or sort order between runs, a new KV-store slot is created and no prior baseline exists. Keep `destination`, `checkIn`, and `checkOut` identical across runs. The first run for any slot produces `direction: "new"` — deltas appear from the second run onward. History is capped at 90 snapshots per hotel, giving roughly 3 months of lookback on a daily schedule.

#### AI analysis fails with "API key missing"

Set `openrouterApiKey` (or your chosen provider's key field) in the actor input, or set the corresponding environment variable (`OPENROUTER_API_KEY`, `ANTHROPIC_API_KEY`, `GOOGLE_API_KEY`, or `OPENAI_API_KEY`). AI analysis is optional — set `enableAiAnalysis: false` to run without it. The actor will skip AI silently if no key is provided and will not abort the scrape.

#### PDP enrichment returns null for some fields

`property_type`, `amenities`, `breakfast_included`, and `free_cancellation` are only populated when Booking exposes them in the search card DOM. In some A/B variants these fields appear only on the hotel detail page. When missing they are returned as `null` / `[]` rather than falsified. Enable `fetchDetails=true` for the most complete amenity data.

#### Rate alerts not firing even though prices have changed

Alerts require at least one prior snapshot in history, so the very first run for a new `(destination, check-in, check-out)` slot is always silent — it is seeding the data. Check that `alertMode` is not `off` and that `alertThresholdPct` is set appropriately. The `rate_movement_digest` item on each run shows which hotels have history and which do not.

### Legal and Compliance

This actor scrapes publicly available data. By using this actor, you agree to the following:

- **Your responsibility**: You are solely responsible for ensuring your use complies with all applicable laws, regulations, and the target website's terms of service. This includes but is not limited to GDPR (EU), CCPA (California), and other data protection laws in your jurisdiction.
- **No legal advice**: This actor does not constitute legal advice. Consult a qualified attorney if you have questions about the legality of your specific use case.
- **Intended use**: This actor is designed for legitimate business purposes such as market research, competitive analysis, and academic research using publicly accessible data.
- **Data handling**: You are responsible for how you store, process, and share any data collected. Ensure you have a lawful basis for processing any personal data under applicable privacy laws.
- **Rate limiting**: This actor implements polite crawling practices including request delays and retry backoff to minimize impact on target servers.
- **No warranty**: This actor is provided "as is" without warranty. Data accuracy depends on the target website's content and structure.
- **Booking.com terms**: Booking.com's terms of service restrict automated data collection. Use residential proxies and moderate request volumes to reduce detection risk and server load.
- **Personal data notice**: Hotel review data may contain reviewer names which constitute personal data under GDPR. Ensure you have a lawful basis for processing. Do not use reviewer data for direct marketing or unsolicited contact.
- **Related actors**: [Zillow Scraper](https://apify.com/harvestlab/zillow-scraper) for US real estate · [Funda Scraper](https://apify.com/harvestlab/funda-scraper) for Dutch real estate · [Review Analyzer](https://apify.com/harvestlab/review-analyzer) for AI sentiment · [Trustpilot Scraper](https://apify.com/harvestlab/trustpilot-scraper) for brand reputation · [News Monitor](https://apify.com/harvestlab/news-monitor) for travel industry news

# Actor input Schema

## `searchUrl` (type: `string`):

Booking.com search results URL. Paste a full URL from Booking.com search results page. If provided, destination/checkIn/checkOut are ignored.

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

City or area name to search (e.g. 'Amsterdam', 'Paris 6th Arrondissement'). Used only if searchUrl is not provided.

## `searchQuery` (type: `string`):

CLI alias for destination. Hidden from Console form.

## `query` (type: `string`):

CLI alias for destination. Hidden from Console form.

## `city` (type: `string`):

CLI alias for destination. Hidden from Console form.

## `location` (type: `string`):

CLI alias for destination. Hidden from Console form.

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

Check-in date in YYYY-MM-DD format (e.g. '2026-05-15').

## `checkInDate` (type: `string`):

CLI alias for checkIn. Hidden from Console form.

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

Check-out date in YYYY-MM-DD format (e.g. '2026-05-18').

## `checkOutDate` (type: `string`):

CLI alias for checkOut. Hidden from Console form.

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

3-letter ISO 4217 currency code for hotel prices. Default is EUR. Booking.com supports EUR, USD, GBP, CHF, PLN, CZK, SEK, NOK, DKK, JPY, CNY, RUB, AUD, CAD, and more.

## `maxListings` (type: `integer`):

Maximum number of hotel listings to scrape. Booking.com shows ~25 per page. Start small (10) to test.

## `maxHotels` (type: `integer`):

CLI alias for maxListings. Hidden from Console form.

## `maxResults` (type: `integer`):

CLI alias for maxListings. Hidden from Console form.

## `maxItems` (type: `integer`):

CLI alias for maxListings. Hidden from Console form.

## `includeReviews` (type: `boolean`):

Visit each hotel page to scrape top guest reviews with author, country, rating, and text. Adds ~3-5s per hotel (25 hotels = +2 min). Disable for faster price-only runs; enable for sentiment analysis or AI review summarization.

## `maxReviewsPerHotel` (type: `integer`):

Maximum number of reviews to scrape per hotel (only used if includeReviews is true).

## `sortBy` (type: `string`):

Hotel result ordering. 'Popularity' = Booking.com's default ranking (mix of clicks, bookings, commission). 'Price low/high' = total stay price in the selected currency. 'Guest Rating' = highest review score first (min 10+ reviews). 'Stars high' = official hotel star rating descending.

## `enableRateHistory` (type: `boolean`):

Persist each run's prices to a named key-value store keyed by (destination, check-in, check-out) and emit rate-trend fields on every hotel: delta\_pct\_7d, delta\_pct\_30d, direction (up/down/flat/new), is\_volatile, rate\_min\_30d, rate\_max\_30d, rate\_avg\_30d. A rate-movement digest item is also pushed per run highlighting the biggest 7-day hikes/drops and volatile hotels. Free feature — no extra HTTP, no extra charge. Disable for pure one-shot snapshot runs.

## `alertMode` (type: `string`):

Emit a dedicated `hotel_rate_alert` item (charged per alert at $0.02) for each hotel that matches the chosen criterion. Requires rate-history. `price_drop` = current price ≤ rate\_avg\_30d × (1 - threshold%). `volatile` = is\_volatile true AND delta\_pct\_7d beyond threshold. `new_low` = current price matches or is below the prior rate\_min\_30d. `all` = any of the above. `off` disables the alert tier (default).

## `alertThresholdPct` (type: `integer`):

Percentage threshold for the alert mode. For `price_drop`, current price must be at least this much below `rate_avg_30d` (default 10 = 10%). For `volatile`, `|delta_pct_7d|` must exceed this value. Ignored for `new_low` and when `alertMode=off`.

## `fetchDetails` (type: `boolean`):

Visit each hotel's product detail page (PDP) to enrich the row with `description`, full `amenities_full[]` list, `property_type`, `checkin_time`, `checkout_time`, `house_rules[]`, and `languages_spoken[]`. Adds ~3-5s per hotel. Charged separately via the `detail-scraped` event ($0.01 per PDP). Disable for faster listing-only runs.

## `includeDetails` (type: `boolean`):

CLI alias for fetchDetails. Hidden from Console form.

## `enableAiAnalysis` (type: `boolean`):

Generate AI market analysis: price trends, competitive positioning, guest satisfaction patterns, and investment ROI estimates. When rate-history tracking is enabled, the AI report also covers rate dynamics (7/30-day price deltas, volatility) for actionable booking-window advice.

## `llmProvider` (type: `string`):

Which LLM provider to use for AI analysis.

## `llmModel` (type: `string`):

Specific model to use. Leave empty for the provider default (google/gemini-2.0-flash-001 for OpenRouter, claude-sonnet-4-20250514 for Anthropic, gemini-2.0-flash for Google AI, gpt-4o-mini for OpenAI, llama3.1 for Ollama).

## `openrouterApiKey` (type: `string`):

API key for OpenRouter (required if LLM provider is OpenRouter and AI analysis is enabled).

## `anthropicApiKey` (type: `string`):

API key for Anthropic (required if LLM provider is Anthropic and AI analysis is enabled).

## `googleApiKey` (type: `string`):

API key for Google AI (Gemini). Get one at https://aistudio.google.com/app/apikey

## `openaiApiKey` (type: `string`):

API key from platform.openai.com (required if using OpenAI provider)

## `ollamaBaseUrl` (type: `string`):

Base URL for Ollama API. Default: http://localhost:11434

## `proxyConfiguration` (type: `object`):

Proxy settings for accessing Booking.com. Residential proxies recommended.

## Actor input object example

```json
{
  "searchUrl": "https://www.booking.com/searchresults.html?ss=Amsterdam",
  "destination": "Amsterdam",
  "currency": "EUR",
  "maxListings": 25,
  "includeReviews": false,
  "maxReviewsPerHotel": 10,
  "sortBy": "popularity",
  "enableRateHistory": true,
  "alertMode": "off",
  "alertThresholdPct": 10,
  "fetchDetails": false,
  "enableAiAnalysis": false,
  "llmProvider": "openrouter",
  "ollamaBaseUrl": "http://localhost:11434",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# 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 = {
    "searchUrl": "https://www.booking.com/searchresults.html?ss=Amsterdam",
    "destination": "Amsterdam",
    "ollamaBaseUrl": "http://localhost:11434",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("harvestlab/booking-scraper").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 = {
    "searchUrl": "https://www.booking.com/searchresults.html?ss=Amsterdam",
    "destination": "Amsterdam",
    "ollamaBaseUrl": "http://localhost:11434",
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("harvestlab/booking-scraper").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 '{
  "searchUrl": "https://www.booking.com/searchresults.html?ss=Amsterdam",
  "destination": "Amsterdam",
  "ollamaBaseUrl": "http://localhost:11434",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call harvestlab/booking-scraper --silent --output-dataset

```

## MCP server setup

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

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Booking.com MCP Tracker — Hotel Drop Alerts + AI",
        "description": "RAG-ready Booking.com hotel feed for travel-research agents — prices, reviews, amenities, PDP enrichment. Cross-run rate-history tracks 7/30-day deltas with price-drop, volatile, new-low alerts. 99%+ run success. AI via 5 LLM providers. Built for revenue managers and travel agencies. x402-ready.",
        "version": "1.8",
        "x-build-id": "eXyH6nIG19LD5OkvF"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/harvestlab~booking-scraper/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-harvestlab-booking-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/harvestlab~booking-scraper/runs": {
            "post": {
                "operationId": "runs-sync-harvestlab-booking-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/harvestlab~booking-scraper/run-sync": {
            "post": {
                "operationId": "run-sync-harvestlab-booking-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": {
                    "searchUrl": {
                        "title": "Search URL",
                        "type": "string",
                        "description": "Booking.com search results URL. Paste a full URL from Booking.com search results page. If provided, destination/checkIn/checkOut are ignored."
                    },
                    "destination": {
                        "title": "Destination",
                        "type": "string",
                        "description": "City or area name to search (e.g. 'Amsterdam', 'Paris 6th Arrondissement'). Used only if searchUrl is not provided."
                    },
                    "searchQuery": {
                        "title": "Destination (CLI alias)",
                        "type": "string",
                        "description": "CLI alias for destination. Hidden from Console form."
                    },
                    "query": {
                        "title": "Destination (CLI alias)",
                        "type": "string",
                        "description": "CLI alias for destination. Hidden from Console form."
                    },
                    "city": {
                        "title": "Destination (CLI alias)",
                        "type": "string",
                        "description": "CLI alias for destination. Hidden from Console form."
                    },
                    "location": {
                        "title": "Destination (CLI alias)",
                        "type": "string",
                        "description": "CLI alias for destination. Hidden from Console form."
                    },
                    "checkIn": {
                        "title": "Check-in Date",
                        "type": "string",
                        "description": "Check-in date in YYYY-MM-DD format (e.g. '2026-05-15')."
                    },
                    "checkInDate": {
                        "title": "Check-in Date (CLI alias)",
                        "type": "string",
                        "description": "CLI alias for checkIn. Hidden from Console form."
                    },
                    "checkOut": {
                        "title": "Check-out Date",
                        "type": "string",
                        "description": "Check-out date in YYYY-MM-DD format (e.g. '2026-05-18')."
                    },
                    "checkOutDate": {
                        "title": "Check-out Date (CLI alias)",
                        "type": "string",
                        "description": "CLI alias for checkOut. Hidden from Console form."
                    },
                    "currency": {
                        "title": "Currency",
                        "enum": [
                            "EUR",
                            "USD",
                            "GBP",
                            "CHF",
                            "PLN",
                            "CZK",
                            "SEK",
                            "NOK",
                            "DKK",
                            "JPY",
                            "CNY",
                            "RUB",
                            "AUD",
                            "CAD",
                            "INR",
                            "BRL"
                        ],
                        "type": "string",
                        "description": "3-letter ISO 4217 currency code for hotel prices. Default is EUR. Booking.com supports EUR, USD, GBP, CHF, PLN, CZK, SEK, NOK, DKK, JPY, CNY, RUB, AUD, CAD, and more.",
                        "default": "EUR"
                    },
                    "maxListings": {
                        "title": "Max Listings",
                        "minimum": 1,
                        "maximum": 100,
                        "type": "integer",
                        "description": "Maximum number of hotel listings to scrape. Booking.com shows ~25 per page. Start small (10) to test.",
                        "default": 25
                    },
                    "maxHotels": {
                        "title": "Max Listings (CLI alias)",
                        "type": "integer",
                        "description": "CLI alias for maxListings. Hidden from Console form."
                    },
                    "maxResults": {
                        "title": "Max Listings (CLI alias)",
                        "type": "integer",
                        "description": "CLI alias for maxListings. Hidden from Console form."
                    },
                    "maxItems": {
                        "title": "Max Items (CLI alias)",
                        "minimum": 1,
                        "maximum": 100,
                        "type": "integer",
                        "description": "CLI alias for maxListings. Hidden from Console form."
                    },
                    "includeReviews": {
                        "title": "Include Reviews",
                        "type": "boolean",
                        "description": "Visit each hotel page to scrape top guest reviews with author, country, rating, and text. Adds ~3-5s per hotel (25 hotels = +2 min). Disable for faster price-only runs; enable for sentiment analysis or AI review summarization.",
                        "default": false
                    },
                    "maxReviewsPerHotel": {
                        "title": "Max Reviews Per Hotel",
                        "minimum": 1,
                        "maximum": 50,
                        "type": "integer",
                        "description": "Maximum number of reviews to scrape per hotel (only used if includeReviews is true).",
                        "default": 10
                    },
                    "sortBy": {
                        "title": "Sort By",
                        "enum": [
                            "popularity",
                            "price_low",
                            "price_high",
                            "rating",
                            "stars_high"
                        ],
                        "type": "string",
                        "description": "Hotel result ordering. 'Popularity' = Booking.com's default ranking (mix of clicks, bookings, commission). 'Price low/high' = total stay price in the selected currency. 'Guest Rating' = highest review score first (min 10+ reviews). 'Stars high' = official hotel star rating descending.",
                        "default": "popularity"
                    },
                    "enableRateHistory": {
                        "title": "Enable Rate-History Tracking",
                        "type": "boolean",
                        "description": "Persist each run's prices to a named key-value store keyed by (destination, check-in, check-out) and emit rate-trend fields on every hotel: delta_pct_7d, delta_pct_30d, direction (up/down/flat/new), is_volatile, rate_min_30d, rate_max_30d, rate_avg_30d. A rate-movement digest item is also pushed per run highlighting the biggest 7-day hikes/drops and volatile hotels. Free feature — no extra HTTP, no extra charge. Disable for pure one-shot snapshot runs.",
                        "default": true
                    },
                    "alertMode": {
                        "title": "Rate-Alert Mode",
                        "enum": [
                            "off",
                            "price_drop",
                            "volatile",
                            "new_low",
                            "all"
                        ],
                        "type": "string",
                        "description": "Emit a dedicated `hotel_rate_alert` item (charged per alert at $0.02) for each hotel that matches the chosen criterion. Requires rate-history. `price_drop` = current price ≤ rate_avg_30d × (1 - threshold%). `volatile` = is_volatile true AND delta_pct_7d beyond threshold. `new_low` = current price matches or is below the prior rate_min_30d. `all` = any of the above. `off` disables the alert tier (default).",
                        "default": "off"
                    },
                    "alertThresholdPct": {
                        "title": "Alert Threshold (%)",
                        "minimum": 1,
                        "maximum": 80,
                        "type": "integer",
                        "description": "Percentage threshold for the alert mode. For `price_drop`, current price must be at least this much below `rate_avg_30d` (default 10 = 10%). For `volatile`, `|delta_pct_7d|` must exceed this value. Ignored for `new_low` and when `alertMode=off`.",
                        "default": 10
                    },
                    "fetchDetails": {
                        "title": "Fetch Hotel Details (PDP)",
                        "type": "boolean",
                        "description": "Visit each hotel's product detail page (PDP) to enrich the row with `description`, full `amenities_full[]` list, `property_type`, `checkin_time`, `checkout_time`, `house_rules[]`, and `languages_spoken[]`. Adds ~3-5s per hotel. Charged separately via the `detail-scraped` event ($0.01 per PDP). Disable for faster listing-only runs.",
                        "default": false
                    },
                    "includeDetails": {
                        "title": "Fetch Hotel Details (CLI alias)",
                        "type": "boolean",
                        "description": "CLI alias for fetchDetails. Hidden from Console form."
                    },
                    "enableAiAnalysis": {
                        "title": "Enable AI Analysis",
                        "type": "boolean",
                        "description": "Generate AI market analysis: price trends, competitive positioning, guest satisfaction patterns, and investment ROI estimates. When rate-history tracking is enabled, the AI report also covers rate dynamics (7/30-day price deltas, volatility) for actionable booking-window advice.",
                        "default": false
                    },
                    "llmProvider": {
                        "title": "LLM Provider",
                        "enum": [
                            "openrouter",
                            "anthropic",
                            "google",
                            "openai",
                            "ollama"
                        ],
                        "type": "string",
                        "description": "Which LLM provider to use for AI analysis.",
                        "default": "openrouter"
                    },
                    "llmModel": {
                        "title": "LLM Model",
                        "type": "string",
                        "description": "Specific model to use. Leave empty for the provider default (google/gemini-2.0-flash-001 for OpenRouter, claude-sonnet-4-20250514 for Anthropic, gemini-2.0-flash for Google AI, gpt-4o-mini for OpenAI, llama3.1 for Ollama)."
                    },
                    "openrouterApiKey": {
                        "title": "OpenRouter API Key",
                        "type": "string",
                        "description": "API key for OpenRouter (required if LLM provider is OpenRouter and AI analysis is enabled)."
                    },
                    "anthropicApiKey": {
                        "title": "Anthropic API Key",
                        "type": "string",
                        "description": "API key for Anthropic (required if LLM provider is Anthropic and AI analysis is enabled)."
                    },
                    "googleApiKey": {
                        "title": "Google AI API Key",
                        "type": "string",
                        "description": "API key for Google AI (Gemini). Get one at https://aistudio.google.com/app/apikey"
                    },
                    "openaiApiKey": {
                        "title": "OpenAI API Key",
                        "type": "string",
                        "description": "API key from platform.openai.com (required if using OpenAI provider)"
                    },
                    "ollamaBaseUrl": {
                        "title": "Ollama Base URL",
                        "type": "string",
                        "description": "Base URL for Ollama API. Default: http://localhost:11434"
                    },
                    "proxyConfiguration": {
                        "title": "Proxy Configuration",
                        "type": "object",
                        "description": "Proxy settings for accessing Booking.com. Residential proxies recommended."
                    }
                }
            },
            "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
                                    }
                                }
                            },
                            "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
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
