# Expedia Hotel Price Scraper & Monitor - Ratings & Price Drops (`neverempty/expedia-hotel-price-monitor`) Actor

For hotel revenue managers, travel agencies and rate shoppers: Expedia hotel prices for any city and dates, with total price incl. taxes, nightly rate, previous price, rooms left, rating, reviews and deals. Monitoring returns only price drops, rises and new hotels. Export as CSV or Excel.

- **URL**: https://apify.com/neverempty/expedia-hotel-price-monitor.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** Travel, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.50 / 1,000 hotel returneds

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

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

## What's an Apify Actor?

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

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

## How to integrate an Actor?

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

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

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

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

# README

## Expedia Hotel Price Monitor - Prices, Ratings & Price Drops

**An Expedia hotel price scraper and rate monitor for any city and dates.** For hotel revenue managers, travel agencies and anyone tracking rates: every hotel Expedia lists for a city, district or landmark on your dates, with the total price including taxes and fees, the nightly rate before taxes, the previous price Expedia shows next to it, the discount, how many rooms are left at that price, guest rating, review count, neighborhood, free cancellation, VIP Access and sponsored flags. In a check on 2026-10-09, 16 of 16 hotel cards on the Expedia page opened in Chrome showed the same name, total price, rating and review count as the rows. Put it on a schedule in monitoring mode and you get back only the hotels whose total price dropped or rose, or that are new to your watch - no diffing on your side.

No Expedia account or API key needed. Export as JSON, CSV or Excel, or push price changes to Slack, email or a sheet with an Apify integration.

### What you get

- **One row per hotel** for each destination, in Expedia's order: name, neighborhood, total price with taxes and fees (`totalPrice`), nightly rate before taxes (`pricePerNightBeforeTaxes`), the previous price Expedia shows struck through (`previousPriceShown`) and the discount badge, rooms left at this price, guest rating out of 10 and its label, review count, free cancellation, VIP Access, Member Price, the amenities shown on the card, and the hotel's Expedia link for your dates.
- **Your dates and guests, checked.** Every row repeats the dates and adults Expedia actually priced and says whether they match what you asked for (`stayAsRequested`). The order Expedia applied is in `sortApplied`.
- **Dates that move with the schedule.** `+30` means 30 days after the day the run starts, so a daily schedule always watches the same distance ahead and never falls into the past.
- **Monitoring mode** (`monitoringMode`): only hotels whose total price moved by at least `minPriceChangePercent` since the row last returned for them, or that are new to this watch, with the previous total in the same row.
- **No failed runs for "nothing found".** A destination with no hotels, a place Expedia reads differently, a refused request or a spending limit each come back as a free row that says why.

### Input

