# Hotel Rates, Award Points & OTA Parity — Marriott, Hilton, IHG (`mu0i/hotel-rates-award-points`) Actor

Live room rates, member rates and award points from 11 hotel chains' own booking systems (Marriott Bonvoy, Hilton Honors, World of Hyatt, IHG One Rewards, Choice, Radisson, Wyndham and more), plus the same hotel's price on Booking.com, Expedia, Hotels.com, Trip.com and Agoda: rate parity in one run.

- **URL**: https://apify.com/mu0i/hotel-rates-award-points.md
- **Developed by:** [Mu0i](https://apify.com/mu0i) (community)
- **Categories:** Travel, Developer tools, AI
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 api credit units

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## What's an Apify Actor?

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

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

## How to integrate an Actor?

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

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

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

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

# README

**Hotel rates and award points from the chains themselves — and the same hotel's price on the OTAs, side by side.** Marriott, Hilton, Hyatt, IHG, Choice, Radisson, Wyndham, Best Western, Meliá, I Prefer and Premier Inn, read from each chain's own booking system at the moment you press Start: every room and rate plan, the member rate, what the night costs in points, and which free-night certificate would cover it. Then Booking.com, Expedia, Hotels.com, Trip.com and Agoda for the same property, compared against the direct rate — rate parity in one run.

The chain data is **brand-direct**: the numbers the chain's own app and site quote, including member rates and award nights no OTA page shows. The OTA data is there for one reason — to put beside it, so you can see where an OTA undercuts the hotel and by how much.

Output is one flat row per rate plan, per hotel, per night or per channel — ready for CSV, Excel, Google Sheets, or the Apify API.

### What you get

| | |
|---|---|
| **Cash rates** | Every room × rate plan for the stay, nightly and stay total, taxes and fees itemised where the chain states them |
| **Member rates** | The loyalty-member price, kept separate from the public rate — never silently mixed into it |
| **Award points** | Points per night and per stay, points+cash options, any cash co-pay, and **cents-per-point** against the cash rate for the same stay |
| **Free-night certificates** | Which certificate covers the night and the top-up it needs — Marriott 35k/40k/50k/85k, Hilton free night, IHG 40k |
| **Rate parity** | The direct rate beside Booking.com, Expedia, Hotels.com, Trip.com and Agoda for the same hotel: the gap per channel, the cheapest channel, the deepest undercut |
| **Stay vs nights** | A multi-night stay sold as one against each night booked alone, and whether a split booking is even possible |
| **Comparable prices** | Chains publish on different sides of tax. Every row carries the chain's native figure *and* an after-tax figure, so a cross-chain or chain-vs-OTA comparison is apples to apples |
| **Availability** | `sold_out`, `closed` and `restricted_los` come back as answers, not as empty rows |
| **Freshness** | `source` and `cache_age_sec` on every row, so you always know how fresh a number was |

### Eight modes

| Mode | One call does | Use it for |
|---|---|---|
| **Rates** | one hotel × one stay → every room × rate plan | the full rate matrix, member rates included |
| **Award** | one hotel × one stay → points, points+cash, cents-per-point, certificates | "should I pay cash, points or a certificate?" |
| **Calendar** | one hotel × up to 60 consecutive check-in dates → lowest per night | finding the cheap dates |
| **Comp set** | up to 25 hotels × one stay → lowest per hotel | your hotel against its competitors |
| **Comp set (award)** | up to 25 hotels × one stay → lowest points + cents-per-point | award sweet spots across a market |
| **Parity** | one hotel × one stay → direct rate vs up to five OTAs | where an OTA undercuts you, and by how much |
| **Stay** | one hotel × a 2–14 night stay → the stay as one vs its nights alone | whether to book the stay or night by night |
| **Find hotel codes** | a city or a map point → the chain's own property codes | the first run, when you don't know the codes yet |

### How to find a chain's hotel codes

Every other mode takes the chain's **own property code** — Marriott `CHIRL`, Hyatt `CHIZP`, IHG `CHIMM`,
Hilton `CHITDHX`, Choice `IL263`. Those codes are not guessable, so **Find hotel codes** mode exists to
produce them: give it a city, an address or a landmark and it returns the chain's hotels around that point
with their codes, nearest first.

```json
{ "mode": "find_hotels", "chain": "marriott", "place": "Chicago, IL", "radius": 5 }
```

One run, one credit ($0.012 — Best Western $0.060, Wyndham $0.072), and you get the whole list however
long it is: 35 Marriott codes for Chicago within 5 miles, measured 2026-10-05. Rows carry the code, a
distance from your search point, and name, brand, address and coordinates where the chain's own search
publishes them (most do; Marriott's returns codes and distances only, so run **Rates** on a code to see
which property it is). You can also search around a point with `lat` + `lng` instead of a place name.

Then paste the codes you want into **Hotel codes** and run any of the other modes.

### Rate parity: the direct rate against the OTAs

Give **Parity** a hotel chain and a code, and it shops the chain's own system and each OTA for the same
property, for the same stay, at the same moment. The OTA listing is found for you — the Actor keeps an id
map from the chain's code to each OTA's hotel id, filled from Google Hotels' own cross-source match when a
hotel is seen for the first time — so you never paste a Booking.com URL. One run on a Choice hotel in
Chicago, measured 2026-10-11:

| Channel | Public / night (after tax) | vs direct | |
|---|---|---|---|
| **direct** (Choice) | 118.70 | — | member 106.83, 20,000 points |
| Booking.com | 119.76 | +0.89 % | |
| **Expedia** | **97.00** | **−18.28 %** | |
| Hotels.com | 97.00 | −18.28 % | |
| Agoda | 119.00 | +0.25 % | |
| Trip.com | — | — | no listing matched |

Every run returns the block above as rows: the direct side first, then one row per channel with
`vs_direct_pct` and `cheaper`, and on every row the verdict (`ota_undercuts` here), the cheapest channel
and the deepest undercut. The comparison is per night **after tax** — Booking.com's all-in price, Expedia's
tax-inclusive one — against the chain's after-tax figure, and public rates only: Genius and Member Price
are reported in their own column, as the chain's member rate is, never mixed into the gap. A channel in a
different currency is shown but not compared, and the row says so.

Schedule it and the dataset becomes your parity log: filter `cheaper = channel` for every undercut, by
date, by channel.

```json
{ "mode": "parity", "chain": "choice", "hotelCodes": ["IL263"], "checkin": "2026-11-10", "los": 1 }
```

Pick fewer channels with `channels` (`["booking", "expedia"]`); each channel shopped is one Rates call on
that OTA, and a channel with no listing for the hotel costs nothing.

#### The OTAs as sources

The five OTAs are also available as a **chain** of their own for Rates, Calendar and Comp set — every
room and rate plan an OTA lists for a property, with the OTA's own tax-inclusive price. The hotel code is
the OTA's hotel id: the number in its URL, or the `channel_id` a Parity run returns. They carry no award
points and no hotel-code search, which is what Parity and the chain modes are for.

### Stay or nights?

A three-night stay is sold at one rate, but each of its nights also has a rate of its own — and a hotel
with a minimum stay on Saturday will not sell Friday alone. **Stay** mode shops both at once: the stay as
one (`row = stay`, with the totals), then each night priced alone (`row = night`, with `min_los`), and the
split verdict — bookable night by night or not, which is cheaper, and the saving.

```json
{ "mode": "stay", "chain": "hilton", "hotelCodes": ["CHITDHX"], "checkin": "2026-11-10", "los": 3 }
```

### Track an award price over time

Award pricing is dynamic at Marriott, Hilton and IHG — the points cost of the same room moves by the
night and by the day you ask. One Calendar run on Marriott `CHIRL`, measured 2026-10-05, priced seven
consecutive nights like this:

| Night | Cash (public) | Points |
|---|---|---|
| 2026-11-10 | 459 | 56,000 |
| 2026-11-11 | 459 | 56,000 |
| 2026-11-12 | 271 | 53,000 |
| 2026-11-13 | 223 | 49,000 |
| 2026-11-14 | 223 | 50,000 |
| 2026-11-15 | 271 | **43,000** |
| 2026-11-16 | 459 | 56,000 |

So to watch a redemption you care about:

1. Run **Award** mode for the hotel and date you are tracking, or **Calendar** mode to see the whole
   window at once.
2. Put the Actor on a schedule — every run appends to the same dataset, so the dataset becomes the price
   history.
3. Compare `points_per_night` and `cpp` between runs. `cpp` is the cents-per-point value of the
   redemption against the cash rate for that same stay, so it answers *cash or points* directly: in the
   run above, 56,000 points against a $535 after-tax night is 0.96 cents per point.

#### Free-night certificates

An Award row also says which **free-night certificate** would cover the night, cheapest first, with the
top-up it needs: `certs` = `marriott_50k +6000; marriott_85k` means a 50k certificate plus 6,000 points,
or an 85k certificate alone. The rules are the programs' published terms — Marriott's 35k/40k/50k/85k
awards with up to 15,000 points of top-up, Hilton's free night on any standard reward night, IHG's 40k
anniversary night with unlimited top-up. Hyatt certificates are by category, which the chain's pricing does
not state, so they are not guessed.

Every run is live at request time (or a cache a few minutes old, which the row tells you), not a nightly
snapshot — that is the point of reading the chain's own system rather than a reseller's page.

### Which hotel chains and OTAs are supported

Eleven chains, each on its own brand-direct system, and five OTAs.

| Chain | Loyalty program | Public rate | Member rate | Award points |
|---|---|---|---|---|
| Marriott | Marriott Bonvoy | ✓ | ✓ | ✓ |
| Hilton | Hilton Honors | ✓ | ✓ | ✓ |
| Hyatt | World of Hyatt | ✓ | ✓ | ✓ |
| IHG | IHG One Rewards | ✓ | ✓ | ✓ |
| Choice | Choice Privileges | ✓ | ✓ | ✓ |
| Radisson | Radisson Rewards | ✓ | ✓ | ✓ |
| I Prefer | I Prefer (Preferred Hotels) | ✓ | ✓ | ✓ |
| Wyndham | Wyndham Rewards | ✓ | ✓ | — |
| Meliá | Meliá Rewards | ✓ | ✓ | — |
| Best Western | Best Western Rewards | ✓ | ✓ | ✓ |
| Premier Inn (UK) | — | ✓ | n/a | n/a |

Points are login-gated at the source on Meliá and Wyndham, and Premier Inn is cash-only. Those columns
come back empty rather than estimated.

**Wyndham rows name rooms and rate plans by code only** — `room_code` (e.g. `ND1`) and `rate_code`
(e.g. `SWR2P`), with `room_name` and `rate_name` empty. That is how Wyndham's own booking system
answers an anonymous shop: the summary payload carries codes, not names. The prices, the member
flag, the tax figures and the availability are complete; only the two name columns are empty, on
every Wyndham row, by design. Rate codes are stable per property, so map them once in your sheet.

| OTA | As a Parity channel | As a source (Rates, Calendar, Comp set) | Member price reported apart |
|---|---|---|---|
| Booking.com | ✓ | ✓ | Genius |
| Expedia | ✓ | ✓ | Member Price |
| Hotels.com | ✓ | ✓ | Member Price |
| Trip.com | ✓ | ✓ | — |
| Agoda | ✓ | ✓ | Member deal |

### How to use it

1. Pick a **mode** — start with *Rates*. (Don't know any hotel codes? Start with *Find hotel codes* and a
   city name instead — see above.)
2. Pick a **chain**.
3. Put one or more **hotel codes** in, one per line. These are the chain's own property codes: Marriott `CHIRL`, Hyatt `CHIZP`, IHG `CHIMM`, Hilton `CHITDHX`. The input is pre-filled with a working example, so you can press **Start** straight away.
4. Set a **check-in date** and **nights**, or leave the date empty for 30 days out.
5. Press **Start**. Results land in the dataset as flat rows; export as CSV, Excel, JSON or XML, or pull them with the Apify API.

Prefer to do it over HTTP? The same search is `GET /v1/hotels?chain=…&q=…` on the API — see the
[API reference](https://directrate.dev/docs).

### Use it from your own stack

- **Schedule it.** Every run appends to the same dataset, so a daily schedule turns the dataset into a
  price history — or a parity log.
- **Call it from the Apify API** or the JavaScript/Python clients, and export the dataset as JSON, CSV,
  Excel or XML.
- **Hook it into what you already use** — webhooks fire when a run finishes, and the usual integrations
  (Make, Zapier, Slack, Google Drive) are a few clicks away.
- **Or skip the Actor entirely**: the same data is a REST API with an OpenAPI spec —
  [directrate.dev/docs](https://directrate.dev/docs). Sign in at [directrate.dev](https://directrate.dev)
  for a key of your own: 250 free credits a month, no card, and a playground to try every call.

### Input example

Rates for one Marriott hotel, two nights:

```json
{
  "mode": "rates",
  "chain": "marriott",
  "hotelCodes": ["CHIRL"],
  "checkin": "2026-11-10",
  "los": 2,
  "adults": 2,
  "currency": "USD"
}
```

A comp set — up to 25 hotels answered in one call:

```json
{
  "mode": "compset",
  "chain": "choice",
  "hotelCodes": ["IL263", "IL264", "IL265"],
  "checkin": "2026-11-10",
  "los": 1,
  "compsetRates": "lowest"
}
```

Award points, cents-per-point and certificates:

```json
{ "mode": "award", "chain": "hyatt", "hotelCodes": ["CHIZP"], "checkin": "2026-11-10", "los": 2 }
```

A 30-night calendar for one hotel:

```json
{ "mode": "calendar", "chain": "ihg", "hotelCodes": ["CHIMM"], "checkin": "2026-11-10", "calendarDays": 30 }
```

Parity against two channels only:

```json
{ "mode": "parity", "chain": "marriott", "hotelCodes": ["CHIRL"], "checkin": "2026-11-10", "channels": ["booking", "expedia"] }
```

Rates straight from an OTA, by its own hotel id:

```json
{ "mode": "rates", "chain": "expedia", "hotelCodes": ["553946"], "checkin": "2026-11-10", "los": 1 }
```

### Output example

One row per rate plan (Rates mode):

```json
{
  "chain": "hyatt",
  "hotel_code": "CHIZP",
  "hotel_name": "Hyatt Place Chicago/Downtown-The Loop",
  "checkin": "2026-11-10",
  "checkout": "2026-11-12",
  "los": 2,
  "adults": 2, "children": 0, "rooms": 1,
  "status": "available",
  "room_name": "1 King, City View",
  "rate_name": "Standard Rate", "rate_type": "bar", "member_only": false,
  "refundable": true, "meal_plan": "room_only",
  "price_per_night": 289.00,
  "price_per_night_after_tax": 330.83,
  "price_total": 578.00,
  "taxes_fees_total": 83.66,
  "tax_included": false,
  "currency": "USD", "rooms_left": 4,
  "lowest_bar": 289.00, "lowest_member": 260.10,
  "lowest_bar_after_tax": 330.83, "lowest_member_after_tax": 297.75,
  "source": "live", "cache_age_sec": 0, "shopped_at": "2026-11-08T02:31:07Z"
}
```

Award mode adds `points_per_night`, `points_total`, `cash_copay`, `award_type`, `cpp`, the certificates and the cash rate it was compared against:

```json
{
  "chain": "marriott", "hotel_code": "CHIRL", "checkin": "2026-11-10", "los": 1,
  "status": "available", "award_type": "standard",
  "points_per_night": 56000, "points_total": 56000, "cash_copay": null, "taxes_fees": 3.78,
  "lowest_points": 56000, "cpp": 1.02,
  "certs": "marriott_50k +6000; marriott_85k", "cert_cheapest": "marriott_50k", "cert_top_up": 6000,
  "lowest_cash": 479.00, "lowest_cash_after_tax": 569.54, "currency": "USD"
}
```

Parity mode: one row per side, the direct rate first — this is the Expedia row of the Chicago run above:

```json
{
  "chain": "choice", "hotel_code": "IL263", "hotel_name": "Clarion Inn Elmhurst - Oak Brook",
  "checkin": "2026-11-10", "los": 1,
  "channel": "expedia", "channel_id": "1089", "id_confidence": 0.85, "status": "available",
  "public_per_night": 97.00, "public_total": 97.00, "member_per_night": null, "basis": "after_tax",
  "rate_name": "pay now", "currency": "USD",
  "vs_direct_per_night": -21.70, "vs_direct_pct": -18.28, "cheaper": "channel",
  "verdict": "ota_undercuts", "cheapest_channel": "expedia", "cheapest_per_night": 97.00, "max_undercut_pct": 18.28,
  "source": "live", "cache_age_sec": 0
}
```

#### Reading the price columns

- **`price_per_night`** is in the chain's own tax basis — some chains publish before tax, some after — and **`tax_included`** says which. Comparing this column *across* chains compares different things.
- **`price_per_night_after_tax`** is the comparable figure. Use it for cross-chain and chain-vs-OTA work. It is `null` only where the source publishes no tax data at all; it is never estimated.
- **`price_total`** is the whole stay. **`points_per_night`** and **`points_total`** are the award equivalents.
- In Parity rows, **`basis`** says whether the side's figure is `after_tax` (comparable) or `native` (the side had no tax data), and **`vs_direct_pct`** is the channel's public rate against the chain's, per night.

### Pricing

One event: **`credit-unit` = $0.003**. A lookup costs a number of units that depends on the chain and what you asked for — you pay for results, not requests.

| Call | Cost |
|---|---|
| Rates or Award, one hotel (Marriott, Hilton, Hyatt, IHG, Choice, Meliá, Premier Inn, I Prefer) | $0.012 |
| Rates, one hotel on an OTA (Booking.com, Expedia, Hotels.com, Trip.com, Agoda) | $0.012 |
| Rates or Award, one hotel (Radisson) | $0.036 |
| Rates or Award, one hotel (Best Western) | $0.060 |
| Rates, one hotel (Wyndham) | $0.072 |
| Find hotel codes, one run — however many hotels come back | $0.012 (Best Western $0.060, Wyndham $0.072) |
| Comp set, per hotel returned | $0.003 (Radisson / Best Western / Wyndham $0.006), with a **minimum of $0.012 per call** |
| Comp set, *Guarantee the member rate* | adds $0.030 per hotel on Radisson, $0.066 on Wyndham and $0.006 on Best Western, where it costs extra upstream calls; no extra on the others |
| Calendar, per night priced | $0.0012 on the hotel chains (Wyndham $0.003); on an OTA, one Rates call per night |
| Parity, one hotel | one Rates call on the chain + one Rates call per OTA that lists the hotel + $0.012 the first time a hotel's OTA ids are looked up. Five channels on a Marriott hotel: at most $0.084, typically less once cached |
| Stay, one hotel | one Rates call on the chain + the Calendar price of its nights |
| A result served from a recent cache | a quarter of the above — the comp-set per-call minimum still applies |
| An error, a hotel code the chain does not know, or an OTA with no listing for the hotel | free |

Worked examples: a 10-hotel Marriott comp set costs **$0.03**; a 3-hotel one costs **$0.012**, because that
is the per-call minimum rather than 3 × $0.003. A 30-night Hyatt calendar costs **$0.036**. One Hilton rate
matrix costs **$0.012**. A Parity run on a Choice hotel against all five OTAs, four of them listing it, cost
**$0.072** live. Set **Stop after N credits** to cap a run; Apify's own per-run charge limit applies on top.

#### What your Apify plan already covers

Event charges come out of the platform usage your Apify plan includes, so most runs cost nothing extra:

| Your plan | Included usage | Single-hotel lookups it covers |
|---|---|---|
| Starter ($19/mo) | $19 | ~1,580 |
| Scale ($199/mo) | $199 | ~16,500 |
| Business ($999/mo) | $999 | ~83,000 |

**On the Apify Free plan this Actor runs in demo mode: a run stops once 5 credits are spent** — five
single-hotel lookups, or a comp set of about twenty hotels. Enough to see the real output and decide. Any
paid plan runs the whole list.

The limit is checked **between** calls, never inside one, so a single large call runs to completion and
is kept in full: a Wyndham lookup (6 credits), a Parity run against all five channels (up to 7 credits),
a 60-night Calendar window, or a comp set on Radisson, Wyndham or Best Western each cost more than the
cap on their own. What you see then is a run that **SUCCEEDED with the rows of that first call complete**,
a status line *"Stopped early: the Apify Free plan limit of 5 credits was reached"*, and the remaining
hotel codes not fetched. That is the demo working as intended, not a failure, and nothing is charged for
the calls not made. Keep a demo run to one hotel — or Parity with two channels — and it never trips; a
paid Apify plan runs the whole list.

### FAQ

#### Is this official hotel chain API data?

No. There is no partnership with any chain or OTA. The data is read from each chain's and each OTA's own publicly reachable pricing surfaces — the same numbers their app and website quote an anonymous visitor. No account credentials are used and no bookings are placed.

#### Does it read Marriott's own site, or an OTA?

Both, and it keeps them apart. A Marriott lookup returns Marriott's rate plans, Bonvoy member prices and
Bonvoy award nights in points — none of which appear on an OTA page. Parity mode then reads Booking.com,
Expedia, Hotels.com, Trip.com and Agoda for the same property and puts their public price beside Marriott's.
Every row says which side it came from (`chain`, or `channel` in Parity rows).

#### What is rate parity, and why would an OTA be cheaper than the hotel?

Rate parity is the hotel's promise — to its guests and in its OTA contracts — that its own site is never
undercut. It breaks all the time: wholesale rates resold through an OTA, an OTA funding a discount from its
own commission, a member price the hotel forgot to match. Parity mode is how a revenue manager finds those
cases without opening five tabs per hotel: one run per hotel per date, `cheaper = channel` filters the
undercuts, `max_undercut_pct` ranks them.

#### Can I monitor award point prices, or a redemption I am about to book?

Yes — that is what Award and Calendar mode are for, and it is the use case the section above walks
through. Schedule the Actor, let the dataset accumulate, and compare `points_per_night` and `cpp` run over
run. Award pricing at Marriott, Hilton and IHG is dynamic, so the same room genuinely changes points cost
between days — and `certs` tells you when a free-night certificate would cover it.

#### How is this different from a Booking.com or Expedia scraper?

Those return OTA inventory and OTA prices, and nothing else. This returns what the **chain itself** quotes,
with the OTA price beside it when you ask for it — and three of the columns below exist on no OTA page:

| | OTA-sourced Actors | This Actor |
|---|---|---|
| Where the number comes from | an OTA's listing page | the chain's own booking system, plus the OTAs on request |
| Public rate | ✓ | ✓ |
| Loyalty **member** rate | — | ✓ |
| Award night in **points** | — | ✓ (8 of 11 chains) |
| Cents-per-point, free-night certificates | — | ✓ |
| OTA vs direct gap, per channel | — | ✓ |
| Rate plans (refundable, breakfast, advance purchase) | whatever the OTA resells | every plan the chain sells |
| Tax basis | rarely stated | stated per row, plus an after-tax figure |

#### Is this legal to use?

Not legal advice, just the facts about where the data comes from: no account credentials, no logins, no
bookings, nothing behind a paywall. The rates are what each chain and each OTA quotes an anonymous visitor
on its own public booking surface. No chain or OTA trademark, logo or creative asset is used or resold —
their names appear here because they are what the data is about. What you do with the data is yours to
square with your own obligations.

#### Can I get member rates without a loyalty account?

Yes, where the chain quotes them to an anonymous visitor — which most do. They arrive as their own rows with `member_only: true` and under `lowest_member`, never folded into the public rate. An OTA's Genius or Member Price is reported the same way, in `member_per_night`, and never enters the parity gap.

#### Does it return award / points pricing?

Yes, on 8 of the 11 chains (see the table above), with `points_per_night`, any `cash_copay`, `cpp` — cents per point — computed per night against the after-tax cash rate for the same stay, and the free-night certificates that would cover the night on Marriott, Hilton and IHG.

#### How fresh are the rates?

Live at request time, or from a cache a few minutes old — every row says which, in `source` and
`cache_age_sec`. There is no nightly crawl behind this: the numbers are fetched when you press Start, so a
points price or a rate that moved an hour ago has already moved here.

#### How many hotels can one run cover?

Comp-set modes answer up to 25 hotels per call, and a run can make as many calls as you like. Rates, Award, Calendar, Parity and Stay take one hotel per call; give several codes and each is looked up in turn.

#### Where do I get the hotel codes?

From **Find hotel codes** mode: a city, address or landmark in, the chain's own codes out. One credit a run
— $0.012, or $0.060 on Best Western and $0.072 on Wyndham, whether the search returns three hotels or
three hundred. The codes are the chain's internal property identifiers (Marriott calls them MARSHA codes),
so they are not guessable and not shared between chains — a Hilton code means nothing to Hyatt. An OTA's
code is its own hotel id, which a Parity run returns in `channel_id`.

#### Why did a hotel come back as `not_found`?

The chain does not recognise that property code. Those rows are never charged. `sold_out`, `closed` and `restricted_los` are real answers — the hotel exists, it just isn't selling that stay. In Parity, a channel with `skipped` set had no listing matched for the hotel (or answered with an error) and is never charged either.

#### Can I use this on the Apify Free plan?

Yes, in demo mode: a free-plan run stops once 5 credits are spent — five single-hotel lookups, or roughly
twenty hotels in a comp set — and the run says so when it stops. That is enough to check the output against
a hotel and a date you care about. Any paid Apify plan runs the whole list, with no limit from this Actor
beyond the ones you set yourself.

#### My Free-plan run says "Stopped early" after one hotel — is the data incomplete?

No. The 5-credit demo cap is checked between calls, so the call that crossed it still ran to the end and
its rows are all there. A Wyndham lookup or a Parity run against all five OTAs is one call of 6–7 credits,
which is over the cap by itself: you get that hotel in full, the run reports *Stopped early* with the
reason, and any further hotel codes in the input are not fetched — and not charged. To fetch several of
those in one run, use a paid Apify plan or split them across runs.

#### Why do my Wyndham rows have no room or rate names?

Because Wyndham's booking system names them by code to an anonymous visitor: every row carries
`room_code` and `rate_code`, and `room_name` / `rate_name` are empty on Wyndham only. Prices, the member
flag, taxes, availability and points are all present. It is the source's shape, not a missing lookup —
the codes are stable per property, so one mapping in your sheet covers every later run.

#### Is there an API instead of an Actor?

Yes — the same data is a REST API with an OpenAPI spec: [directrate.dev/docs](https://directrate.dev/docs). This Actor is a thin wrapper around it. A key is self-serve at [directrate.dev](https://directrate.dev) (250 free credits a month, no card); the plans there bill per credit, the same credits this Actor charges.

#### Something looks wrong — who do I talk to?

Open an issue on the Actor. Every error row carries a `request_id`; quoting it makes a problem traceable to the exact call.

### Good to know before you rely on it

- **No uptime SLA.** Chains and OTAs change their systems without notice and a source can degrade for hours. `source` and `cache_age_sec` are on every row so you can always see what you got.
- A live single-hotel lookup typically takes ~1–10 seconds; a 25-hotel comp set and a five-channel Parity run are paced on purpose and can take tens of seconds. Schedule bulk work rather than running it in a request path.
- Coverage is the eleven chains and five OTAs above — not independents outside those chains, except where an OTA lists them and you shop the OTA directly.
- A stay that has already started is refused rather than priced: a past check-in date returns an error and costs nothing.
- Wyndham rows carry room and rate **codes** without names (the chain publishes codes only); on the Apify Free plan a single Wyndham or five-channel Parity call exceeds the 5-credit demo cap by itself, completes in full, and the run stops there with its rows intact — see the FAQ.

# Actor input Schema

## `mode` (type: `string`):

**Rates** = every room × rate plan for one stay (cash + member). **Award** = points options, points+cash, cents-per-point and the free-night certificates that cover the night. **Calendar** = lowest rate per night over a window. **Comp set** = lowest rate for up to 25 hotels in one call. **Comp set (award)** = lowest points for up to 25 hotels. **Parity** = the chain's direct rate beside Booking.com, Expedia, Hotels.com, Trip.com and Agoda for the same hotel, with the gap and the verdict. **Stay** = a 2–14 night stay sold as one versus each night priced alone, and whether a split booking is possible. **Find hotel codes** = turn a city or a point on the map into the chain's own property codes, which every other mode takes as input.

## `chain` (type: `string`):

Each hotel chain is shopped on its own brand-direct system: Marriott Bonvoy, Hilton Honors, World of Hyatt, IHG One Rewards, Choice Privileges, Meliá Rewards, Premier Inn, I Prefer, Radisson Rewards, Best Western Rewards, Wyndham Rewards. The five OTAs at the end are channels: Rates, Calendar and Comp set work on them with the OTA's own hotel id (the number in its URL, or the `channel_id` a Parity run returns); they have no award points and no hotel-code search. Parity takes a hotel chain here and compares it against the OTAs.

## `hotelCodes` (type: `array`):

The chain's own property codes, one per line — e.g. Marriott `CHIRL` (Chicago Marriott Downtown), Hyatt `CHIZP`, IHG `CHIMM`, Hilton `CHITDHX`. For an OTA, its hotel id (Booking.com `708601`, Expedia/Hotels.com `553946`, Agoda `2458383`). In Rates/Award/Calendar/Parity/Stay mode each code is a separate lookup; in comp-set mode up to 25 codes are answered in one call. Don't know the codes? Run **Find hotel codes** mode with a city name first. Not used in that mode.

## `channels` (type: `array`):

Parity mode only: which OTAs to shop beside the chain's direct rate. Leave empty for all five. Each channel shopped costs one Rates call on that OTA ($0.012 live); a channel with no id for the hotel costs nothing.

## `place` (type: `string`):

City, address or landmark — e.g. `Chicago, IL`, `Shibuya, Tokyo`, `LHR airport`. Used only in **Find hotel codes** mode; the chain resolves it to a point and returns its hotels around it with their codes.

## `radius` (type: `integer`):

Find hotel codes mode only. 1–100 miles around the resolved point.

## `checkin` (type: `string`):

YYYY-MM-DD. Leave empty for 30 days from today. In Calendar mode this is the first night of the window. A date already in the past is refused — no chain sells a stay that has started.

## `los` (type: `integer`):

Nights per stay, 1–30 (1–7 in Calendar mode, 2–14 in Stay mode).

## `calendarDays` (type: `integer`):

Calendar mode only: how many consecutive check-in dates to price, starting at the check-in date. 1–60.

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

Adults per room, 1–8. Chains price by occupancy, so this changes the rate you get back.

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

One age per child, e.g. \[4, 9]. Leave empty for none.

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

Rooms to quote, 1–8. More than one room returns the rate for that room count, not a sum.

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

ISO 4217 code. Chains that price only in the property's currency return that currency instead.

## `compsetRates` (type: `string`):

Comp-set modes only. **Lowest** = lowest public rate, plus the member rate where the chain returns it for free. **Member** = guarantee the lowest member rate. It costs extra only on the chains where it needs extra upstream calls: +$0.030 per hotel on Radisson, +$0.066 on Wyndham and +$0.006 on Best Western. No extra on the others.

## `maxCredits` (type: `integer`):

Safety cap: stop the run once this many API credits have been spent. It is checked between calls, not inside one, so a single large call — a long Calendar window, or a comp set on Radisson, Wyndham or Best Western, a Parity run against five channels — can carry past the cap before the run stops. For a hard ceiling, set Apify's own per-run charge limit when you start the run. Leave empty for no cap. On the Apify Free plan a run stops once 5 credits are spent regardless; any paid plan runs the whole list.

## `lat` (type: `number`):

Find hotel codes mode: search around this point instead of a place name (use with lng).

## `lng` (type: `number`):

Find hotel codes mode: longitude of the search point.

## `maxAgeSec` (type: `integer`):

Comp-set, Parity and Stay modes. Caps how stale a cached rate may be. 0 forces a live shop at full price. Leave empty to accept any fresh cached result at the cheaper cached rate.

## `concurrency` (type: `integer`):

How many hotels to look up at once. Higher is not faster — the API paces each chain's upstream itself.

## `apiBaseUrl` (type: `string`):

Development only. On the Apify platform this is ignored unless it points at the same host as the Actor's configured API, because the Actor's API key is only ever sent to that host.

## Actor input object example

```json
{
  "mode": "rates",
  "chain": "marriott",
  "hotelCodes": [
    "CHIRL"
  ],
  "channels": [],
  "place": "Chicago, IL",
  "radius": 30,
  "los": 1,
  "calendarDays": 30,
  "adults": 2,
  "childrenAges": [],
  "rooms": 1,
  "currency": "USD",
  "compsetRates": "lowest",
  "concurrency": 2
}
```

# Actor output Schema

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

One row per rate plan, per hotel or per night. A stay with no availability still produces a row (status sold_out / closed / restricted_los), and a hotel the chain could not answer for produces one with status error and an error_code — a run never hides a gap.

## `resultsCsv` (type: `string`):

The same rows as a CSV download — the columns are already flat, so they open straight in Excel or Google Sheets.

## `runSummary` (type: `string`):

What this run did: mode, chain, rows pushed, failures, API credits charged and the number of credit-unit events billed. Also says when a run stopped early and why.

# API

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

## JavaScript example

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

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

// Prepare Actor input
const input = {
    "mode": "rates",
    "chain": "marriott",
    "hotelCodes": [
        "CHIRL"
    ],
    "place": "Chicago, IL",
    "los": 1,
    "adults": 2,
    "currency": "USD"
};

// Run the Actor and wait for it to finish
const run = await client.actor("mu0i/hotel-rates-award-points").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 = {
    "mode": "rates",
    "chain": "marriott",
    "hotelCodes": ["CHIRL"],
    "place": "Chicago, IL",
    "los": 1,
    "adults": 2,
    "currency": "USD",
}

# Run the Actor and wait for it to finish
run = client.actor("mu0i/hotel-rates-award-points").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 '{
  "mode": "rates",
  "chain": "marriott",
  "hotelCodes": [
    "CHIRL"
  ],
  "place": "Chicago, IL",
  "los": 1,
  "adults": 2,
  "currency": "USD"
}' |
apify call mu0i/hotel-rates-award-points --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mu0i/hotel-rates-award-points"
        }
    }
}
```

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/B96DdfbFVs0e8yaGW/builds/ryR6gnbrg6iLTj6za/openapi.json
