# Expedia Hotel Prices (`superslowsloth/expedia-hotels`) Actor

One search, one flat table: hotel name, id, neighbourhood, all-in price for the stay, nightly rate, guest score and review count, and the Expedia URL.

- **URL**: https://apify.com/superslowsloth/expedia-hotels.md
- **Developed by:** [Superslow Sloth](https://apify.com/superslowsloth) (community)
- **Categories:** Travel, AI, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.75 / 1,000 hotel scrapeds

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

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

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

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

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

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

# README

## Expedia Hotel Prices

One search in, one flat table out. Give it a destination and a date; it returns
the hotels Expedia would show you, each with its **all-in price for the whole
stay**, Expedia's own **nightly rate**, the guest score and review count, the
neighbourhood, and the URL of the property page.

Built to be called by an agent or a script: small input, small output, one row
per hotel, no nested objects, no HTML.

Field names match the **Booking.com Hotel Prices** actor wherever the two sites
publish the same measurement, so you can run both for one stay and join the rows
on `name` or compare `total_price` directly.

### Input

| field | default | meaning |
|---|---|---|
| `destination` | *required* | City, region, district or landmark: `Bangkok`, `Osaka`, `Bali`, `Times Square` |
| `checkIn` | 30 days out | Check-in date, `YYYY-MM-DD` |
| `nights` | `1` | Length of stay; check-out is derived from it |
| `adults` | `2` | Adult guests per room |
| `rooms` | `1` | Rooms requested |
| `currency` | `USD` | Which Expedia point of sale to search — see below |
| `maxResults` | `50` | Stop after this many hotels have been **delivered**; 100 is the ceiling |
| `includeUnpriced` | `false` | Also emit properties with nothing bookable, at a `null` price |

### Output

One item per hotel:

| field | meaning |
|---|---|
| `hotel_id` | Expedia's numeric property id, stable across searches |
| `name` | Property name as Expedia displays it |
| `url` | Property page on Expedia, carrying your dates and nothing else |
| `area` | Neighbourhood as Expedia labels the card, e.g. `Silom` |
| `star_rating` | Official star rating — **always `null` today**, see below |
| `review_score` | Expedia guest score **out of 10** |
| `review_count` | Number of guest reviews behind that score |
| `total_price` | All-in price for the **whole stay** |
| `price_per_night` | Expedia's own nightly figure, never divided out of the total |
| `lead_price` | The headline number on Expedia's card, exactly as shown |
| `lead_price_basis` | `TOTAL` or `NIGHTLY` — which of the two `lead_price` is |
| `currency` | Currency the prices are quoted in |
| `check_in`, `check_out`, `nights`, `adults`, `rooms` | The search the prices belong to |
| `requested_destination` | The destination string you sent, echoed back |
| `resolved_destination`, `resolved_region_id` | The region Expedia actually searched |
| `destination_matched` | `false` when Expedia searched somewhere other than what you asked for |

#### Currency picks the Expedia site

Expedia sets the currency per **point of sale**, not per request — asking
expedia.com for JPY returns USD, and no cookie or query parameter changes that.
So `currency` here chooses which Expedia site is searched:

`USD` expedia.com · `GBP` .co.uk · `EUR` .ie · `CAD` .ca · `AUD` .com.au ·
`NZD` .co.nz · `SGD` .com.sg · `MYR` .com.my · `THB` .co.th · `HKD` .com.hk ·
`INR` .co.in · `JPY` .co.jp · `PHP` .com.ph

Anything outside that list **stops the run** instead of quietly returning USD: a
price column in a currency nobody asked for is worse than no run at all. Property
names and price labels are requested in English on every one of those sites.

Note that inventory and price differ genuinely between points of sale — the same
hotel is not guaranteed to cost the converted-equivalent on two of them.

#### Two prices, and which is which

Expedia's card leads with a different number depending on the site: expedia.com
shows the **total for the stay** and expedia.co.th shows the **nightly rate**.
Nothing in the payload types the difference, so this actor reads the companion
message Expedia prints beside it — "`$147 nightly`" next to a total,
"`฿16,986 total`" next to a nightly rate — and reports the answer in
`lead_price_basis`.

- When the basis is known, `total_price` and `price_per_night` are both filled.
- When it is not — a few points of sale write "for 3 nights, 1 room" with no
  figure — `lead_price` is still there and **both** resolved fields are `null`.

`price_per_night` is never computed by dividing the total by `nights`, because a
stay whose rate changes from night to night has no single nightly price and
inventing one would be wrong for exactly the bookings people care about.

#### `star_rating` is always null right now

Expedia's search cards stopped carrying an official star rating. The field is
still declared in their schema and answered `null` on 100% of the rows measured
on 2026-08-25, and the older selection that used to carry it returns an empty
list on every point of sale tested. The column is kept — it is part of the
vocabulary shared with the Booking.com actor, and Expedia may start filling it
again — but it is never populated with a guess derived from the review score.
A `null` here means "Expedia did not say", not "unrated".

#### Always check `destination_matched`

Expedia resolves free text on its own side and answers nearly any string with
*some* region, so a typo or a bad input can produce a complete, successful,
**charged** run about somewhere else. This actor cannot refuse that outright
without also refusing legitimate variants — Expedia answers `Bangkok` with
`Bangkok, Thailand (BKK-All Airports)`. So every row carries what you asked for,
what Expedia searched, and a boolean saying whether they agree. Check it before
you trust the prices.

A string Expedia cannot place at all comes back as a failed run with no rows and
no per-hotel charges, not as an empty success.

#### 50-100 hotels per search, and why the number moves

Expedia serves 50 properties per page and only **two** distinct pages per
search — every offset above the first page returns the same second page — so 100
is the hard ceiling. Ask for more and the run ends early rather than charging you
for repeats.

The second page is also session-bound: Expedia serves it only to a connection
that has already asked for the first. When the exit address is rotated mid-walk
because of a rate limit, that session is gone, and the actor rebuilds it before
retrying. That usually works and sometimes does not, so a search asking for 90
can legitimately deliver 46 or 50. You are charged for rows delivered, never for
the repeats, and the run log says how many pages it reached.

To go wider, search narrower — a district instead of a city, or several dates.

### Proxy

Residential is required in practice, and it is not optional the way it is on
gentler sites. Measured on 2026-08-25: from an ordinary connection the API
answers cold, but from a shared proxy pool a bare request was answered **HTTP
429** on six consecutive exit addresses. So the actor opens by loading the search
page like a browser would, carries those cookies through the walk, and on a
refusal backs off with jitter, takes a new address, and rebuilds the session
before trying again.

Expedia also occasionally serves an interstitial as **HTTP 200**. That is treated
as "this address is refused" rather than "no results" — an empty dataset is never
reported as a successful empty search.

When the pool is contended a run can still exhaust all six of its addresses and
finish with **no rows**; the log says `HTTP 429` and no per-hotel charge is made.
Non-US points of sale are the ones that see it. Re-running usually clears it
within a minute — the same Osaka search that came back empty returned 47 rows on
the next attempt.

### Billing

Pay per event: a small fixed charge when the run starts — taken only **after**
the input parses, so a run rejected for a bad date or an unsupported currency is
never billed — and one charge per hotel row delivered. Properties skipped for
having no price are not charged for.

# Actor input Schema

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

A city, region, district or landmark such as Bangkok, Osaka, Bali or Times Square. Expedia resolves the text on its own side and answers almost any string with some region, so a typo can return a real but unrelated destination. Every output row carries requested\_destination, resolved\_destination and destination\_matched so you can tell the difference.

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

Check-in date as YYYY-MM-DD. Defaults to 30 days from the day the run starts.

## `nights` (type: `integer`):

Length of stay in nights. The check-out date is derived from it.

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

Number of adult guests per room. Prices are quoted for this occupancy.

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

Number of rooms requested. Expedia prices the same party once per room, so the total scales with this.

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

Three-letter currency code the prices are quoted in. Expedia sets currency per point of sale rather than per request, so this also picks which Expedia site is searched: USD, GBP, EUR, CAD, AUD, NZD, SGD, MYR, THB, HKD, INR, JPY, PHP. Anything else stops the run instead of quietly returning USD. Property names and price labels are requested in English on every one of them.

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

Stop after this many hotels have been delivered. Properties with no price for those dates do not count against it. Expedia serves 50 per page and only two distinct pages per search, so 100 is the real ceiling for one destination and one set of dates - ask for more and the run simply ends early rather than charging you for repeats.

## `includeUnpriced` (type: `boolean`):

Off by default. When on, properties with nothing bookable for those dates are emitted too, with a null price - they are never given a price of 0.

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

Required in practice, and residential is what works. Expedia rate limits per exit address and answers a throttled one with HTTP 429, or occasionally with an HTML interstitial served as HTTP 200; both are cleared by a different address, which the actor rotates to automatically.

## Actor input object example

```json
{
  "destination": "Bangkok",
  "nights": 1,
  "adults": 2,
  "rooms": 1,
  "currency": "USD",
  "maxResults": 50,
  "includeUnpriced": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `hotels` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "destination": "Bangkok",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

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

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

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

```

## Python example

```python
from apify_client import ApifyClient

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

# Prepare the Actor input
run_input = {
    "destination": "Bangkok",
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("superslowsloth/expedia-hotels").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "destination": "Bangkok",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call superslowsloth/expedia-hotels --silent --output-dataset

```

## MCP server setup

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

```

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

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/4coTPJJo5hUMlp6UQ/builds/TFwHzZOo5DBUlFtzs/openapi.json
