Architecture tutorial for a hotel + Airbnb price comparison tool: identity resolution, live multi-OTA rates, like-for-like normalization, cache and credit control, parity as a signal.
A hotel price comparison tool built on a hotel price comparison API takes one stay request — property, check-in, check-out, occupancy — resolves that property's identity on every channel, fetches the live price from each OTA and short-let platform, normalizes the results into one comparable product, and ranks them. Veetal Connect API supplies each building block as a separate endpoint: an Account API to resolve identities, a Real Time layer with live rates from Booking.com, Expedia, Hotels.com, Google Hotels and Airbnb, a configurable 0–24 h cache to keep credit spend under control, and a parity dataset you can layer on top as a signal.
This is an architecture tutorial, not a code dump: which endpoint answers which question, where comparisons go wrong, and where the money goes.
What a hotel price comparison tool actually does
The UI is the easy part. The hard part is that "the price of a hotel" does not exist as a single number: every channel sells several products for the same room (room-only vs breakfast, flexible vs non-refundable, desktop vs mobile, member vs public), in its own currency, with its own occupancy rules. A hotel metasearch API flow has to reduce that to something a user can rank.
The pipeline, in text:
[1] Resolve identity name / URL → slug · bookingId · expedia-id · GHA entity · Airbnb listingId
│
[2] Fan out live calls parity (all OTAs on Google Hotels) + Booking + Expedia/Hotels.com + Airbnb
│
[3] Normalize currency · board · cancellation · occupancy · total vs per-night
│
[4] Cache & budget cache 0–24 h · credit meter · location search instead of hotel-by-hotel
│
[5] Enrich & rank parity score · cheapest comparable product · deep link
Step 0: decide where the prices come from
Teams that build a hotel price comparison tool for the first time usually consider extracting prices from OTA pages themselves. It works for a demo and fails at scale, for purely technical reasons.
| Criterion | Own extraction from OTA pages | Hotel data API (Veetal Connect API) |
|---|---|---|
| Output | Raw HTML that changes with every layout release | Stable JSON with typed fields (price_per_night, meal_plan, cancellation) |
| Multi-OTA coverage | One parser per OTA, each maintained separately | One schema across Booking, Expedia, Hotels.com, Google Hotels, Airbnb |
| Reliability | Breaks silently on layout changes; anti-bot friction at volume | Managed access infrastructure; you consume the result |
| Cost model | Engineers' time, infrastructure, retries | Credits per call; cached repeats cost nothing extra |
Five OTAs mean five parsers, five monitors and five sets of layout changes to chase; an API moves that work to a provider whose whole job is keeping the schema stable. See hotel API vs web scraping for the full comparison and real-time hotel prices via API for what "live" really means.
Step 1: resolve the hotel's identity on every channel
A comparison tool dies at step one if it cannot tell whether "Hotel Vincci Maritimo" on one site and "Vincci Marítimo Barcelona" on another are the same product. Veetal Connect API handles this with two identity patterns:
- Registered accommodation (
slug). Create the hotel once through the Account API (POST /v2/account/accommodation) and get aslugsuch asvincci-mae. Every Feed and Real Time endpoint then acceptsreference_type=accommodationplus that slug. Right for a known set of properties: your clients' hotels and their comp sets. - Channel entity reference. For an arbitrary hotel a user types in, reference it by its channel identifier:
booking-entity(bookingId+dest_txt+dest_id+country),expedia-id,hotelscom-id,google-hotel-ads-entity, or the numeric AirbnblistingId. Several endpoints auto-create the accommodation on first use, so the second call already has a slug.
Resolution helpers:
GET /v2/real-time/booking/search/hotel/{name}— finds a property on Booking by name and returns its entity fields.GET /v2/real-time/expedia/search/{type}andGET /v2/real-time/hotelscom/search/{type}— the same on Expedia and Hotels.com.GET /v2/real-time/airbnb/search?query=...— destination suggestions on Airbnb; the listing id comes from a room URL or a location-rates call.GET /v2/real-time/search/cities?query=...— catalog search (regions, countries, cities, airports, currencies). 0 credits, so use it freely for autocomplete and geo ids.
Persist the resolved map (slug → per-channel ids): identity then costs credits once, not on every search.
Step 2: fetch live prices across channels
Veetal's Real Time layer gives two granularities, and a good tool uses both.
Property level, all OTAs in one call. The parity endpoint returns one rate per OTA as listed on Google Hotels for the stay — Booking, Expedia, Hotels.com, the official website and whoever else sells that night — each with rate, link, is_official_website and cancellation extras, plus total_otas_found:
GET /v2/real-time/accommodation/vincci-mae/parity/2026-11-06/2026-11-08/2?currency=eur
One call, 4–14 seconds, and you have the headline table most comparison sites show. It is the endpoint behind the Google Hotels source and the rate parity dataset.
Room level, per channel. When the user wants to know which room and conditions produce that price:
| Channel | Endpoint | What comes back | Latency |
|---|---|---|---|
| Booking.com | GET /v2/real-time/accommodation/{slug}/rate/booking/{checkin}/{checkout}/{adults} | Rooms with price_per_night, price_total, capacity, meal_plan, cancellation, genius/mobile offers | 5–20 s |
| Expedia | GET /v2/real-time/{reference_type}/{identifier}/rate/expedia/{checkin}/{checkout}/{adults} | Room types, room_max_occupancy, board, bed configurations | 15–30 s |
| Hotels.com | GET /v2/real-time/{reference_type}/{identifier}/rate/hotelscom/{checkin}/{checkout}/{adults} | Same shape as Expedia | 15–30 s |
| Airbnb | GET /v2/real-time/airbnb/listing/{listingId}/rate/{checkin}/{checkout}/{adults} | total, originalTotal, line-item breakdown, rate plans with refundable | 5–15 s |
Run the channel calls in parallel with a per-call timeout around 45 s and render results as they arrive. Treat "no rate" as a first-class answer: the Booking endpoint returns a reason when the hotel is sold out or cannot fit the party. The Booking.com source is the only one that returns a whole destination in one call; the Airbnb source covers the short-let supply that competes with hotels and never appears in hotel feeds.
Step 3: normalize so you compare like for like
This is where most multi-OTA hotel prices tools quietly mislead their users. A 189 flexible room-only rate is not cheaper than a 205 non-refundable rate with breakfast; they are different products. Normalize before you rank:
- Currency. Request the same currency everywhere (
currency=eurorusdon parity; ISO-4217 on Booking and Airbnb). If a channel cannot, convert at request time with a dated rate stored on the record. - Board. Map each channel to one enum: room-only, breakfast, half-board, full-board, all-inclusive. Booking exposes booleans under
meal_plan; Expedia/Hotels.com use board codes (alo,ad,mp,pc,ti); Airbnb is always room-only. - Cancellation. Reduce to
flexible/non_refundableplus the free-cancellation deadline (free_cancellation_dayson Booking and parity,refundableper rate plan on Airbnb). - Occupancy. Compare rooms that fit the party:
capacity/can_fit_all_guestson Booking,room_max_occupancyon Expedia and Hotels.com. Airbnb prices the whole unit, so record the sleeps-N figure explicitly. - Total vs per night. Store both. Airbnb's
totalincludes fees a per-night figure hides. - Audience. Flag member, mobile-only and promotional rates (
is_genius,is_mobile_offer,has_offer): real prices, but not bookable by everyone.
Then rank within a product bucket (breakfast · flexible · 2 adults), never across buckets. The rate shopping like for like recipe implements exactly this grouping on the Feed layer; its worked example shows a hotel that looked 28 % cheaper than a competitor until the report noticed the competitor did not sell a flexible rate at all. That finding is the product of a comparison tool; a single "cheapest" number hides it.
Step 4: control cost with the cache and the credit meter
Live calls consume credits, and a comparison tool is a call multiplier: five channels per search, many searches per user. Three levers keep it sane.
The cache. Real Time responses can be cached per API from the dashboard, 0 to 24 hours. Within the window a repeated request returns the cached payload at no extra cost. Set the window per surface:
- Search results or destination listing: 6–24 h.
- Hotel detail page: 1–3 h.
- "Check price before booking" button:
?no_cache=trueon that request only. The parameter is global, so you are fresh exactly where freshness sells.
The credit meter. GET /v2/account/customer-info returns balance and consumption at no cost. Wire it into your metrics and alert on burn rate; the control your credit spend recipe is a small script that does it. Plans and credit packs are on the pricing page.
Location search instead of hotel-by-hotel. For "hotels in Lisbon on these dates", do not fan out one call per property. GET /v2/real-time/location-search/{location_search_id}/booking/{target_date} runs one destination search and returns every property found with its price. On the Feed layer the same dataset is dramatically cheaper: the track destination ranking recipe reads 192 hotels of a destination for 0.32 credits. Use the Feed read for the listing — the hotel rates dataset is the Feed side of the data you fetch live in step 2, with history attached — and reserve Real Time for the properties the user opens. Rule of thumb: Feed for the recurring and configured, Real Time for the one-off and arbitrary.
Step 5: add parity as a signal, not just a price
A tool that only shows prices is a commodity; one that explains them is a product. The Feed parity endpoint returns a pre-processed parity score for a registered hotel against its comp set:
GET /v2/feed/accommodation/vincci-mae/parity?from_date=2026-11-06&to_date=2026-11-13&adults=2&device=desktop
Use it for signals the raw grid cannot give: "the official website is 4 % below the cheapest OTA on 6 of the next 7 nights". For a metasearch product it is a trust badge; for a hotel marketing agency, the direct-booking argument. Edge cases are covered in what is hotel rate parity.
Commercially, the comparison surface is where the decision happens: Mirai's analysis of hotel direct channels found Google Hotels' share of metasearch bookings rose from about 70 % in 2019 to over 85 % by 2022, with metasearch contributing roughly 13 % of direct revenue (Mirai, 2023). Owning the data behind that surface is what makes a tool defensible.
Common mistakes when building a hotel price comparison tool
- Ranking non-comparable rates. Cheapest-per-channel across different boards and cancellation policies. Fix: normalize first, rank inside a product bucket.
- Ignoring the cache. Every page view triggers a live call because nobody set a retention window. Fix: 6–24 h on listings,
no_cache=trueonly at checkout. - Calling hotel by hotel when a location search exists. Fifty parallel calls to draw one destination page. Fix: one location search, then per-hotel detail on demand.
- Mixing occupancy and bases. A room "for 2" next to a unit "for 4", or per-night next to totals. Fix: carry
sleepsand both price bases on every record. - Treating timeouts as zero prices. Fix: distinguish
no_rate,timeoutandsold_outin schema and UI.
A worked scenario: a city-break comparison app
A travel startup builds a comparison app for weekend trips in Spain, Portugal and Italy: search a city and dates, see hotels and apartments side by side, open any property for channel-by-channel prices.
- Listing page. A Feed read of the destination's Booking location search plus one Airbnb location-rates call cached 12 h: a mixed list with a comparable total for the stay.
- Detail page. The parity call for hotels, the Airbnb listing rate for apartments, cached 2 h. A "check live price" button fires the Booking room-level call with
no_cache=true. - Signals and budget. Parity score for the hotels registered with a comp set, shown as a "best price direct" badge; customer-info polled every 10 minutes with an alert at 80 % of the monthly credit plan.
The live rate shopper recipe is the smallest version of that detail page: one property, one stay, the Booking room grid in seconds. Start there, add the parity call, and you have the core of the tool.
Frequently asked questions
What is a hotel price comparison API? An API that returns the price of a hotel stay across several sales channels in one structured format, so a developer can compare and rank them without extracting pages. Veetal Connect API exposes this through its Real Time layer (live rates from Booking.com, Expedia, Hotels.com, Google Hotels and Airbnb) and its Feed layer (pre-processed rates and parity for registered hotels).
Can I compare hotels against Airbnb listings in the same tool? Yes. Veetal's Airbnb endpoints return the live total, rate plans and refundability of a listing, or the priced listings of a whole destination. Normalize on total price for the stay and on sleeping capacity, since Airbnb prices the unit rather than a room.
How fresh are the prices?
Real Time calls fetch the price at request time, typically in 4–30 seconds depending on the channel. With the cache enabled (0–24 h, per API), repeated requests within the window return the stored response; no_cache=true forces a fresh fetch.
How many credits does a comparison cost?
Each Real Time call consumes credits that vary by endpoint; cached repeats cost nothing extra and Account endpoints are free. The free trial includes 1,000 credits, and GET /v2/account/customer-info reports your balance so you can budget per search.
Should I use Feed or Real Time for a comparison tool? Both. Feed for the recurring and configured — destination listings, registered hotels, comp sets, parity scores — because reads are cheap and history accumulates. Real Time for arbitrary properties and the moment of decision, where freshness is worth a credit.
Start building
Everything here runs on the free tier: register, resolve one hotel, call the parity endpoint and the Booking room grid, and you have your first comparison table in minutes. The trial includes 1,000 credits with no card required — start with Veetal Connect API.