SO Labs · Rate Intelligence

The Rate Shopper
Nobody Wanted
to Build

Why ski resort pricing
is its own animal

A client brief arrives. An Alps ski resort group wants to understand how their competitors are pricing. Simple enough — rate shoppers are a solved problem in hospitality. Except that in mountain resort accommodation, almost nothing is solved.

Three weeks later, we had a live system scraping five competitors daily, normalising prices across incompatible formats, detecting anti-bot protections, and surfacing a price evolution matrix that nobody in the industry was looking at. This is the story of how we got there, what broke along the way, and what it taught us about building real intelligence infrastructure for hospitality.

Urban hotels sell room nights. You can shop them by check-in date, room type, and length of stay with reasonable consistency across competitors. The unit is a night. The product is a room. The price is the price.

Ski resort accommodation doesn’t work like that. In the Alps, you’re dealing with a product that is simultaneously a room, a catering package, a ski pass, and a weekly formula — often sold only on fixed check-in days, in fixed durations, for fixed occupancy configurations. The price you see online for a “studio for 4” might include half board, full board, breakfast, or nothing at all — and the label won’t always tell you which.

Before you can compare a single number across five competitors, you need to decide what you’re actually comparing. This is the problem we were handed. And it turns out, nobody had solved it for this specific corner of the market.

The compset

Five competitors.
Five data architectures.
Zero standardisation.

The client operates a reference property in an Alps resort area. Their relevant competitive set spans five operators, each with a different distribution architecture.

Competitor Architecture Key challenge
A — largest apartment rental operator in the French Alps HTML scraping 10–20+ unit types per property, all dynamically priced. No API.
B — club-format operator, single room type per property HTML scraping Fixed pricing, limited dynamic variation. Ignores duration parameters.
C — OG of French mountain apartment rentals JSON API Protected Authenticated API behind a rotating session token. Blocks cloud IPs entirely.
D — club-format operator, weekly stays JSON API Returns per-person price via PrixPax field — undocumented. Miss it and you understate their pricing by 50%.
Client — reference property JSON API Full pricing calendar including refundable / non-refundable formulas.
What hotels do to stop you

What hotels do to
stop you shopping them

Hotels and accommodation operators actively protect their pricing from automated shopping — for competitive reasons, rate parity management, and because their distribution partners increasingly require it. The protections vary by sophistication.

IP blocking is the most common. Cloud infrastructure IPs (Railway, AWS, Google Cloud) are often pre-blocked entirely, because operators know that real users don’t browse from data centre IP ranges. This is what stopped us with Competitor C until we routed through a residential proxy.

Session token rotation is what Competitor C uses. Their pricing API requires a valid session token that expires and rotates. Without a fresh token, every request returns empty results — no error, just silence. The scraper needs to detect token expiry and acquire a new one automatically, which requires actually loading the site as a browser would.

Duration parameter ignorance — this one surprised us. Both Competitor A and Competitor B accept a duration parameter in their URLs, but neither actually uses it. The page always returns their standard 7-night offer regardless of what you request. If you store that price tagged as “3 nights”, you end up with a per-night rate that’s less than half of reality — and a dashboard that looks like Competitor A is dramatically cheaper than it is.

The page accepted the parameter. It just ignored it entirely. Three nights, seven nights — same price returned, every time.

— Studio Oriente · Rate Shopper Build Log

Discovering this required inspecting the HTML of each returned page, parsing the actual date range shown in each accommodation card, and cross-checking it against the requested duration. When they don’t match, you discard the result.

The architecture

The architecture

The system runs on Railway — a container hosting platform that costs roughly €60/month at our scale. The stack is FastAPI (Python) for the API layer, SQLite on a persistent volume for storage, and APScheduler for the daily scrape cron.

Each competitor has its own scraper module with its own approach to the pricing source:

rate_shopper/
  scrapers/
    competitor_a.py      # HTML scraping via BeautifulSoup
    competitor_b.py      # HTML scraping, duration param ignored
    competitor_c.py      # Authenticated API + token rotation + residential proxy
    competitor_d.py      # JSON API — PrixPax × participants
    client.py            # Reference property API, full calendar

