# Expedia.com & Hotel.com \[$2.5💰] Reviews/Ratings/Sentiment A. (`memo23/expedia-scraper`) Actor

\[Only $2.5💰] Expedia + Hotels.com + Things-To-Do activities at $2.50/1k — only scraper covering all 3 brands. Deeplink auto-resolution, date filter, 10+ regional TLDs, bare property IDs. 25+ fields: sentiment, traveled-with, manager responses, category ratings. JSON or CSV. Pure HTTP.

- **URL**: https://apify.com/memo23/expedia-scraper.md
- **Developed by:** [Muhamed Didovic](https://apify.com/memo23) (community)
- **Categories:** Travel, Lead generation, Automation
- **Stats:** 140 total users, 69 monthly users, 99.4% runs succeeded, 6 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $2.50 / 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 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

## Expedia Reviews Scraper — Hotels + Activities + Hotels.com, one actor

![How Expedia Reviews Scraper works](https://raw.githubusercontent.com/muhamed-didovic/muhamed-didovic.github.io/main/assets/how-it-works-expedia.png)

Scrape every review from  **Expedia hotels**, **Expedia Things-To-Do activities** (tours, attractions, day trips), **and Hotels.com** — all in one run, with deeplink resolution, date filtering, and 10+ regional TLDs supported. **The only Apify scraper that covers all 3 brands plus activities** under a single PPE actor. **$2.50 per 1,000 reviews.**

### Why use this scraper

- **🏨 3 brands in one actor** — Expedia hotels, Expedia activities (Things-To-Do), and Hotels.com share the same code path and same input array. No need to run separate scrapers per brand.
- **🎢 Activities / Things-To-Do support** — tours, museum tickets, day trips, transfers, bus tours, classes — anything bookable on Expedia's activities section. Rows are tagged `provider: "expedia-activity"` for easy filtering. **No other Apify Expedia scraper does activities.**
- **🔗 Deeplink auto-resolution** — paste `https://expe.app.link/KjB1EuacRSb` or `https://expe.onelink.me/...` and the scraper follows the redirect chain to the real hotel page. Useful for ingesting URLs from mobile-app sharing, marketing campaigns, and social media.
- **📅 Date filtering with smart pagination** — pass `reviewsFrom: "2025-01-01"` and the scraper stops paginating when it reaches older reviews. You only pay for the reviews you want. Perfect for monitoring-mode delta runs.
- **🌍 10+ regional TLDs** — `.com`, `.co.uk`, `.com.br`, `.de`, `.fr`, `.com.sg`, `.com.au`, `.es`, `.it`, `.nl`, `.jp` — all resolve via the same scraper.
- **🆔 Bare property IDs accepted** — paste `600217` or `h600217` instead of full URLs. Auto-expanded to `https://www.expedia.com/Hotel.h{id}.Hotel-Information`.
- **🏷️ Sentiment + traveled-with + management response** on hotel rows — liked/disliked highlights, "Traveled with family / partner / business", and hotel manager replies when present.

### Overview

Expedia Group operates several review surfaces: hotels (under `expedia.com`, `expedia.co.uk`, `expedia.de`, …), Things-To-Do activities (`/things-to-do/{slug}.a{id}.activity-details`), and the Hotels.com sister brand. Every Apify Expedia scraper before this one targets just one of these. This actor unifies all three plus four input shortcuts (long URL, short URL, bare ID, deeplink) so a buyer can ingest URLs from any source — CRM exports, mobile-app shares, Excel pastes — without preprocessing.

Every dataset row represents one review. Rows are tagged `provider: "expedia" | "expedia-activity" | "hotels"` so downstream consumers can split them apart. Hotel rows additionally carry the hotel-level aggregate (overall rating, total reviews, **full 5-category breakdown** — Cleanliness, Staff & service, Amenities, Property conditions & facilities, Location — **Expedia's AI-generated guest-sentiment summary** when available, **the property's full photo gallery** — typically 100-300 image URLs per hotel, and a **rich descriptive block** — name, star class, address, coordinates, description, categorized amenities, and highlights) when `includeCategoryRatings: true`.

### Supported inputs

| Input type | Example URL | Provider tag | Output |
|---|---|---|---|
| **Expedia hotel (long slug)** | `https://www.expedia.co.uk/Kastoria-Hotels-Hotel-Anastassiou.h6136437.Hotel-Information` | `expedia` | One row per review + hotel aggregate |
| **Expedia hotel (short path)** | `https://www.expedia.com/h17369936.Hotel-Information` | `expedia` | Same |
| **Expedia hotel (minimal slug)** | `https://www.expedia.com/Hotel.h600217.Hotel-Information` | `expedia` | Same |
| **Bare property ID** | `600217` or `h600217` | `expedia` | Auto-expanded to `Hotel.h{id}.Hotel-Information` |
| **Expedia activity (Things-To-Do)** | `https://www.expedia.com.sg/things-to-do/singapore-hop-on-hop-off-bus-tour.a203344.activity-details` | `expedia-activity` | One row per review + activity metadata block (price, highlights, features) |
| **Hotels.com hotel** | `https://el.hotels.com/ho434012/hotel-anastassiou-kastoria-ellada/` | `hotels` | One row per review + hotel aggregate |
| **Expedia deeplink (`expe.app.link`)** | `https://expe.app.link/KjB1EuacRSb` | `expedia` | Auto-resolved to hotel page, then scraped |
| **Expedia deeplink (`expe.onelink.me`)** | `https://expe.onelink.me/hnLd/5dfidl8b` | `expedia` | Same |
| **Any mix of the above** | array of all 8 types | mixed | Scraper partitions and dispatches each correctly |
| **Regional Expedia TLDs** | `.co.uk`, `.com.br`, `.de`, `.fr`, `.com.sg`, `.com.au`, `.es`, `.it`, `.nl`, `.jp`, … | `expedia` | All work — same code path |
| 🆕 **Google Maps URL or Place ID** | `https://www.google.com/maps/place/Hotel+Anastassiou+Kastoria/@40.5,21.2/...` or `ChIJX_BcZ7C_TBQRyQXrJlw0fJg` | `google-maps-pending` | Recognised. Parses the hotel name from the URL slug, emits a row with the parsed name + a pre-built Expedia hotel-search URL the buyer can use to find the canonical Expedia listing. **Full auto-resolution coming in v1.1** — until then, click the search URL, find the matching Expedia hotel, re-run with that URL. |

> ⚠️ **Not supported:** Expedia destination/region landing pages (`.../Place.d{id}.Place-To-Visit`) have no reviews of their own — they only list nearby hotels and tours. If you pass one, the actor emits a single explanatory row with a hint about the URL types it does support and **skips the network fetch entirely**. You don't get charged for dead URLs.

### Use cases

| Audience | Use case |
|---|---|
| **Hotel reputation managers** | Daily monitoring of new reviews across owned properties; alert on negative trends |
| **Hospitality consultants** | Benchmark a hotel against direct competitors in the same city; surface category-rating gaps |
| **Tour & activity operators** | Track Things-To-Do listings (your own + competitors) for review-rate, rating-band, recurring complaints |
| **Market analysts** | Geographic + price-tier sentiment analysis across Expedia + Hotels.com inventory |
| **Aggregator builders** | Power downstream review-aggregation SaaS with comprehensive multi-brand data |
| **PE / hospitality due diligence** | Sentiment + rating-trend snapshot of a target property before acquisition |
| **Academic / consumer-research** | Auto-translation patterns, multi-language review trends, traveler-companion demographics |

### How it works

1. **Classify each `startUrls[]` entry** — hotel / activity / Hotels.com / bare ID / deeplink. Reject non-Expedia-Group hosts. Destination pages emit a single explanatory row.
2. **Resolve deeplinks** — for `expe.app.link` / `expe.onelink.me`, follow redirects to the canonical hotel/activity URL before fetching reviews.
3. **For hotel URLs**: GraphQL request to Expedia's `ProductReviewsList` for the per-review stream, plus (when `includeCategoryRatings: true`) parallel calls to `ProductReviewDetails` for the rating summary, `ProductReviewProgressBar` for the **full 5-category breakdown**, `ReviewsSummarisation` for Expedia's **AI-generated guest-sentiment bullets**, and `PageLayout` for the **property photo gallery + descriptive content** (name, address, coordinates, star class, description, categorized amenities, highlights — one response feeds both `propertyImages` and the `property*` fields), and — when opted in — `ProductLocation` for the **nearby/transit/restaurants block** (`includeLocation`) `PropertyContentSectionGroups` for **policies & important info** (`includePolicies`), and `PropertyOffersRoomsAndRates` for **room inventory + live rates** over a chosen date window (`includeRooms`). Paginate through review pages, merging the aggregate block into every emitted row.
4. **For activity URLs**: GraphQL request to Expedia's `ActivityReviewsQuery` + parallel `OverviewQuery` for activity metadata (price, highlights, features). Paginate through review pages.
5. **For Hotels.com URLs**: Use Hotels.com's own review API for the per-review stream, then resolve the **unified Expedia property ID** from the response and fire the same hotel-level add-on calls as the Expedia flow — so Hotels.com rows carry the identical aggregate block (overall rating, total reviews, category ratings, AI summary, photo gallery, property details, and the opt-in location/policies/rooms blocks).
6. **Date filter (when `reviewsFrom` is set)**: stop paginating as soon as a review older than the cutoff appears. Reviews are sorted newest-first by Expedia.
7. **Emit one flat row per review** to the Apify dataset. JSON or CSV export.

### Input configuration

| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
| `startUrls` | `string[]` | yes | — | Any mix of hotel URLs (long / short / minimal), activity URLs, Hotels.com URLs, bare IDs, deeplinks |
| `maxItems` | `integer` | no | `1000` | Hard cap on total review rows. Each row = one paid `result` event. |
| `reviewsFrom` | `string` (YYYY-MM-DD) | no | — | Only emit reviews on/after this date. Triggers smart-pagination stop. Leave empty for all reviews. |
| `includeCategoryRatings` | `boolean` | no | `true` | Include hotel/activity-level aggregate: overall rating, total reviews, **full 5-category breakdown** (Cleanliness, Staff & service, Amenities, Property conditions & facilities, Location), **AI-generated guest-sentiment summary** (`reviewSummaryAI`), and **the property's full photo gallery** (`propertyImages` — typically 100-300 URLs per hotel). Disable if you only need per-review fields — saves one `additional-cost` event per URL. |
| `includePropertyImages` | `boolean` | no | `true` | When `true`, every gallery image URL is returned in `propertyImages` and **billed per image** at $0.0001 each (~$0.02 for a typical 200-image hotel). Set `false` to skip image extraction and pay nothing for images. Has no effect unless `includeCategoryRatings: true`. |
| `maxPropertyImages` | `integer` | no | — (full gallery) | Keep only the **first N** gallery images per hotel (gallery order — hero shots first). Billed only for images returned: `maxPropertyImages: 10` → at most 10 image units per hotel instead of 100-300. |
| `includePropertyDetails` | `boolean` | no | `true` | When `true`, each row carries rich hotel-level descriptive fields: `propertyName`, `starRating`, `propertyType`, `propertyAddress`, `propertyCoordinates`, `propertyDescription`, `propertyLanguages`, `propertyAmenities` (categorized), `propertyHighlights`. Extracted from the **same page-layout response as the photo gallery — no extra per-item charge**. Set `false` to omit them. Has no effect unless `includeCategoryRatings: true`. |
| `includeLocation` | `boolean` | no | **`false`** | **Opt-in.** When `true`, each row carries `propertyLocation` — nearby landmarks, transit/airports, and restaurants, each with walk/drive times. Fires a separate request per hotel, **billed one `additional-cost` ($0.001) per hotel**. Leave off if you don't need location data. |
| `includePolicies` | `boolean` | no | **`false`** | **Opt-in.** When `true`, each row carries `propertyPolicies` — check-in/out times & fees, minimum age, pets, children & extra beds, deposits, payment types, and "you need to know" notes, grouped by section. Fires a separate request per hotel, **billed one `additional-cost` ($0.001) per hotel**. |
| `includeRooms` | `boolean` | no | **`false`** | **Opt-in.** When `true`, each row carries `propertyRooms` — room types, bed config, occupancy, inclusions, nightly & total rate, and cancellation terms. **Prices are a snapshot** for the search window below (stamped as `quotedFor`). Fires a separate (heavy) request per hotel, **billed one `additional-cost` ($0.001) per hotel**. |
| `roomsCheckIn` | `string` (YYYY-MM-DD) | no | ~30 days out | Check-in date for the room-rate quote. Only used with `includeRooms`. |
| `roomsCheckOut` | `string` (YYYY-MM-DD) | no | night after check-in | Check-out date for the room-rate quote. Only used with `includeRooms`. |
| `roomsAdults` | `integer` | no | `2` | Adults for the room-rate quote. Only used with `includeRooms`. |
| `roomsRegion` | `string` | no | `auto` | Region & currency for room-rate quotes. `auto` follows each URL's own domain (`expedia.se` → SEK); an explicit code (`us`, `uk`, `de`, `se`, `jp`, `au`, `in`, `br`, … 33 regions) forces that currency for every quote — even bare hotel IDs. |
| `roomsLanguage` | `string` | no | `english` | Language of room-rate **labels** (room names, meal plans, cancellation texts). `english` (default): English labels with local-currency prices — "Breakfast buffet + 237 kr". `site-default`: the regional site's own language — "Frukostbuffé + 237 kr". Never affects the currency. |
| `includeRoomOffers` | `boolean` | no | **`false`** | **Opt-in.** Adds `mealPlans[]` + `cancellationOptions[]` to every room — each meal-plan and cancellation option with its "+ $X" price delta. **Self-sufficient** — enabling it alone also fetches the room rates (no need to set `includeRooms` too). Billed via the `additional-cost` event: the base rooms unit **plus 5 units per offer option** extracted. |
| `skipReviews` | `boolean` | no | **`false`** | **Rooms-only / price-monitoring mode.** When `true`, reviews are **not scraped** — the actor emits **one summary row per hotel** (`rowType: "hotel"`) carrying whatever hotel-level data you enabled (rooms, offers, details, location, policies, images). No per-review charges, far fewer requests per hotel (so far fewer 429s at scale). Ideal for weekly rate-intelligence runs over a hotel list. |
| `reviewSort` | `enum` | no | `newest` | Sort order for the review list. **`newest`** and **`relevant`** are fully wired via Expedia's GraphQL. **`highest`**, **`lowest`**, **`mostHelpful`** are accepted but currently fall back to `newest` (see [FAQ — v1.1 roadmap](#faq) below). |
| `includeReviewPhotos` | `boolean` | no | `true` | Set `false` to strip `reviewPhotos[]` from every emitted row. Useful for CSV exports or compact JSON. |
| `includeOwnerResponses` | `boolean` | no | `true` | Set `false` to strip `hotelResponse` from every emitted row. Use when you only want guest-side review text. |
| `maxConcurrency` | `integer` | no | `10` | Parallel fetches. Sweet spot 8-15 via Apify Residential. |
| `minConcurrency` | `integer` | no | `1` | Floor — auto-scales up. |
| `maxRequestRetries` | `integer` | no | `100` | Per-URL retry budget on transient failures. |
| `proxy` | object | no | Apify Residential | Auto-prefilled — leave alone unless you need a specific country. |

#### Example input — mixed run, all 6 input types

```json
{
  "startUrls": [
    "https://www.expedia.co.uk/Kastoria-Hotels-Hotel-Anastassiou.h6136437.Hotel-Information",
    "https://www.expedia.com/h17369936.Hotel-Information",
    "https://www.expedia.com.br/Rio-De-Janeiro-Hotels-Premier-Copacabana-Hotel.h53296.Hotel-Information",
    "https://www.expedia.com.sg/things-to-do/singapore-hop-on-hop-off-bus-tour-by-open-top-bus.a203344.activity-details",
    "https://el.hotels.com/ho434012/hotel-anastassiou-kastoria-ellada/",
    "https://expe.app.link/KjB1EuacRSb",
    "600217"
  ],
  "reviewsFrom": "2025-01-01",
  "includeCategoryRatings": true,
  "maxItems": 1000,
  "maxConcurrency": 10,
  "proxy": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
````

This yields up to 1,000 review rows since 2025-01-01 across 7 input URLs spanning 3 brands and 5 regional TLDs.

### Output schema

Two row shapes — distinguishable by the `provider` field.

#### `provider: "expedia"` or `"hotels"` (hotel review row)

```jsonc
{
  "provider":          "expedia",            // or "hotels" for Hotels.com
  "url":               "https://www.expedia.co.uk/Kastoria-Hotels-Hotel-Anastassiou.h6136437.Hotel-Information",
  "hotelId":           "6136437",
  "hotelName":         "Hotel Anastassiou",
  "locale":            "en_GB",

  // ── Per-review fields ───────────────────────────────────────
  "reviewId":          "6136437|2026-04-12|10|...",
  "reviewText":        "The hotel was wonderful and the staff very welcoming...",
  "reviewTitle":       "Amazing stay",
  "reviewRating":      10,                   // 0-10 scale
  "reviewRatingLabel": "Exceptional",        // "Exceptional" / "Wonderful" / "Very good" / "Good" / "Okay" / "Poor"
  "reviewDate":        "2026-04-12",
  "stayMonth":         "Mar 2026",
  "stayDuration":      "Stayed 3 nights in Mar 2026",
  "verified":          true,
  "authorName":        "Maria",

  // ── Trip metadata ───────────────────────────────────────────
  "tripType":          "leisure",
  "traveledWith":      "partner",            // "family" / "partner" / "business" / "solo" / "friends"

  // ── Sentiment highlights ────────────────────────────────────
  "sentiments": {
    "liked":           ["Cleanliness", "Staff & service", "Property condition"],
    "disliked":        ["Room amenities"]
  },

  // ── Photos ──────────────────────────────────────────────────
  "reviewPhotos":      ["https://...jpg", "https://...jpg"],

  // ── Management response (when present) ──────────────────────
  "hotelResponse": {
    "responseText":    "Thank you for staying with us...",
    "respondedOn":     "2026-04-15",
    "responderName":   "Hotel Manager"
  },

  // ── Hotel aggregate (when includeCategoryRatings: true) ─────
  "hotelOverallRating":   8.2,
  "hotelRatingLabel":     "Very good",
  "hotelTotalReviews":    1247,
  "hotelCategoryRatings": [                                  // up to 5 categories
    { "label": "Cleanliness",                       "percent": 88 },
    { "label": "Staff & service",                   "percent": 84 },
    { "label": "Amenities",                         "percent": 84 },
    { "label": "Property conditions & facilities",  "percent": 82 },
    { "label": "Location",                          "percent": 88 }
  ],

  // ── Expedia AI guest-sentiment summary (when available) ─────
  // Bulleted topics auto-generated by Expedia from many reviews. Each
  // bullet aggregates several individual reviews — `mentions` tells you
  // how many. `null` when the property has too few reviews for Expedia
  // to summarise (typically small / low-volume listings).
  "reviewSummaryAI": [
    {
      "category": "What guests liked",
      "text":     "Breakfast was consistently delicious, with hot food served hot and cold items kept cool.",
      "mentions": 7
    },
    {
      "category": "What guests liked",
      "text":     "The spa experience was exceptional with beautifully designed rooms and large whirlpool tubs.",
      "mentions": 3
    }
  ],
  "reviewSummaryAIDisclaimer": "From real guest reviews summarized by AI.",

  // ── Full property photo gallery (when available) ────────────
  // Every image Expedia shows in the hotel's gallery, ordered as
  // Expedia returns them, deduplicated. Typically 100-300 images
  // per property. URLs are the canonical CDN paths — append your
  // own `?impolicy=resizecrop&rw={width}&ra=fit` query string if
  // you want a specific size, or use as-is for the original.
  "propertyImages": [
    "https://images.trvl-media.com/lodging/10000000/9730000/9721900/9721825/w5977h3997x10y0-0092ce90.jpg",
    "https://images.trvl-media.com/lodging/10000000/9730000/9721900/9721825/92e976cb.jpg",
    "https://images.trvl-media.com/lodging/10000000/9730000/9721900/9721825/8d2cbffe.jpg"
    // … 217 more
  ],

  // ── Property description & amenities (when includePropertyDetails: true) ──
  "propertyName":        "The Anniversary Inn Logan",
  "starRating":          2.5,                       // star class, NOT the guest score
  "propertyType":        "BedAndBreakfast",         // schema.org type
  "propertyAddress": {
    "street":     "169 East Center Street",
    "city":       "Logan",
    "region":     "UT",
    "postalCode": "84321",
    "country":    "USA"
  },
  "propertyCoordinates": { "latitude": 41.731704, "longitude": -111.830614 },
  "propertyDescription": "The Anniversary Inn Logan provides free continental breakfast and more.\nRoom features\nAll guestrooms are individually furnished…",
  "propertyLanguages":   ["English"],
  "propertyAmenities": [                             // categorized
    { "category": "Parking",        "items": ["Free self parking on site"] },
    { "category": "Breakfast",      "items": ["Continental breakfast included", "Served daily"] },
    { "category": "Spa",            "items": ["In-room massages"] },
    { "category": "Accessibility",  "items": ["No elevator", "Wheelchair accessible (may have limitations)"] }
    // … more categories (Pets, Conveniences, Guest services, Business services, More)
  ],
  "propertyHighlights": [
    { "title": "Highly rated by solo travelers", "description": "This property received multiple high ratings from solo travelers." },
    { "title": "Top rated breakfast",            "description": "Experience delightful mornings with the top rated breakfast." }
  ],

  // ── Location intelligence (only when includeLocation: true) ──
  "propertyLocation": {
    "whatsNearby": [
      { "name": "Logan Utah Temple",      "distance": "7 min walk" },
      { "name": "Utah State University",  "distance": "18 min walk" }
    ],
    "gettingAround": [
      { "name": "Pleasant View Station",  "distance": "56 min drive" }
    ],
    "restaurants": [
      { "name": "Center Street Grill",    "distance": "3 min walk" },
      { "name": "Le Nonne",               "distance": "5 min walk" }
    ]
  },

  // ── Policies & important info (only when includePolicies: true) ──
  "propertyPolicies": [
    { "section": "Check-in",  "items": ["Check-in start time: 5:00 PM; Check-in end time: midnight", "Minimum check-in age: 21", "Early check-in fee: USD 35.00"] },
    { "section": "Check-out", "items": ["Check-out before noon", "Late check-out fee: USD 35.00"] },
    { "section": "Pets",      "items": ["Pets not allowed"] },
    { "section": "Fees",      "items": ["Deposit: USD 50.00 per stay"] }
    // … more sections (Children & extra beds, Payment types, You need to know, …)
  ],

  // ── Room inventory + rates (only when includeRooms: true) ──
  // Prices are a point-in-time snapshot for the `quotedFor` window, quoted by
  // the `pos` point-of-sale (drives the currency — see `roomsRegion`). Labels
  // are English by default (`roomsLanguage`), whatever the currency.
  "propertyRooms": {
    "quotedFor": { "checkIn": "2026-06-28", "checkOut": "2026-06-29", "adults": 2 },
    "pos": "www.expedia.com",           // POS host that produced the quote
    "currency": "USD",                  // ISO currency of the quote
    "rooms": [
      {
        "name": "Signature Room",
        "details": ["Sleeps 2", "1 King Bed", "Breakfast available", "Parking included"],
        "nightlyRate": "$414",          // lead (cheapest) offer — localized display string
        "totalRate": "$1,465",
        "nightlyRateValue": 414,        // same prices as plain numbers — sort/compare ready
        "totalRateValue": 1465,
        "currency": "USD",
        "cancellation": "Non-refundable",
        // Meal-plan "Extras" with their per-stay price delta:
        "mealPlans": [
          { "name": "No extras",                             "subText": null, "price": "+ $0",   "selected": true  },
          { "name": "Half board",                            "subText": null, "price": "+ $99",  "selected": false },
          { "name": "All-inclusive (food/beverages/snacks)", "subText": null, "price": "+ $295", "selected": false }
        ],
        // Cancellation-policy choices with their price delta. The same policy can
        // repeat with different payment terms in subText (pay deposit vs pay later):
        "cancellationOptions": [
          { "name": "Non-Refundable",                "subText": null,                   "price": "+ $0",   "selected": true  },
          { "name": "Fully refundable before Jul 9", "subText": "Reserve now, pay deposit", "price": "+ $0",   "selected": false },
          { "name": "Fully refundable before Jul 9", "subText": "Reserve now, pay later",   "price": "+ $646", "selected": false }
        ]
      }
      // … one entry per room type
    ]
  },

  "scrapedAt":         "2026-05-16T10:24:31Z"
}
```

#### `provider: "expedia-activity"` (activity review row)

```jsonc
{
  "provider":               "expedia-activity",
  "activityId":             "203344",
  "activityUrl":            "https://www.expedia.com.sg/things-to-do/singapore-hop-on-hop-off-bus-tour.a203344.activity-details",
  "locale":                 "en_SG",
  "siteid":                 "1",
  "currency":               "SGD",

  // ── Activity aggregate (always included for activities) ─────
  "activityName":           "Singapore Hop-On Hop-Off Bus Tour by Open-Top Bus",
  "activitySubHeading":     "By Big Bus Tours",
  "activityPrice":          "S$65",
  "activityOverallRating":  "8.4/10",
  "activityRatingLabel":    "Very good",
  "activityTotalReviews":   216,
  "activityHighlights":     ["Explore Singapore by open-top bus", "Frequent service every 25-40 min", ...],
  "activityFeatures":       ["Free cancellation", "Printed voucher", "Instant confirmation", ...],

  // ── Per-review fields ───────────────────────────────────────
  "reviewId":               "203344|2026-03-14|...",
  "reviewText":             "No plug in to keep phone charged. I was afraid I'd lose my access...",
  "reviewRating":           3,                    // 0-10 scale
  "reviewRatingLabel":      "Poor",
  "reviewDate":             "2026-03-14",
  "activityDate":           "Mar 2026",           // when the user did the activity
  "userLocation":           "Australia",
  "authorName":             "A verified traveller",
  "reviewedOn":             "Reviewed on 14 Mar, 2026"
}
```

> Hotel rows and activity rows have **different field sets** — that's intentional. Activities don't have `traveledWith` / `stayDuration` / `hotelResponse`; hotels don't have `activityHighlights` / `activityFeatures`. Filter by `provider` if you only want one shape.

### Pricing

Pay-per-event — no monthly subscription.

| Event | When it fires | Rate |
|---|---|---|
| `Actor Start` | Once per run | $0.008 |
| `result` | Per review row emitted | $0.0025 |
| `additional-cost` (monitoring mode / category ratings) | Per hotel/activity when `includeCategoryRatings: true` | $0.001 |
| `additional-cost` (gallery images) | When `includePropertyImages: true` — **1 unit per gallery image returned** (typically 100-300 per hotel; cap the count & cost with `maxPropertyImages`) | $0.001 / unit |
| `additional-cost` (location) | Per hotel when `includeLocation: true` — the `propertyLocation` block | $0.001 |
| `additional-cost` (policies) | Per hotel when `includePolicies: true` — the `propertyPolicies` block | $0.001 |
| `additional-cost` (rooms) | Per hotel when `includeRooms: true` — the `propertyRooms` block | $0.001 |
| `additional-cost` (room offers) | When `includeRoomOffers: true` — **5 units per meal-plan/cancellation option** extracted, per hotel (on top of the base rooms unit) | $0.001 / unit |

**Cost examples:**

| Run | Charges |
|---|---|
| 1 hotel, 50 reviews, with category ratings (no images) | $0.008 + 50 × $0.0025 + 1 × $0.001 = **$0.134** |
| 1 hotel, 50 reviews, with category ratings **+ 220 gallery images** | $0.134 + 220 × $0.0001 = **$0.156** |
| 10 hotels × 100 reviews avg, with category ratings (no images) | $0.008 + 1000 × $0.0025 + 10 × $0.001 = **$2.52** |
| 10 hotels × 100 reviews avg, with category ratings + ~200 images per hotel | $2.52 + 10 × 200 × $0.0001 = **$2.72** |
| 1 activity, 200 reviews, with metadata | $0.008 + 200 × $0.0025 + 1 × $0.001 = **$0.509** |
| Date-filtered (5 hotels, reviewsFrom: last 7 days) — say 30 new reviews total | $0.008 + 30 × $0.0025 + 5 × $0.001 = **$0.088** |

### What makes this richer than competing actors

We surveyed all Expedia + Hotels.com review scrapers on Apify Store. Direct comparison:

| Capability | Competitor actors | This actor |
|---|---|---|
| Expedia hotel reviews | ✅ most | ✅ |
| **Expedia Things-To-Do activities** | ❌ none | ✅ unique — tours, attractions, day trips |
| **Hotels.com reviews via the same scraper** | ❌ usually separate actor | ✅ same input array, same row shape |
| Multi-region TLDs (`.co.uk`, `.de`, `.com.br`, …) | partial | ✅ 10+ TLDs validated |
| **Expedia deeplink resolution** (`expe.app.link`, `expe.onelink.me`) | ❌ | ✅ |
| **Bare property ID input** (e.g. `600217`) | ❌ | ✅ auto-expanded |
| Date filtering with smart pagination stop | ❌ usually fetches everything | ✅ stops early, saves cost |
| Sentiment (liked / disliked) extraction | partial | ✅ structured array |
| Traveled-with normalization | ❌ | ✅ `family` / `partner` / `business` / `solo` / `friends` |
| Management response capture | partial | ✅ text + date + responder name |
| Hotel category ratings (cleanliness / staff / amenities / property conditions / location) | ❌ usually missing or partial (3 of 5) | ✅ full 5-category breakdown when `includeCategoryRatings: true` |
| Expedia AI guest-sentiment summary (bulleted topics + mention counts) | ❌ not extracted by other scrapers | ✅ included when Expedia has generated one for the property |
| **Full property photo gallery URLs** (100-300 per hotel) | ❌ not extracted by other scrapers | ✅ every gallery image URL, deduplicated, when `includeCategoryRatings: true` |
| **Property description + categorized amenities + address/coords/star-class/highlights** | ❌ reviews-only | ✅ full descriptive block per hotel, no per-item charge, when `includePropertyDetails: true` |
| **Location intelligence** (nearby landmarks / transit / restaurants + walk-drive times) | ❌ not extracted | ✅ structured `propertyLocation` block, opt-in via `includeLocation: true` |
| **Policies & important info** (check-in/out times & fees, min age, pets, deposits) | ❌ not extracted | ✅ structured `propertyPolicies` block, opt-in via `includePolicies: true` |
| **Room inventory + live rates** (room types, occupancy, nightly/total price, cancellation) | ❌ not extracted | ✅ structured `propertyRooms` block with configurable date window, opt-in via `includeRooms: true` |
| **Meal-plan & cancellation pricing** (Half board / All-inclusive / refundable-vs-not, each priced) | ❌ not extracted | ✅ `mealPlans[]` + `cancellationOptions[]` on every room — each option with its "+ $X" delta |
| Activity highlights + features (for Things-To-Do) | ❌ | ✅ full arrays |
| Destination-page graceful handling (no dead-URL charges) | ❌ usually 0-row failures | ✅ single explanatory row, no fetch |

### FAQ

**Can I scrape Expedia hotels AND Hotels.com in the same run?**
Yes. Paste both in `startUrls`. Each row carries a `provider` field (`expedia` / `hotels`) so you can split them apart in your downstream pipeline.

**What's a "Things-To-Do" activity, and how is it different from a hotel?**
Activities are tours, attractions, day trips, classes, transfers — anything bookable in Expedia's "Things to do" section. URL pattern: `/things-to-do/{slug}.a{id}.activity-details`. The output row has a different shape (no `traveledWith`, `stayDuration`, or `hotelResponse`; adds `activityHighlights`, `activityFeatures`, `userLocation`, `activityDate`). Rows are tagged `provider: "expedia-activity"`.

**Can I paste a deeplink URL like `expe.app.link/KjB1EuacRSb`?**
Yes. The scraper follows the redirect chain to the canonical hotel/activity URL before fetching reviews. Useful when ingesting URLs from mobile-app shares, marketing campaigns, or social media.

**How does date filtering work with pagination?**
Pass `reviewsFrom: "YYYY-MM-DD"`. Expedia returns reviews newest-first, so the scraper paginates until it hits a review older than your cutoff, then stops. You only pay for reviews that match. Perfect for daily-delta monitoring workflows.

**Why do I get an "explanatory row" instead of reviews on a destination URL?**
URLs like `.../Place.d12345.Place-To-Visit` are destination/region landing pages — they don't have reviews of their own, just lists of nearby hotels and tours. Rather than silently failing or charging you for a dead fetch, the scraper emits a single row with a clear hint about the URL types that ARE supported. No network charge.

**Can I just paste hotel IDs without full URLs?**
Yes. `600217` and `h600217` both auto-expand to `https://www.expedia.com/Hotel.h600217.Hotel-Information`. Saves time when importing IDs from a CRM or spreadsheet.

**Does this work on regional Expedia domains?**
Yes — all 33 Expedia points-of-sale are supported and verified: `.com`, `.ca`, `.mx`, `.com.br`, `.com.ar`, `.co.uk`, `.ie`, `.de`, `.fr`, `.es`, `.it`, `.nl`, `.be`, `.at`, `.ch`, `.dk`, `.fi`, `.gr`, `.no`, `.se`, `.com.au`, `.co.nz`, `.co.jp`, `.co.kr`, `.co.in`, `.com.sg`, `.com.hk`, `.com.tw`, `.com.my`, `.co.th`, `.com.ph`, `.com.vn`, `.co.id` — plus regional Hotels.com hosts (`fr.hotels.com`, …). Same scraper, same row shape. The hotel's `locale` field on each row tells you which region the data came from, and room rates are quoted in that region's currency (see `roomsRegion`).

**What if `includeCategoryRatings: false`?**
You'll save one `additional-cost` event per URL ($0.001 each), but you'll lose **all** hotel-level fields: `hotelOverallRating`, `hotelTotalReviews`, `hotelCategoryRatings`, `reviewSummaryAI`, `reviewSummaryAIDisclaimer`, `propertyImages`, and the descriptive block (`propertyName`, `starRating`, `propertyType`, `propertyAddress`, `propertyCoordinates`, `propertyDescription`, `propertyLanguages`, `propertyAmenities`, `propertyHighlights`). Set this to `false` if you only need per-review data and have those aggregates elsewhere.

**🆕 What is `propertyRooms` (`includeRooms`)?**
Opt-in (`includeRooms: true`, default off). Adds a `propertyRooms` object with the hotel's room inventory **and live rates** for a search window: `{ quotedFor: { checkIn, checkOut, adults }, pos, currency, rooms: [ { name, details[], nightlyRate, totalRate, nightlyRateValue, totalRateValue, currency, cancellation, mealPlans[], cancellationOptions[] } ] }`. Each room's `details` lists bed config / occupancy / inclusions (e.g. "Sleeps 2", "1 Queen Bed", "Breakfast included"). `nightlyRate`/`totalRate` are the localized display strings ("$320", "3 415 kr", "￥51,312"); `nightlyRateValue`/`totalRateValue` are the same prices as **plain numbers** — ready for sorting, math, and cross-hotel comparison without parsing. `pos` records which Expedia point-of-sale produced the quote (e.g. `www.expedia.se`) and the top-level `currency` its ISO code.

When you enable **`includeRoomOffers: true`** (a paid option — see below; it also fetches the room rates on its own, so you don't need `includeRooms` as well), each room also carries two arrays that mirror the room card's selectors — **this is where the "+$X" deltas live**:

- **`mealPlans[]`** — the "Extras" / board-basis options, each `{ name, price, selected }`: e.g. `{ "name": "Half board", "price": "+ $99", "selected": false }`, `{ "name": "All-inclusive (food/beverages/snacks)", "price": "+ $295" }`. Empty for properties that don't offer meal-plan tiers.
- **`cancellationOptions[]`** — the cancellation-policy choices, each `{ name, price, selected }`: e.g. `{ "name": "Non-Refundable", "price": "+ $0", "selected": true }`, `{ "name": "Fully refundable before Jul 9", "price": "+ $90" }`.

`selected: true` marks the option included in the base `totalRate`; add another option's `price` to that base to get its total. The top-level `nightlyRate`/`totalRate`/`cancellation` on the room are the **lead (cheapest, base) offer**, kept for quick reference.

> **Pricing:** the per-offer meal-plan + cancellation breakdown is premium rate-intelligence data, billed via the **`additional-cost` event — 5 units ($0.001 each) per meal-plan/cancellation option extracted**, per hotel, on top of the 1-unit base rooms charge. So a hotel exposing 40 options costs 40 × 5 = 200 units = $0.20 for the offers (plus $0.001 for the room data). Without `includeRoomOffers`, `mealPlans`/`cancellationOptions` come back as empty arrays and you only pay the base rooms charge.

**Local currency — any of 33 regions:** room prices are quoted by an Expedia **point-of-sale**, and the POS decides the currency. Two ways to pick it:

- **Automatic (default)** — the quote follows each input URL's own domain: `https://www.expedia.se/...` → SEK, `expedia.co.jp` → JPY, `expedia.com.br` → BRL, `fr.hotels.com` → EUR, plain `.com` and bare property IDs → USD.
- **`roomsRegion`** — force one region for the whole run regardless of URL form. Pick from 33 verified codes (`us ca mx br ar uk ie de fr es it nl be at ch dk fi gr no se au nz jp kr in sg hk tw my th ph vn id`), each quoting in its local currency (USD, CAD, MXN, BRL, GBP, EUR, CHF, DKK, NOK, SEK, AUD, NZD, JPY, KRW, INR, SGD, HKD, TWD, MYR, THB, PHP, VND, IDR). Example: bare IDs + `"roomsRegion": "se"` → every quote in SEK. (Argentina's POS quotes in USD — that's Expedia's own behavior.)

The output is self-describing: `propertyRooms.pos` is the POS host used, `propertyRooms.currency` the ISO code, and every room carries both the localized price strings and numeric `nightlyRateValue`/`totalRateValue`.

**English labels + local currency (default):** label language is independent of currency. By default the actor requests **English** labels from every POS — so an `expedia.se` quote reads `"Breakfast buffet" + 237 kr` (English name, SEK price). Set `roomsLanguage: "site-default"` if you want the labels in the regional site's own language instead (`"Frukostbuffé" + 237 kr`). Every one of the 33 POSes supports the English mode — verified live.

**Prices are a point-in-time snapshot** — they depend on the search dates, which is why the window is stamped into `quotedFor`. Control it with `roomsCheckIn` / `roomsCheckOut` (YYYY-MM-DD) and `roomsAdults`; leave them empty to default to ~30 days out, 1 night, 2 adults. Fires a separate (~309KB) request per hotel and is **billed one `additional-cost` event ($0.001) per hotel** for the room inventory; the meal-plan/cancellation breakdown is billed separately per option (see the pricing note above). (If a hotel is sold out for the chosen dates, `propertyRooms` is `null` — pick different dates.)

**🆕 What is `propertyPolicies` (`includePolicies`)?**
Opt-in (`includePolicies: true`, default off). Adds a `propertyPolicies` array to every hotel row — the property's policies & important info grouped by section: **Check-in** (start/end times, early-check-in fee, minimum age), **Check-out** (time, late fee), **Pets**, **Children & extra beds**, **Fees / deposits**, accepted **payment types**, and the "**You need to know**" notes (ID requirements, incidental holds, safety features). Each section is `{ section, items[] }`. Fires a separate request per hotel and is **billed one `additional-cost` event ($0.001) per hotel**.

**🆕 What is `propertyLocation` (`includeLocation`)?**
When you opt in with `includeLocation: true`, every hotel row gains a structured `propertyLocation` object with three lists — `whatsNearby` (landmarks/attractions), `gettingAround` (transit stations & airports), and `restaurants` — each entry a `{ name, distance }` pair where distance is a human-readable walk/drive time (e.g. `{ "name": "Utah State University", "distance": "18 min walk" }`). It's **opt-in (default off)** because it fires a separate request per hotel and is **billed one `additional-cost` event ($0.001) per hotel**. Like all hotel-level fields, the same block is repeated on every review row for that hotel.

**🆕 What property descriptive data do I get (`includePropertyDetails`)?**
When `includePropertyDetails: true` (default), every hotel row carries: `propertyName` (canonical name), `starRating` (the property's star class, e.g. 2.5 — distinct from the guest review score), `propertyType` (e.g. "Hotel", "BedAndBreakfast", "Resort"), `propertyAddress` (street/city/region/postalCode/country), `propertyCoordinates` (lat/lng), `propertyDescription` (the "About this property" narrative), `propertyLanguages` (languages spoken), `propertyAmenities` (categorized — Parking, Breakfast, Spa, Accessibility, …), and `propertyHighlights` (the marketing highlight cards). All of this is extracted from the **same page-layout response as the photo gallery**, so it adds **no extra charge** beyond the `additional-cost` event that `includeCategoryRatings` already triggers. Set `includePropertyDetails: false` to omit these fields.

**🆕 What is `propertyImages` and how big does it get?**
Every URL in the hotel's photo gallery on Expedia (e.g. main exterior shots, all room photos, lobby, restaurant, pool, etc.) — typically **100-300 image URLs per property**. Only need the first few? Set `maxPropertyImages` (e.g. `10`) to keep just the first N in gallery order (hero/cover shots first) — you're billed only for what's returned. The URLs are returned as the canonical CDN paths (`images.trvl-media.com/lodging/...jpg`) with the `?impolicy=...` resize query stripped, so you can append your own size parameter (`?impolicy=resizecrop&rw=1200&ra=fit` for 1200px-wide, etc.) or use the original. Images are deduplicated and ordered as Expedia returns them. Returns `null` when extraction fails (rare — usually a transient GraphQL error). Requires `includeCategoryRatings: true` AND `includePropertyImages: true` (both default `true`).

**🆕 How is `propertyImages` billed?**
Each image is billed as **1 `additional-cost` unit ($0.001)** — and only for images actually returned. A typical 200-image hotel adds ~$0.20 to your run cost; a 500-image resort adds ~$0.50. Two ways to control this:

- **`maxPropertyImages`** — cap the gallery to the first N images (hero/cover shots come first in gallery order). `{ "includePropertyImages": true, "maxPropertyImages": 10 }` returns at most 10 URLs and bills at most 10 units ($0.01) per hotel.
- **`includePropertyImages: false`** — the PageLayout image extraction is skipped entirely and you pay $0 for images.

**🆕 What is `reviewSummaryAI` and where does it come from?**
Expedia generates an AI-written bulleted summary of guest sentiment for each hotel that has enough review volume — surfaced in their iOS app's "See all reviews" screen. Each bullet aggregates several individual reviews into a topic (e.g. *"Breakfast was consistently delicious, with hot food served hot and cold items kept cool."*) and carries a `mentions` count showing how many guest reviews fed into it. The category heading (`"What guests liked"` etc.) is preserved alongside the text. Returns `null` when Expedia hasn't generated one — usually low-volume / niche properties. The accompanying `reviewSummaryAIDisclaimer` field carries Expedia's own attribution string (*"From real guest reviews summarized by AI."*) so downstream consumers can credit the source correctly. Only fetched when `includeCategoryRatings: true`.

**🆕 Can I paste a Google Maps URL or Place ID?**
Yes — paste it directly into `startUrls`. The actor recognises Maps URLs (`/maps/place/Hotel+Name/...`, `maps.app.goo.gl/...`) and bare Place IDs (`ChIJX_BcZ7C_TBQRyQXrJlw0fJg`). It parses the hotel name from the URL slug and emits a row tagged `provider: "google-maps-pending"` containing the parsed name, a pre-built Expedia hotel-search URL, and a hint with the next step. **Full auto-resolution (Maps URL → Expedia canonical hotel URL → reviews fetched in same run) is queued for v1.1.** The blockers are (a) capturing Expedia's hotel-search GraphQL persisted-query from a live browser session, (b) building a match-quality scorer for ambiguous results. Until then, the explanatory row gives you a deterministic shortcut to find the right Expedia URL manually.

**🆕 What does `reviewSort` do, and why don't all 5 options work yet?**
`reviewSort` controls the order of reviews fetched. Five values accepted:

- **`newest`** (default) — fully wired, applies Expedia's `MOST_RECENT_SORT_URN`. Required for `reviewsFrom` date-filter mode (the smart-stop pagination relies on newest-first ordering).
- **`relevant`** — fully wired, omits the `sortBy` parameter so Expedia returns its default "most relevant" order.
- **`highest`** / **`lowest`** / **`mostHelpful`** — accepted as input but **currently fall back to `newest`** with a clear log message. Expedia's GraphQL uses opaque `urn:expediagroup:taxonomies:core:#{uuid}` sort values per option. We have the `MOST_RECENT` URN; the other 3 need to be captured from a live Expedia browser session, which is queued for v1.1.

**🆕 What do `includeReviewPhotos` and `includeOwnerResponses` do?**
Toggles that strip the matching fields from emitted rows (`reviewPhotos[]` and `hotelResponse`). Useful for CSV exports (flatter row shape), or when you want compact rows. **Note:** these are post-fetch filters — they don't reduce proxy bandwidth cost. GraphQL-level skipping (slimming the request body) is queued for v1.1.

### Support

- **Bugs / feature requests** — open an [issue on this actor](https://console.apify.com/actors/uVgVxc4Db5dKvybiP/issues)
- **Custom exports / tailored field selection** — email <muhamed.didovic@gmail.com>
- **API access (no Apify fee, raw usage)** — same address for direct API setup

### Additional services

- Custom scraping work for sites not in the catalog
- Bulk historical dataset purchase (skip the scraping step entirely)
- Webhook delivery to your S3 / GCS / DB pipeline
- Daily / hourly scheduled monitoring with email or Telegram alerts

Contact: <muhamed.didovic@gmail.com>

### Explore more scrapers

Other travel + reviews actors in the [memo23 portfolio](https://apify.com/memo23):

- [Trustpilot Reviews](https://apify.com/memo23/trustpilot-scraper-ppe) — flagship reviews scraper (264 u30d, 4.9★)
- [Glassdoor Reviews](https://apify.com/memo23/glassdoor-scraper-ppr) — employer reviews
- [Indeed Reviews](https://apify.com/memo23/apify-indeed-reviews-ppr) — employer + interview reviews
- [Clutch](https://apify.com/memo23/apify-clutch-cheerio) — B2B services reviews
- [TripAdvisor](https://apify.com/memo23/tripadvisor-scraper) — competing travel reviews
- [Booking.com](https://apify.com/memo23) — see profile for related travel scrapers

***

### ⚠️ Disclaimer

This Actor is an independent tool and is **not affiliated with, endorsed by, or sponsored by** Expedia Group, Inc., Expedia.com, Hotels.com, or any related entity. All trademarks mentioned are the property of their respective owners.

The scraper accesses only **publicly visible** review content rendered by Expedia.com, Hotels.com, and their regional TLD variants. No login bypass, no API-key forgery, no CAPTCHA solving, no private/authenticated endpoints. The actor honours `robots.txt` and rate-limits via concurrency cap (default 10) to avoid burdening Expedia Group's infrastructure.

Users are responsible for:

- Complying with Expedia.com's and Hotels.com's Terms of Service in your target jurisdiction
- Following GDPR (EU/UK), CCPA (US), PIPL (China), LGPD (Brazil), and any other applicable data-protection law when storing, processing, or republishing review + guest-name data
- Not contacting reviewers listed in scraped data for unsolicited commercial outreach
- Not republishing scraped reviews in a way that competes commercially with Expedia or Hotels.com
- Respecting reviewer privacy — many reviewers use first-name-only handles, do not attempt to de-anonymize

We do not store scraped data — the Actor returns rows directly to your Apify dataset for your authorized use.

***

### SEO Keywords

expedia scraper, expedia reviews scraper, expedia.com scraper, scrape expedia, expedia api, hotels.com scraper, hotels.com reviews, expedia things-to-do scraper, expedia activities scraper, hotel reviews api, tour reviews scraper, attraction reviews scraper, travel reviews data, hospitality reviews api, hotel reputation monitoring, sentiment analysis hotels, expedia deeplink resolver, expedia.co.uk scraper, expedia.de scraper, expedia.com.br scraper, multi-region expedia, expedia JSON export, expedia CSV export, travel data mining, hotel category ratings scraper, expedia AI review summary, AI generated hotel sentiment summary, guest sentiment AI bullets, expedia 5-category breakdown, cleanliness staff amenities property conditions location ratings, expedia property images scraper, expedia hotel photo gallery, expedia gallery URLs, hotel image scraping, scrape hotel photos expedia, expedia hotel amenities scraper, expedia property description scraper, hotel amenities data, scrape hotel address coordinates, hotel metadata scraper, expedia star rating scraper, expedia nearby places scraper, hotel location intelligence, points of interest near hotel, hotel walk drive times, expedia what's nearby data, expedia hotel policies scraper, check-in check-out times scraper, hotel pet policy data, hotel deposit fees scraper, expedia important information, expedia room rates scraper, hotel price scraper, room inventory scraper, expedia nightly rate tracking, hotel rate intelligence, room type occupancy scraper, expedia cancellation policy scraper, expedia meal plan prices, half board all-inclusive scraper, board basis pricing, expedia rate plan scraper, refundable rate prices

# Actor input Schema

## `startUrls` (type: `array`):

Hotel pages, activity (Things-To-Do) pages, Hotels.com pages, **or bare Expedia property IDs**. Expedia and Hotels.com URLs may be mixed in the same run.

**Expedia hotels** — long slug (`.../City-Hotels-Name.h600217.Hotel-Information`), minimal slug (`.../Hotel.h600217.Hotel-Information`), short path (`.../h600217.Hotel-Information`), all regional TLDs (`expedia.co.uk`, `expedia.com.br`, `expedia.de`, `expedia.com.sg`, …), and deeplinks (`expe.app.link/...`, resolved automatically). Bare ids (digits or `h600217`) expand to `https://www.expedia.com/Hotel.h{id}.Hotel-Information`.

**Expedia activities (Things-To-Do)** — `https://www.expedia.com/things-to-do/{slug}.a{id}.activity-details` (and regional equivalents). Returns activity reviews under `provider: "expedia-activity"`.

**Hotels.com** — `https://{locale}.hotels.com/ho{id}/{slug}/`.

**Not supported:** destination/region landing pages (`.../Name.d{id}.Place-To-Visit`) — those have no reviews of their own. If supplied, the run produces one explanatory row per such URL and skips the fetch entirely (no charges burned on dead URLs).

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

City or destination to search, e.g. London, Tokyo, New York. Leave empty to use Option A (URLs) instead.

## `searchCheckIn` (type: `string`):

Check-in date for the destination search (YYYY-MM-DD). Prices are a snapshot for this window. Leave empty to default to ~30 days from the run date. Only used in Option B.

## `searchCheckOut` (type: `string`):

Check-out date for the destination search (YYYY-MM-DD). Leave empty to default to the night after check-in. Only used in Option B.

## `searchAdults` (type: `integer`):

Number of adults per room for the search price quote. Defaults to 2. Only used in Option B.

## `searchSort` (type: `string`):

How to sort the destination search results.

## `searchCurrency` (type: `string`):

ISO 4217 currency code for search prices, e.g. USD, EUR, GBP, THB. Defaults to USD. Only used in Option B.

## `searchRegionId` (type: `string`):

Power-user override: skip city→regionId resolution and search this Expedia regionId directly (e.g. 6195474 for London City Centre). Leave empty to resolve from the Destination field above.

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

Maximum number of items that will be scraped.

## `googleMapsUrlNote` (type: `string`):

**You can paste Google Maps URLs and place IDs directly into `Start URLs` above.** The actor recognizes them, parses the hotel name from the URL slug (e.g. `Hotel+Anastassiou+Kastoria`), and emits a row with the parsed name plus an Expedia hotel-search URL the buyer can use to find the canonical Expedia listing. **Full auto-resolution (Google Maps → Expedia hotel URL → reviews) is coming in v1.1** — for now this surfaces the hotel name + a search shortcut. Paste an actual Expedia hotel URL alongside if you want reviews fetched in the same run.

## `reviewsFrom` (type: `string`):

Format should be YYYY-MM-DD, e.g., 2025-02-20

## `reviewSort` (type: `string`):

Order in which to fetch reviews. **All five values are fully wired** via Expedia's GraphQL `sortBy` URN — buyers get the order they ask for. `mostHelpful` maps to Expedia's `Most relevant` URN (Expedia has no separate helpfulness dimension; relevance already accounts for helpful-vote weighting). The resolved URN is printed at run start so you can verify what's being sent.

## `includeReviewPhotos` (type: `boolean`):

When `false`, the actor strips the `reviewPhotos` array from each emitted row. Useful for CSV exports where flat structure matters, or to make rows smaller. Default `true`.

## `includeOwnerResponses` (type: `boolean`):

When `false`, the actor strips the `hotelResponse` object from each emitted row. Set `false` when you only want guest-side review data. Default `true`.

## `skipReviews` (type: `boolean`):

When `true`, the actor does **not** scrape reviews — it emits a single summary row per hotel carrying whatever hotel-level data you enabled (room rates, meal-plan/cancellation pricing, property details, location, policies, images). Ideal for rate-intelligence / price-monitoring runs: no per-review charges, far fewer requests per hotel (so fewer 429s at scale). Leave `false` for normal review scraping.

## `includeCategoryRatings` (type: `boolean`):

Fetch hotel-level aggregate fields such as overall rating, rating label, total reviews, and category ratings.

## `includePropertyDetails` (type: `boolean`):

Attach rich hotel-level descriptive fields to every review row: `propertyName`, `starRating`, `propertyType`, `propertyAddress`, `propertyCoordinates`, `propertyDescription`, `propertyLanguages`, `propertyAmenities` (categorized), and `propertyHighlights`. Extracted from the same page-layout response as the photo gallery — **no extra per-item charge**. Set `false` to omit these fields. Requires `includeCategoryRatings: true`.

## `includePropertyImages` (type: `boolean`):

Fetch every image URL in the hotel's photo gallery (typically 100-300 URLs per property) and attach them as `propertyImages` on every row. **Billed 1 `additional-cost` unit ($0.001) per image returned** — a 220-image hotel adds ~$0.22. Cap the count (and the cost) with `maxPropertyImages` below, or set `false` to skip the gallery and pay nothing for images. Requires `includeCategoryRatings: true`.

## `maxPropertyImages` (type: `integer`):

Keep only the **first N** gallery image URLs per hotel (gallery order — hero/cover shots first). You are **billed only for the images returned**, so `maxPropertyImages: 10` costs at most 10 `additional-cost` units ($0.01) per hotel instead of the full gallery's 100-300. Leave empty for the complete gallery. Only used when the photo gallery is enabled.

## `includeLocation` (type: `boolean`):

Attach a `propertyLocation` block to every review row — nearby landmarks/attractions, transit & airports, and restaurants, each with walk/drive times (e.g. "Utah State University — 18 min walk"). **Opt-in**: this fires a separate request per hotel and is **charged one `additional-cost` event ($0.001) per hotel**. Leave off if you don't need location data.

## `includePolicies` (type: `boolean`):

Attach a `propertyPolicies` block to every review row — check-in/check-out times & fees, minimum check-in age, pet policy, children & extra beds, deposits, accepted payment types, and 'You need to know' notes, grouped by section. **Opt-in**: fires a separate request per hotel and is **charged one `additional-cost` event ($0.001) per hotel**.

## `includeRooms` (type: `boolean`):

Attach a `propertyRooms` block to every row — each room type's name, bed config, occupancy, inclusions, nightly & total price, and cancellation terms. **Prices are a point-in-time snapshot** for the search window below (stamped in the output as `quotedFor`). **Opt-in**: fires a separate request per hotel and is **charged one `additional-cost` event ($0.001) per hotel**.

## `includeRoomOffers` (type: `boolean`):

Adds `mealPlans` and `cancellationOptions` to every room — each meal-plan (No extras / Half board / All-inclusive …) and cancellation-policy (Non-refundable / Fully refundable …) option with its '+ $X' price delta, exactly as shown on the Expedia room card. **Self-sufficient: enabling this alone also fetches the room rates** (you don't need to set `includeRooms` too). Premium rate-intelligence data — billed via the `additional-cost` event: the base rooms unit plus 5 units per offer option extracted.

## `roomsCheckIn` (type: `string`):

Check-in date for the room-rate quote, format YYYY-MM-DD. Only used when room rates are enabled. Leave empty to default to ~30 days from the run date.

## `roomsCheckOut` (type: `string`):

Check-out date for the room-rate quote, format YYYY-MM-DD. Only used when room rates are enabled. Leave empty to default to the night after check-in.

## `roomsAdults` (type: `integer`):

Number of adults for the room-rate quote. Only used when room rates are enabled. Defaults to 2.

## `roomsRegion` (type: `string`):

Which Expedia point-of-sale quotes the room prices — this decides the **currency** of `nightlyRate` / `totalRate` / `mealPlans` / `cancellationOptions`. **Auto (default)**: currency follows each input URL's own domain — `www.expedia.se` URLs quote in SEK, `www.expedia.co.jp` in JPY, plain `.com` (and bare hotel IDs) in USD. Pick an explicit region to force every quote into that currency regardless of URL form — e.g. bare hotel IDs + `Sweden` → SEK. All 33 regions verified live. Note: option labels (meal plans etc.) come back in that region's language; numeric `nightlyRateValue`/`totalRateValue` fields are always plain numbers.

## `roomsLanguage` (type: `string`):

Language of the room-rate **labels** (room names, meal plans, cancellation texts). **English (default)**: labels come back in English while prices keep the local currency — e.g. on `expedia.se` you get `"Breakfast buffet" + 237 kr` instead of `"Frukostbuffé" + 237 kr`. **Site default**: labels in the regional site's own language (Swedish on `.se`, German on `.de`, …). Currency is never affected by this setting — use the region selector above for that.

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

Maximum number of pages that can be processed at the same time. For large hotel lists, 3-5 reduces 429 rate-limit errors.

## `minConcurrency` (type: `integer`):

Minimum number of pages that will be processed at the same time.

## `maxRequestRetries` (type: `integer`):

Number of times the crawler will retry a failed request before giving up.

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

Specifies proxy servers that will be used by the scraper in order to hide its origin.<br><br>For details, see <a href='https://apify.com/apify/web-scraper#proxy-configuration' target='_blank' rel='noopener'>Proxy configuration</a> in README.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.expedia.co.uk/Kastoria-Hotels-Hotel-Anastassiou.h6136437.Hotel-Information",
    "https://www.expedia.com/h17369936.Hotel-Information",
    "https://www.expedia.com/Hotel.h600217.Hotel-Information",
    "https://www.expedia.com.br/Rio-De-Janeiro-Hotels-Premier-Copacabana-Hotel.h53296.Hotel-Information",
    "https://www.expedia.com.sg/things-to-do/singapore-hop-on-hop-off-bus-tour-by-open-top-bus.a203344.activity-details",
    "h6136437",
    "https://expe.app.link/KjB1EuacRSb",
    "600217",
    "https://el.hotels.com/ho434012/hotel-anastassiou-kastoria-ellada/",
    "https://www.expedia.com/La-Chapelle-En-Serval-Hotels-InterContinental-Chantilly-Chateau-Mont-Royal.h600217.Hotel-Information"
  ],
  "location": "London",
  "searchAdults": 2,
  "searchSort": "RECOMMENDED",
  "searchCurrency": "USD",
  "maxItems": 1000,
  "reviewSort": "newest",
  "includeReviewPhotos": true,
  "includeOwnerResponses": true,
  "skipReviews": false,
  "includeCategoryRatings": true,
  "includePropertyDetails": true,
  "includePropertyImages": true,
  "includeLocation": false,
  "includePolicies": false,
  "includeRooms": false,
  "includeRoomOffers": false,
  "roomsAdults": 2,
  "roomsRegion": "auto",
  "roomsLanguage": "english",
  "maxConcurrency": 10,
  "minConcurrency": 1,
  "maxRequestRetries": 100,
  "proxy": {
    "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 = {
    "startUrls": [
        "https://www.expedia.co.uk/Kastoria-Hotels-Hotel-Anastassiou.h6136437.Hotel-Information",
        "https://www.expedia.com/h17369936.Hotel-Information",
        "https://www.expedia.com/Hotel.h600217.Hotel-Information",
        "https://www.expedia.com.br/Rio-De-Janeiro-Hotels-Premier-Copacabana-Hotel.h53296.Hotel-Information",
        "https://www.expedia.com.sg/things-to-do/singapore-hop-on-hop-off-bus-tour-by-open-top-bus.a203344.activity-details",
        "h6136437",
        "https://expe.app.link/KjB1EuacRSb",
        "600217",
        "https://el.hotels.com/ho434012/hotel-anastassiou-kastoria-ellada/",
        "https://www.expedia.com/La-Chapelle-En-Serval-Hotels-InterContinental-Chantilly-Chateau-Mont-Royal.h600217.Hotel-Information"
    ],
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/expedia-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 = {
    "startUrls": [
        "https://www.expedia.co.uk/Kastoria-Hotels-Hotel-Anastassiou.h6136437.Hotel-Information",
        "https://www.expedia.com/h17369936.Hotel-Information",
        "https://www.expedia.com/Hotel.h600217.Hotel-Information",
        "https://www.expedia.com.br/Rio-De-Janeiro-Hotels-Premier-Copacabana-Hotel.h53296.Hotel-Information",
        "https://www.expedia.com.sg/things-to-do/singapore-hop-on-hop-off-bus-tour-by-open-top-bus.a203344.activity-details",
        "h6136437",
        "https://expe.app.link/KjB1EuacRSb",
        "600217",
        "https://el.hotels.com/ho434012/hotel-anastassiou-kastoria-ellada/",
        "https://www.expedia.com/La-Chapelle-En-Serval-Hotels-InterContinental-Chantilly-Chateau-Mont-Royal.h600217.Hotel-Information",
    ],
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("memo23/expedia-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 '{
  "startUrls": [
    "https://www.expedia.co.uk/Kastoria-Hotels-Hotel-Anastassiou.h6136437.Hotel-Information",
    "https://www.expedia.com/h17369936.Hotel-Information",
    "https://www.expedia.com/Hotel.h600217.Hotel-Information",
    "https://www.expedia.com.br/Rio-De-Janeiro-Hotels-Premier-Copacabana-Hotel.h53296.Hotel-Information",
    "https://www.expedia.com.sg/things-to-do/singapore-hop-on-hop-off-bus-tour-by-open-top-bus.a203344.activity-details",
    "h6136437",
    "https://expe.app.link/KjB1EuacRSb",
    "600217",
    "https://el.hotels.com/ho434012/hotel-anastassiou-kastoria-ellada/",
    "https://www.expedia.com/La-Chapelle-En-Serval-Hotels-InterContinental-Chantilly-Chateau-Mont-Royal.h600217.Hotel-Information"
  ],
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call memo23/expedia-scraper --silent --output-dataset

```

## MCP server setup

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

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Expedia.com & Hotel.com [$2.5💰] Reviews/Ratings/Sentiment A.",
        "description": "[Only $2.5💰] Expedia + Hotels.com + Things-To-Do activities at $2.50/1k — only scraper covering all 3 brands. Deeplink auto-resolution, date filter, 10+ regional TLDs, bare property IDs. 25+ fields: sentiment, traveled-with, manager responses, category ratings. JSON or CSV. Pure HTTP.",
        "version": "0.0",
        "x-build-id": "qY9ZC4WMzFELgxH9H"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/memo23~expedia-scraper/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-memo23-expedia-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/memo23~expedia-scraper/runs": {
            "post": {
                "operationId": "runs-sync-memo23-expedia-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/memo23~expedia-scraper/run-sync": {
            "post": {
                "operationId": "run-sync-memo23-expedia-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": {
                    "startUrls": {
                        "title": "Start URLs",
                        "type": "array",
                        "description": "Hotel pages, activity (Things-To-Do) pages, Hotels.com pages, **or bare Expedia property IDs**. Expedia and Hotels.com URLs may be mixed in the same run.\n\n**Expedia hotels** — long slug (`.../City-Hotels-Name.h600217.Hotel-Information`), minimal slug (`.../Hotel.h600217.Hotel-Information`), short path (`.../h600217.Hotel-Information`), all regional TLDs (`expedia.co.uk`, `expedia.com.br`, `expedia.de`, `expedia.com.sg`, …), and deeplinks (`expe.app.link/...`, resolved automatically). Bare ids (digits or `h600217`) expand to `https://www.expedia.com/Hotel.h{id}.Hotel-Information`.\n\n**Expedia activities (Things-To-Do)** — `https://www.expedia.com/things-to-do/{slug}.a{id}.activity-details` (and regional equivalents). Returns activity reviews under `provider: \"expedia-activity\"`.\n\n**Hotels.com** — `https://{locale}.hotels.com/ho{id}/{slug}/`.\n\n**Not supported:** destination/region landing pages (`.../Name.d{id}.Place-To-Visit`) — those have no reviews of their own. If supplied, the run produces one explanatory row per such URL and skips the fetch entirely (no charges burned on dead URLs).",
                        "items": {
                            "type": "string"
                        }
                    },
                    "location": {
                        "title": "Destination (city)",
                        "type": "string",
                        "description": "City or destination to search, e.g. London, Tokyo, New York. Leave empty to use Option A (URLs) instead."
                    },
                    "searchCheckIn": {
                        "title": "Search: check-in date",
                        "type": "string",
                        "description": "Check-in date for the destination search (YYYY-MM-DD). Prices are a snapshot for this window. Leave empty to default to ~30 days from the run date. Only used in Option B."
                    },
                    "searchCheckOut": {
                        "title": "Search: check-out date",
                        "type": "string",
                        "description": "Check-out date for the destination search (YYYY-MM-DD). Leave empty to default to the night after check-in. Only used in Option B."
                    },
                    "searchAdults": {
                        "title": "Search: adults",
                        "minimum": 1,
                        "type": "integer",
                        "description": "Number of adults per room for the search price quote. Defaults to 2. Only used in Option B.",
                        "default": 2
                    },
                    "searchSort": {
                        "title": "Search: sort order",
                        "enum": [
                            "RECOMMENDED",
                            "PRICE_LOW_TO_HIGH",
                            "PRICE_HIGH_TO_LOW",
                            "REVIEW",
                            "DISTANCE",
                            "PROPERTY_CLASS"
                        ],
                        "type": "string",
                        "description": "How to sort the destination search results.",
                        "default": "RECOMMENDED"
                    },
                    "searchCurrency": {
                        "title": "Search: currency",
                        "type": "string",
                        "description": "ISO 4217 currency code for search prices, e.g. USD, EUR, GBP, THB. Defaults to USD. Only used in Option B.",
                        "default": "USD"
                    },
                    "searchRegionId": {
                        "title": "Search: Expedia regionId (advanced)",
                        "type": "string",
                        "description": "Power-user override: skip city→regionId resolution and search this Expedia regionId directly (e.g. 6195474 for London City Centre). Leave empty to resolve from the Destination field above."
                    },
                    "maxItems": {
                        "title": "Maximum number of items OR limit the results per crawl",
                        "type": "integer",
                        "description": "Maximum number of items that will be scraped.",
                        "default": 1000
                    },
                    "googleMapsUrlNote": {
                        "title": "Google Maps URL / Place ID input",
                        "type": "string",
                        "description": "**You can paste Google Maps URLs and place IDs directly into `Start URLs` above.** The actor recognizes them, parses the hotel name from the URL slug (e.g. `Hotel+Anastassiou+Kastoria`), and emits a row with the parsed name plus an Expedia hotel-search URL the buyer can use to find the canonical Expedia listing. **Full auto-resolution (Google Maps → Expedia hotel URL → reviews) is coming in v1.1** — for now this surfaces the hotel name + a search shortcut. Paste an actual Expedia hotel URL alongside if you want reviews fetched in the same run."
                    },
                    "reviewsFrom": {
                        "title": "Only scrape reviews newer than [date]",
                        "type": "string",
                        "description": "Format should be YYYY-MM-DD, e.g., 2025-02-20"
                    },
                    "reviewSort": {
                        "title": "Sort order for reviews",
                        "enum": [
                            "newest",
                            "relevant",
                            "highest",
                            "lowest",
                            "mostHelpful"
                        ],
                        "type": "string",
                        "description": "Order in which to fetch reviews. **All five values are fully wired** via Expedia's GraphQL `sortBy` URN — buyers get the order they ask for. `mostHelpful` maps to Expedia's `Most relevant` URN (Expedia has no separate helpfulness dimension; relevance already accounts for helpful-vote weighting). The resolved URN is printed at run start so you can verify what's being sent.",
                        "default": "newest"
                    },
                    "includeReviewPhotos": {
                        "title": "Include review photos",
                        "type": "boolean",
                        "description": "When `false`, the actor strips the `reviewPhotos` array from each emitted row. Useful for CSV exports where flat structure matters, or to make rows smaller. Default `true`.",
                        "default": true
                    },
                    "includeOwnerResponses": {
                        "title": "Include hotel manager responses",
                        "type": "boolean",
                        "description": "When `false`, the actor strips the `hotelResponse` object from each emitted row. Set `false` when you only want guest-side review data. Default `true`.",
                        "default": true
                    },
                    "skipReviews": {
                        "title": "Rooms-only mode (skip reviews)",
                        "type": "boolean",
                        "description": "When `true`, the actor does **not** scrape reviews — it emits a single summary row per hotel carrying whatever hotel-level data you enabled (room rates, meal-plan/cancellation pricing, property details, location, policies, images). Ideal for rate-intelligence / price-monitoring runs: no per-review charges, far fewer requests per hotel (so fewer 429s at scale). Leave `false` for normal review scraping.",
                        "default": false
                    },
                    "includeCategoryRatings": {
                        "title": "Include hotel category ratings",
                        "type": "boolean",
                        "description": "Fetch hotel-level aggregate fields such as overall rating, rating label, total reviews, and category ratings.",
                        "default": true
                    },
                    "includePropertyDetails": {
                        "title": "Include property description & amenities",
                        "type": "boolean",
                        "description": "Attach rich hotel-level descriptive fields to every review row: `propertyName`, `starRating`, `propertyType`, `propertyAddress`, `propertyCoordinates`, `propertyDescription`, `propertyLanguages`, `propertyAmenities` (categorized), and `propertyHighlights`. Extracted from the same page-layout response as the photo gallery — **no extra per-item charge**. Set `false` to omit these fields. Requires `includeCategoryRatings: true`.",
                        "default": true
                    },
                    "includePropertyImages": {
                        "title": "Include property photo gallery",
                        "type": "boolean",
                        "description": "Fetch every image URL in the hotel's photo gallery (typically 100-300 URLs per property) and attach them as `propertyImages` on every row. **Billed 1 `additional-cost` unit ($0.001) per image returned** — a 220-image hotel adds ~$0.22. Cap the count (and the cost) with `maxPropertyImages` below, or set `false` to skip the gallery and pay nothing for images. Requires `includeCategoryRatings: true`.",
                        "default": true
                    },
                    "maxPropertyImages": {
                        "title": "Max property images per hotel",
                        "minimum": 1,
                        "type": "integer",
                        "description": "Keep only the **first N** gallery image URLs per hotel (gallery order — hero/cover shots first). You are **billed only for the images returned**, so `maxPropertyImages: 10` costs at most 10 `additional-cost` units ($0.01) per hotel instead of the full gallery's 100-300. Leave empty for the complete gallery. Only used when the photo gallery is enabled."
                    },
                    "includeLocation": {
                        "title": "Include location intelligence (nearby places)",
                        "type": "boolean",
                        "description": "Attach a `propertyLocation` block to every review row — nearby landmarks/attractions, transit & airports, and restaurants, each with walk/drive times (e.g. \"Utah State University — 18 min walk\"). **Opt-in**: this fires a separate request per hotel and is **charged one `additional-cost` event ($0.001) per hotel**. Leave off if you don't need location data.",
                        "default": false
                    },
                    "includePolicies": {
                        "title": "Include policies & important info",
                        "type": "boolean",
                        "description": "Attach a `propertyPolicies` block to every review row — check-in/check-out times & fees, minimum check-in age, pet policy, children & extra beds, deposits, accepted payment types, and 'You need to know' notes, grouped by section. **Opt-in**: fires a separate request per hotel and is **charged one `additional-cost` event ($0.001) per hotel**.",
                        "default": false
                    },
                    "includeRooms": {
                        "title": "Include room inventory & rates",
                        "type": "boolean",
                        "description": "Attach a `propertyRooms` block to every row — each room type's name, bed config, occupancy, inclusions, nightly & total price, and cancellation terms. **Prices are a point-in-time snapshot** for the search window below (stamped in the output as `quotedFor`). **Opt-in**: fires a separate request per hotel and is **charged one `additional-cost` event ($0.001) per hotel**.",
                        "default": false
                    },
                    "includeRoomOffers": {
                        "title": "Include meal-plan & cancellation pricing",
                        "type": "boolean",
                        "description": "Adds `mealPlans` and `cancellationOptions` to every room — each meal-plan (No extras / Half board / All-inclusive …) and cancellation-policy (Non-refundable / Fully refundable …) option with its '+ $X' price delta, exactly as shown on the Expedia room card. **Self-sufficient: enabling this alone also fetches the room rates** (you don't need to set `includeRooms` too). Premium rate-intelligence data — billed via the `additional-cost` event: the base rooms unit plus 5 units per offer option extracted.",
                        "default": false
                    },
                    "roomsCheckIn": {
                        "title": "Room rates: check-in date",
                        "type": "string",
                        "description": "Check-in date for the room-rate quote, format YYYY-MM-DD. Only used when room rates are enabled. Leave empty to default to ~30 days from the run date."
                    },
                    "roomsCheckOut": {
                        "title": "Room rates: check-out date",
                        "type": "string",
                        "description": "Check-out date for the room-rate quote, format YYYY-MM-DD. Only used when room rates are enabled. Leave empty to default to the night after check-in."
                    },
                    "roomsAdults": {
                        "title": "Room rates: number of adults",
                        "type": "integer",
                        "description": "Number of adults for the room-rate quote. Only used when room rates are enabled. Defaults to 2.",
                        "default": 2
                    },
                    "roomsRegion": {
                        "title": "Room rates: region & currency",
                        "enum": [
                            "auto",
                            "us",
                            "ca",
                            "mx",
                            "br",
                            "ar",
                            "uk",
                            "ie",
                            "de",
                            "fr",
                            "es",
                            "it",
                            "nl",
                            "be",
                            "at",
                            "ch",
                            "dk",
                            "fi",
                            "gr",
                            "no",
                            "se",
                            "au",
                            "nz",
                            "jp",
                            "kr",
                            "in",
                            "sg",
                            "hk",
                            "tw",
                            "my",
                            "th",
                            "ph",
                            "vn",
                            "id"
                        ],
                        "type": "string",
                        "description": "Which Expedia point-of-sale quotes the room prices — this decides the **currency** of `nightlyRate` / `totalRate` / `mealPlans` / `cancellationOptions`. **Auto (default)**: currency follows each input URL's own domain — `www.expedia.se` URLs quote in SEK, `www.expedia.co.jp` in JPY, plain `.com` (and bare hotel IDs) in USD. Pick an explicit region to force every quote into that currency regardless of URL form — e.g. bare hotel IDs + `Sweden` → SEK. All 33 regions verified live. Note: option labels (meal plans etc.) come back in that region's language; numeric `nightlyRateValue`/`totalRateValue` fields are always plain numbers.",
                        "default": "auto"
                    },
                    "roomsLanguage": {
                        "title": "Room rates: label language",
                        "enum": [
                            "english",
                            "site-default"
                        ],
                        "type": "string",
                        "description": "Language of the room-rate **labels** (room names, meal plans, cancellation texts). **English (default)**: labels come back in English while prices keep the local currency — e.g. on `expedia.se` you get `\"Breakfast buffet\" + 237 kr` instead of `\"Frukostbuffé\" + 237 kr`. **Site default**: labels in the regional site's own language (Swedish on `.se`, German on `.de`, …). Currency is never affected by this setting — use the region selector above for that.",
                        "default": "english"
                    },
                    "maxConcurrency": {
                        "title": "Max Concurrency",
                        "type": "integer",
                        "description": "Maximum number of pages that can be processed at the same time. For large hotel lists, 3-5 reduces 429 rate-limit errors.",
                        "default": 10
                    },
                    "minConcurrency": {
                        "title": "Min Concurrency",
                        "type": "integer",
                        "description": "Minimum number of pages that will be processed at the same time.",
                        "default": 1
                    },
                    "maxRequestRetries": {
                        "title": "Max Request Retries",
                        "type": "integer",
                        "description": "Number of times the crawler will retry a failed request before giving up.",
                        "default": 100
                    },
                    "proxy": {
                        "title": "Proxy configuration",
                        "type": "object",
                        "description": "Specifies proxy servers that will be used by the scraper in order to hide its origin.<br><br>For details, see <a href='https://apify.com/apify/web-scraper#proxy-configuration' target='_blank' rel='noopener'>Proxy configuration</a> in README.",
                        "default": {
                            "useApifyProxy": true,
                            "apifyProxyGroups": [
                                "RESIDENTIAL"
                            ]
                        }
                    }
                }
            },
            "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
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
