# Hotel Loyalty Promo Tracker (Accor, Marriott, IHG) (`automatic/hotel-promo-tracker`) Actor

Track current hotel loyalty promotions from ALL Accor, Marriott Bonvoy and IHG One Rewards: title, bonus points, nights or discount, region, registration, book-by and stay-by dates, terms and offer links, with the date each promo was first seen.

- **URL**: https://apify.com/automatic/hotel-promo-tracker.md
- **Developed by:** [Philip Kirkbride](https://apify.com/automatic) (community)
- **Categories:** Travel, E-commerce
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 promotions

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?

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 Loyalty Promo Tracker (Accor, Marriott, IHG)

Track the current public promotions of three hotel loyalty programs, ALL - Accor Live Limitless, Marriott Bonvoy and IHG One Rewards, and get one clean row per promotion: program, title, summary, the bonus (points, a points multiplier, free nights, a discount or a hotel credit) when the offer states it, member-only and registration flags, region, book-by and stay-by dates, terms and offer links, and the date the promotion was first seen.

**Coverage: ALL - Accor Live Limitless, Marriott Bonvoy and IHG One Rewards only.** Hilton Honors and World of Hyatt are not supported, so the input accepts only these three programs.

Use it to watch for new bonus-points promotions, compare what the big programs offer this week, or feed a travel newsletter: schedule a weekly run and keep the rows where `isNew` is `true`.

### Quick Start

Current promotions of all three programs:

```json
{}
```

Every live Marriott offer plus IHG, Europe only, including promotions that name no region:

```json
{
  "programs": ["marriott", "ihg"],
  "region": "europe",
  "marriottAllOffers": true,
  "maxResults": 2000
}
```

### Input

| Field | Type | Default | What it does |
|---|---|---:|---|
| `programs` | array | all three | `accor`, `marriott`, `ihg`. |
| `region` | string | `any` | `americas`, `europe`, `asia-pacific` or `middle-east-africa`. |
| `includeUnspecifiedRegion` | boolean | `true` | With a region set, also keep promotions that name no region (worldwide or unstated). |
| `includeExpired` | boolean | `false` | Also return promotions still published after their book-by or stay-by date (`isExpired: true`). |
| `marriottAllOffers` | boolean | `false` | Off: the offers Marriott features (about 40). On: every live Marriott offer, about 1,800, in about 2 minutes. |
| `maxResults` | integer | `100` | Maximum promotions returned, up to 5,000. Programs are interleaved; covering every program requires enough results and available matching offers. |
| `trackFirstSeen` | boolean | `true` | Remember when each promotion was first seen, so later runs mark new ones with `isNew`. |
| `firstSeenStore` | string | `hotel-promo-tracker-first-seen` | Name of the key-value store in your account that keeps the first-seen dates. Use one name per schedule. |

### What it returns

Every row has `promoKey`, `program`, `programName`, `promoId`, `offerCode`, `title`, `summary`, `category`, `bonusType`, `bonusAmount`, `bonusUnit`, `memberExclusive`, `registrationRequired`, `regions`, `regionLabel`, `brand`, `hotelName`, `participatingHotels`, `bookBy`, `stayFrom`, `stayBy`, `isExpired`, `offerUrl`, `termsUrl`, `terms`, `firstSeenAt`, `isNew`, `source` and `scrapedAt`.

- `bonusType` is `points`, `nights`, `discount` or `credit`, with `bonusAmount` in `bonusUnit` (`points`, `multiplier`, `nights`, `percent` or a currency code). It is read from the offer copy, so it is empty when the offer names no figure (a breakfast package, for example).
- `regions` holds `americas`, `europe`, `asia-pacific` and `middle-east-africa`. Accor's come from its regional offer pages; Marriott's and IHG's from the places the offer names. An empty list means worldwide or not stated.
- Dates are `YYYY-MM-DD`. `bookBy` and `stayBy` are filled when the program publishes them: always for Marriott's stay end, and for most dated Accor and IHG promotions.
- `promoKey` is stable across runs, so you can deduplicate or join on it.

### Output example

```json
{"promoKey":"accor:hotel-bookings-earn-x4-reward-points","program":"accor","programName":"ALL - Accor Live Limitless","promoId":"hotel-bookings-earn-x4-reward-points","offerCode":"020214","title":"Earn four times more points: discover our new hotels","summary":"To celebrate their (re)opening, multiply your Reward points earned by your stay by four if you are an ALL member.","category":"Member Exclusive","bonusType":"points","bonusAmount":4.0,"bonusUnit":"multiplier","memberExclusive":true,"registrationRequired":true,"regions":[],"regionLabel":null,"brand":null,"hotelName":null,"participatingHotels":null,"bookBy":"2026-11-22","stayFrom":"2026-09-28","stayBy":"2027-03-28","isExpired":false,"offerUrl":"https://all.accor.com/a/en/offers/hotel-bookings-earn-x4-reward-points.html","termsUrl":"https://all.accor.com/a/en/offers/hotel-bookings-earn-x4-reward-points.html","terms":"1. General ALL terms and conditions apply to the Reward Points x4 New Hotels offer and you must be a ALL member to benefit from it.\n2. To take advantage of this…","firstSeenAt":"2026-10-27","isNew":true,"source":"all.accor.com","scrapedAt":"2026-10-27T15:08:16+00:00"}
```

### Limits and caveats

- **Programs covered:** ALL - Accor Live Limitless (the Offers & Deals corner, English), Marriott Bonvoy (its offers search, US English) and IHG One Rewards (promotion pages of the US English site, all IHG brands). Hilton Honors and World of Hyatt are not supported: their websites don't serve their promotion pages to automated tools.
- **Member-specific promotions:** promotions shown only after signing in (Marriott's and IHG's personal "registered promotions") are not public and are not included.
- **Marriott offers are hotel offers:** most are one hotel's package or rate (`hotelName`, `participatingHotels`). Filter on `category` (`Bonus Points`, `PointSavers`, `Cash + Points`, `Member Rates`) or `bonusType` for loyalty promotions.
- **Evergreen IHG pages:** IHG keeps some standing offers (member rate, senior and AAA rates, buying points) on promotion pages; they come with no dates.
- **Dates are read from the published copy.** `bookBy`, `stayFrom` and `stayBy` are empty when the program doesn't state them (Marriott rarely states a booking deadline). A date printed with a year is kept as printed. When the copy leaves the year out ("Book by 13th September"), the year is inferred from the offer's other dates, and a stay end that would fall before the booking deadline is moved to the next year. `isExpired` follows these dates, so check them against the offer page before acting on a borderline promotion.
- **Terms are a copy, not the contract.** `terms` holds the published conditions, cut at 4,000 characters. The program's own page (`termsUrl`) governs; eligibility, blackout dates and participating hotels are not checked.
- **Bonus and region are read from the copy** and can be empty or broad when an offer is vague. Check `terms` before relying on a figure.
- **Freshness:** every run reads the programs' websites live, as of `scrapedAt`. Programs change, extend or withdraw promotions without notice, so a row is only as current as its run. `firstSeenAt` is the first day this tracker saw the promotion, not its launch date.
- **Incomplete runs are flagged.** If a program or some of its promotion pages can't be read, the run's status message names them and those promotions are missing from the results; they are never reported as "no promotions". A run that nears its timeout stops early, keeps the rows already saved and says so in the status message.
- A run with the default input takes well under a minute; `marriottAllOffers` adds about 2 minutes.

### FAQ

**How do I get only new promotions each week?** Schedule a weekly run with the same `firstSeenStore` and keep rows where `isNew` is `true`. The first run marks everything as new.

**Why did a promotion I saw last week disappear?** Expired promotions are left out by default. Set `includeExpired` to `true` to see promotions still published after their dates.

**What happens when one program's site is down?** The other programs still return their rows; the run's status message names the program, or the number of its promotion pages, that could not be read.

**How fresh is the data?** Every run reads the programs' websites live; `scrapedAt` is when the row was read. Schedule runs as often as you need current promotions.

### Price

**$0.001 per promotion** ($1.00 per 1,000 promotions), plus **$0.005 per run start**. Each delivered promotion row is one result; promotions left out as expired, outside your region or duplicates are not charged. The run-start event is charged per GB of memory; the default 512 MB run is one start event. The start is charged even if the run saves no results. Platform usage is included in these prices. If you set a maximum charge for the run, it stops reading once the promotions it pays for are saved, and the status message says it stopped at the maximum charge.

### Support

For questions or issues, contact us from the Actor page.

# Actor input Schema

## `programs` (type: `array`):

Loyalty programs to read. Leave empty for all three.

## `region` (type: `string`):

Keep promotions for one region. The region comes from the program's regional offer pages or from the places the offer names.

## `includeUnspecifiedRegion` (type: `boolean`):

With a region selected, also keep promotions that name no region (worldwide offers and offers that don't say where they apply).

## `includeExpired` (type: `boolean`):

Also return promotions whose book-by or stay-by date has passed but that are still published. They are flagged with isExpired.

## `marriottAllOffers` (type: `boolean`):

Off: the offers Marriott features on its offers page (about 40). On: every live Marriott offer, about 1,800 hotel deals and promotions, which takes about 2 minutes.

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

Maximum promotions returned. Programs are interleaved; covering every program requires enough results and available matching offers.

## `trackFirstSeen` (type: `boolean`):

Remember when each promotion was first seen (in a key-value store in your account), so scheduled runs mark new promotions with isNew. Off: firstSeenAt is the run date and isNew is empty.

## `firstSeenStore` (type: `string`):

Name of the key-value store that keeps the first-seen dates: letters, digits and hyphens. Use one name per schedule. Empty: hotel-promo-tracker-first-seen.

## Actor input object example

```json
{
  "programs": [
    "accor",
    "marriott",
    "ihg"
  ],
  "region": "any",
  "includeUnspecifiedRegion": true,
  "includeExpired": false,
  "marriottAllOffers": false,
  "maxResults": 30,
  "trackFirstSeen": true
}
```

# Actor output Schema

## `results` (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 = {
    "programs": [
        "accor",
        "marriott",
        "ihg"
    ],
    "region": "any",
    "maxResults": 30
};

// Run the Actor and wait for it to finish
const run = await client.actor("automatic/hotel-promo-tracker").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 = {
    "programs": [
        "accor",
        "marriott",
        "ihg",
    ],
    "region": "any",
    "maxResults": 30,
}

# Run the Actor and wait for it to finish
run = client.actor("automatic/hotel-promo-tracker").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 '{
  "programs": [
    "accor",
    "marriott",
    "ihg"
  ],
  "region": "any",
  "maxResults": 30
}' |
apify call automatic/hotel-promo-tracker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automatic/hotel-promo-tracker"
        }
    }
}
```

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/44AblaWZ71uNuvNFP/builds/sPghlhVe7CuelNGs3/openapi.json