| Field | What it does |
|---|---|
| `destinations` | Places to search, one per entry, as you would type them into Expedia: `Paris`, `Paris, France`, `Times Square, New York`. Each row carries `resolvedDestination`, the place Expedia actually searched. Required. |
| `checkInDate` | `+N` for N days after the run date (UTC), or a fixed `YYYY-MM-DD`. Prefilled with `+30`. Empty = 30 days from today. Dates before yesterday (UTC) and arrivals more than 330 days ahead are refused with a free row before anything is requested. |
| `checkOutDate` | `+N` or `YYYY-MM-DD`. Prefilled with `+31`. Empty = 1 night after check-in. At most 28 nights. |
| `adults` | Guests in one room, 1-14. Empty = 2. |
| `sortBy` | `recommended` (empty = this, Expedia's default), `lowest-price`, `highest-price`, `guest-rating`, `star-rating` or `distance` - the site's sort menu. |
| `maxHotels` | Hotels to return per destination, 1-1000. Empty = 100. The first page has about 95; more are read 100 at a time. In monitoring mode this is how many hotels are watched. |
| `includeSponsored` | Keep hotels Expedia marks as Ad (flagged `sponsored: true`). Empty or on = keep; off = skip them, not charged. |
| `monitoringMode` | Return only hotels that are new to this watch or whose total price moved. The first run returns every hotel as new. |
| `minPriceChangePercent` | Monitoring only. Smallest move that counts, in percent of the total last returned. Empty = 2. |
| `resetMonitoringState` | Forget what this search remembered; this monitoring run returns every hotel as new. |

### Output

```json
{
  "status": "ok",
  "searchQuery": "Paris",
  "resolvedDestination": "Paris, France",
  "position": 3,
  "hotelId": "115063",
  "hotelName": "Hotel Villa Boheme",
  "neighborhood": "Paris",
  "guestRating": 8.8,
  "guestRatingText": "Excellent",
  "reviewCount": 888,
  "reviewsAreExternal": false,
  "totalPrice": 101,
  "totalPriceText": "$101 total",
  "priceIncludesTaxesAndFees": true,
  "pricePerNightBeforeTaxes": 81,
  "previousPriceShown": 111,
  "discountText": "$10 off",
  "currency": "USD",
  "roomsLeftAtThisPrice": 3,
  "freeCancellation": false,
  "vipAccess": false,
  "memberPriceAvailable": false,
  "sponsored": false,
  "amenitiesShown": ["Bar and lounge", "Buffet avail", "AC"],
  "checkInDate": "2026-11-08",
  "checkOutDate": "2026-11-09",
  "nights": 1,
  "adults": 2,
  "stayAsRequested": true,
  "sortBy": "recommended",
  "sortApplied": "RECOMMENDED",
  "matchedProperties": 508,
  "expediaUrl": "https://www.expedia.com/Paris-Hotels-Hotel-Villa-Boheme.h115063.Hotel-Information?chkin=2026-11-08&chkout=2026-11-09&rm1=a2",
  "scrapedAt": "2026-10-09T06:37:01.238Z"
}
```

A value Expedia does not show for a hotel is `null` (for example `previousPriceShown` when there is no struck-through price, or `roomsLeftAtThisPrice` when Expedia does not say).

In monitoring mode each row also has `change` (`new`, `price-down`, `price-up` or `price-shown`), `previousTotalPrice`, `previousTotalPriceText` and `previousReturnedAt`.

#### Free rows (never charged)

| `status` | When |
|---|---|
| `no-results` | Expedia answered "0 Properties" / "We don't have any available properties for your search". |
| `destination-resolved-elsewhere` | Expedia searched a place that shares no word with what you wrote (the hotel rows for it still come back). |
| `blocked` | Expedia kept showing its "Bot or Not?" check page. The Actor does not answer it; it says which destinations were not searched. |
| `unreadable` | A page could not be read. Nothing is guessed. |
| `bad-input` | The input could not be used (for example a date in the past). Nothing was requested. |
| `no-change` | Monitoring: nothing new and no price moved since the last returned rows. |
| `budget-reached` | The run reached the maximum total charge you set; the rest was not returned and not charged. |

### Monitoring on a schedule

1. Run once with `monitoringMode` on: every hotel comes back as `new` and becomes the starting point.
2. Schedule the same input (for example daily). Each run returns only hotels whose total price moved by `minPriceChangePercent` or more since the row last returned for them, and hotels that are new to the list.
3. A run with no change returns one free `no-change` row; only a small watch check fee is charged for that run.

Use a fixed order such as `lowest-price` for a watch. Expedia's `recommended` order reshuffles between reads, so hotels moving into your top `maxHotels` come back as `new` (in a 2026-10-09 check, two runs a minute apart with `recommended` returned 1 and 2 new hotels out of 30; with `lowest-price` the second run returned no change).

The memory is kept per destination, check-in and check-out as you wrote them (`+30` is one watch that moves with the calendar), adults and sort order. Hotels not returned because of a spending limit are not remembered, so the next run returns them.

### How it works and what it costs

Unofficial and not affiliated with Expedia. The Actor opens the Expedia search page the same way a visitor does and reads the hotel list the page itself loads. If Expedia keeps showing a check page, the Actor never answers it and returns a free `blocked` row that says which destinations were not searched. A destination with 100 hotels usually takes 20-40 seconds.

### Pricing

You pay per hotel row returned. Free rows are never charged. In monitoring mode, a run that finds no change is charged one watch check instead of rows.

### FAQ

**Is the price per night or for the whole stay?** `totalPrice` is the total for the whole stay with taxes and fees, as the big price on the Expedia card. `pricePerNightBeforeTaxes` is the "nightly" figure above it.

**Can I get other currencies?** Not yet: expedia.com prices in US dollars. Tell us in the Issues tab if you need another Expedia site.

**Why is there no star rating?** Expedia's search list does not show it. The `star-rating` sort is passed to Expedia as its own sort option (the rows show `sortApplied: PROPERTY_CLASS`).

### Thanks for using this Actor

We build these tools for people who run them every day, and we improve them from what users tell us.

- **Missing a field, or need another filter?** Tell us in the **Issues** tab. If the data is there, we add it.
- **Found a bug or a wrong value?** Post the run ID in the **Issues** tab. Wrong data is the thing we fix first.

If this Actor saved you time, a short review helps other people find it.

# Actor input Schema

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

Where to look, written as you would type it into the Expedia search box: a city (Paris), a city with its country (Paris, France), a district or a landmark (Times Square, New York). Each entry is one search. Every row says which place Expedia actually searched (resolvedDestination). Required.

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

+N for N days after the run date in UTC (+30 = 30 days from the day the run starts, so a schedule always looks the same distance ahead), or a fixed date YYYY-MM-DD. Dates before yesterday (UTC) and arrivals more than 330 days ahead are refused with a free row before anything is requested. Empty = 30 days from today.

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

+N days from the run date or YYYY-MM-DD. Must be after check-in; at most 28 nights. Empty = 1 night after check-in.

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

Guests in one room that the price is for. Empty = 2. Every row repeats the dates and guests Expedia priced (stayAsRequested says whether they match what you asked for).

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

Order Expedia lists hotels in, as the sort menu on the site. Empty = recommended. Each row repeats the order Expedia applied (sortApplied).

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

How many hotels to return for each destination, in Expedia's order. The first page has about 95; more are read 100 at a time. Empty = 100. In monitoring mode this is how many hotels are watched.

## `includeSponsored` (type: `boolean`):

Keep hotels Expedia marks as Ad. They are flagged with sponsored = true. Empty or on = keep them; off = skip them (they are not charged).

## `monitoringMode` (type: `boolean`):

Return only hotels that are new to this watch or whose total price moved since the row last returned for them (price-down, price-up, price-shown). Unchanged hotels are not returned. The first run returns every hotel as new. When a run finds no change, one small watch check fee is charged instead of rows.

## `minPriceChangePercent` (type: `number`):

Monitoring only. A total price counts as changed when it moved at least this much from the price last returned. Empty = 2. 0 reports every change of at least one dollar.

## `resetMonitoringState` (type: `boolean`):

Forget what this search remembered, so this monitoring run returns every hotel as new. Empty or off = keep the memory.

## Actor input object example

```json
{
  "destinations": [
    "Paris"
  ],
  "checkInDate": "+30",
  "checkOutDate": "+31",
  "maxHotels": 20
}
```

# Actor output Schema

## `results` (type: `string`):

One row per hotel in Expedia's search results for each destination: name, neighborhood, total price with taxes and fees, nightly rate before taxes, previous price shown, discount, rooms left at this price, guest rating and review count, free cancellation, VIP Access, sponsored flag, amenities shown, the dates and guests Expedia priced, and the hotel's Expedia link. In monitoring mode, only hotels new to the watch or whose total price moved, with the price last returned. Searches with no hotels, places Expedia read differently, refused requests and runs that hit their maximum charge come back as free rows that say why.

# 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": [
        "Paris"
    ],
    "checkInDate": "+30",
    "checkOutDate": "+31",
    "maxHotels": 20
};

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

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

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

```

## Python example

```python
from apify_client import ApifyClient

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

# Prepare the Actor input
run_input = {
    "destinations": ["Paris"],
    "checkInDate": "+30",
    "checkOutDate": "+31",
    "maxHotels": 20,
}

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

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

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

```

## CLI example

```bash
echo '{
  "destinations": [
    "Paris"
  ],
  "checkInDate": "+30",
  "checkOutDate": "+31",
  "maxHotels": 20
}' |
apify call neverempty/expedia-hotel-price-monitor --silent --output-dataset

```

## MCP server setup

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

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

## OpenAPI specification

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