Recipes/Compare ADR across your whole portfolio
Code Feed15 min

Compare ADR across your whole portfolio

A portfolio table is easy to print and easy to misread. Twelve ADRs side by side look comparable; they only are when every hotel was priced on the same nights, on the same product, collected the same way. This recipe prints the table and, under it, every reason to distrust a line.

Compare ADR across your whole portfolio

What you'll build

A command that prints one line per hotel — ADR over a window of nights, its distance from the portfolio median, how many nights it is built on and which products — followed by every reason two lines are not comparable, plus a CSV for the spreadsheet.

Set this up first

Required

The endpoints don't take free text — they take your identifiers. This recipe assumes the things below already exist on your account; while one of them is missing, the call answers with an error or with less than you expect. Each point links to the recipe that walks it.

  1. 01

    Register the hotel in your dashboard

    Veetal resolves the property against Booking.com and assigns it a slug — and that slug, not the hotel name, is what every accommodation endpoint takes.

    Guide: Add your hotel and the comp set you price against →
  2. 02

    Run one import on the hotel

    A Feed dataset only contains what an import has written. Activate this API on the hotel, launch the first run by hand and read the credit estimate before you press — the cost grows with the comp set and, on multi-OTA datasets, with the OTAs too.

The same thing over the API, if you would rather not click:

cURL
curl -X POST "https://api.veetal.app/v3/account/accommodation" \
  -H "veetal-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ ... }'   # see the account reference for the body

To see whether an import has already finished, list them:

cURL
curl "https://api.veetal.app/v3/account/imports" \
  -H "veetal-api-key: YOUR_API_KEY"

Feed reads are not billed per request. The credits go on the imports that collect the data, which is why the estimate appears before you launch and not after.

How it works

  1. 01

    Put every hotel on the account

    Add every property in Accommodations. A portfolio view missing three hotels is one nobody trusts.

  2. 02

    Import them all the same way

    Launch an Accommodation Rates import over the nights you want, with the same visitor type and the same device for every hotel, and put it on a schedule. Two hotels collected differently produce numbers that look identical and mean different things.

  3. 03

    Read one month per hotel

    Call /rate/v7/{year}/{month} once per hotel and month. It answers every night of that hotel's latest finished import; keep the hotel's own rates and drop the comp set. Build the window of nights in UTC, or it slides one night back east of UTC.

  4. 04

    Price each night, then the window

    Take the cheapest rate of each night with the product it came from, average those into the ADR, and measure every hotel against the portfolio median, not the mean.

  5. 05

    Read the warnings before the ranking

    A hotel priced on fewer nights, a hotel mixing products across nights, a hotel collected as Genius or on another device, or a second currency: each one makes its line less comparable, and the recipe lists them under the table.

The call

List the hotels first — the slug is only the key of that object. Then one call per hotel and month: every night of that hotel's latest import, in the same shape for the whole portfolio.

cURL
# your hotels, indexed by slug
curl "https://api.veetal.app/v3/account/accommodation" \
  -H "veetal-api-key: YOUR_API_KEY"

# every night of one hotel's latest import, for a whole month
curl "https://api.veetal.app/v3/feed/accommodation/YOUR_ACCOMMODATION_SLUG/rate/v7/2026/10" \
  -H "veetal-api-key: YOUR_API_KEY"

Clone and run

Runnable

The whole recipe is one self-contained folder: Node 20, no build step, and a test suite that runs without a key.

Terminal
git clone https://github.com/Veetal-Connect/recipes.git
cd recipes/portfolio-monitor
cp .env.example .env          # your VEETAL_API_KEY
node --env-file=.env index.mjs YYYY-MM-DD YYYY-MM-DD   # put the window: first night, last night
View the repository ↗ Stack: Node 20 · JavaScript, no dependencies

Check your key first

One call, five seconds. If this answers, the recipe will run.

cURL
# your hotels, indexed by slug
curl "https://api.veetal.app/v3/account/accommodation" \
  -H "veetal-api-key: YOUR_API_KEY"

# every night of one hotel's latest import, for a whole month
curl "https://api.veetal.app/v3/feed/accommodation/YOUR_ACCOMMODATION_SLUG/rate/v7/2026/10" \
  -H "veetal-api-key: YOUR_API_KEY"

Questions

Is this ADR the same as my PMS's ADR?

No, and do not present it as if it were. This is the rate published on Booking; your PMS knows what was actually sold, including direct and negotiated business. They answer different questions and the gap between them is itself informative.

Why the median and not the average?

Because one 2,000 EUR property would drag the reference every other hotel is judged by. The median stays put when one line is extreme, so each deviation says something about that hotel, not about the outlier.

Why does a hotel say it is priced on fewer nights?

Because it had no rate on some nights of the window, and those nights are not missing at random: the ones a hotel sells out first are its most expensive. An ADR built on what is left is systematically low, so the recipe tells you how many nights each line stands on.

What does "collected as [anonymous · mobile]" mean?

How that hotel's rates were collected: as an anonymous or a Genius shopper, on desktop or mobile. A Genius price is not a public price and a mobile price is not a desktop price, so when the portfolio mixes them the recipe groups the hotels by fingerprint. Fix the import configuration before trusting the ranking.

What if properties are in different currencies?

The recipe warns that the ADRs are not addable and does not convert them: a conversion you did not choose is worse than none. Set the same currency on every hotel, or convert outside with a rate you can state.

Why is one of my hotels missing from the table?

It had no rates in the window in its latest finished import. Each hotel is read from its own latest import, so a night only an older import covered does not count. Check the Imports tab and the schedule.

Build your own

Start free with 1,000 API credits. No credit card, no sales call.

Recipes · MCP

What do you want to build with hotel rates?

Say it in one line and we hand you back the prompt, the recipe if one already exists, and the APIs it runs on — starting with hotel rates.

Try

Browse all recipes →

Whatsapp