The daily scheduler generates a matrix of shopping combinations — all 7 check-in weekdays × 3, 5, and 7-night durations × 2 occupancy configurations (2 adults standard, 2 adults + 2 children family) — and distributes those combos across the scrapers. Competitor C handles its own token lifecycle via a separate Railway service routing through Decodo’s Web Scraping API over residential IPs, pinned per scraping cycle to avoid triggering anomaly detection on mid-session IP changes.

The residential proxy is the key piece. Decodo’s WSA service routes requests through residential IP addresses with session persistence. This adds roughly €30/month to the infrastructure cost and is the price of accessing the most commercially interesting competitor in the compset.

The normalisation problem

Raw prices mean nothing across this compset. Before any comparison is possible, every price passes through the same normalisation logic.

Per-night conversion. A 7-night stay at €654 and a 3-night stay at €325 are not comparable as totals. We divide every price by its duration to get a per-night rate — the primary metric surfaced in the dashboard.

Occupancy consistency. Competitor D returns a per-person price via its API. We multiply by the number of participants before storing. A price of €675 per person for 2 adults is €1,350 total — closer to the real market rate for that product.

Duration deduplication. When Competitor A returns the same price for both a 3-night and 7-night request on the same date, we detect the duplicate via the card-level date range in the HTML and only store the 7-night result.

Minimum price per property per date. Each competitor has multiple unit types. For the heatmap view, we surface the minimum per-night price per property per arrival date — the entry point into that property on that date, which is what a guest would see first.

Price evolution

The thing nobody
was looking at

Once you have daily snapshots of the same prices over time, something interesting becomes possible. You can ask: how has the price for a specific check-in date changed as the booking window has closed?

This is different from asking “what is the price today.” It’s asking “was this date always priced at this level, or did it move — and when?”

For revenue managers, this is the signal that matters. A competitor priced at €80/night for a July 12 arrival in February, and now at €140/night in April, is either responding to demand or adjusting to inventory depletion. A competitor that has held €325/night flat for six months is either not doing dynamic pricing, or is sitting on unsold inventory and refusing to move.

We built a price evolution matrix that lets the revenue team pull up any arrival date and see its full pricing history across all competitors — one line per competitor, one data point per daily scrape. Two weeks into operation, you can already see Competitor C moving dynamically in peak school holiday weeks while Competitor B holds flat. That tells you something real about their respective pricing strategies.

This view didn’t exist in the original brief. It emerged from the architecture — once you’re storing timestamped snapshots, the evolution view is just a query.

— Studio Oriente · SO Labs
What this taught us

What this taught us
about Meridian

We started this project as a client engagement. We ended it with a clearer picture of what hospitality data infrastructure actually requires in production. Three things carried over directly into how we’re building Meridian.

Data heterogeneity is the default, not the exception. Every data source in hospitality has its own schema, its own protection layer, its own quirks. Any intelligence layer that assumes clean, consistent inputs is going to fail. Meridian needs to be designed around normalisation as a first-class concern — not a pre-processing step.

Timestamps are as important as values. The price evolution matrix wasn’t planned. It emerged because we were storing scraped_at alongside every price record. That single field transformed a rate shopper into a market intelligence tool. In Meridian, every data point will carry its lineage — when it was captured, from what source, under what conditions.

The residential proxy problem is universal. Any hospitality data source worth monitoring is protected. Cloud IPs are increasingly blocked. Rate limits are aggressive. Session management is required. The Decodo integration we built for Competitor C is a pattern we’ll reuse across Meridian — it’s not a special case, it’s the standard operating environment.

3wk
Brief to live daily scrape
55k
Price points per daily run
90
/ month total infrastructure
9m
Average scrape duration

There are SaaS rate shopping tools that cover mountain resort accommodation. They cost €300–800/month, they cover the properties their vendor has already integrated, and they surface the data in a format the vendor decided was useful. What they don’t do is give you a price evolution matrix, family occupancy data, refundable vs non-refundable segmentation, or the ability to add a competitor that isn’t in their catalogue.

Building custom got us all of that, at a lower monthly cost, with full control over what gets measured and how.

Newsletter

Checked In.

What actually works when hotels put AI to use. No slide decks. Straight to your inbox — bi-weekly.