Compare OTA rates for one night
Get Booking.com, Hotels.com, Agoda and Expedia prices for the same stay in one call, or every seller Google shows.
Overview
You get | The nightly price for the same stay on Booking.com, Hotels.com, Agoda and Expedia, the lowest source, the spread and the basis of each price. |
Calls | One name search per OTA, once per hotel, then POST/v1/ota/compare per stay |
Cost | 1 credit per name search (once) + 20 credits per comparison |
Latency | Several seconds to tens of seconds: each OTA is queried live, in parallel |
Collect the OTA ids
Each OTA identifies the hotel differently. Resolve them once and store them next to your hotel record:
| OTA | Endpoint | Read |
|---|---|---|
Booking.com | recommended_match.pagename and .country | |
Hotels.com | recommended_match.property_id | |
Agoda | recommended_match.property_id | |
Expedia | recommended_match.property_id |
import os
import requests
API = "https://api.scrapercompany.com"
HEADERS = {"x-api-key": os.environ["SCRAPERCOMPANY_API_KEY"]}
hotel = {"name": "Moxy Boston Downtown", "city": "Boston"}
def resolve(path, body):
r = requests.post(f"{API}{path}", headers=HEADERS, json=body, timeout=90)
r.raise_for_status()
data = r.json()
match = data["recommended_match"] # None unless one candidate is a confident match
if match is None:
print(path, data["search_status"], [m["name"] for m in data["matches"]])
return match
booking = resolve("/v1/ota/booking/search", hotel)
hotels = resolve("/v1/ota/hotels/search", {**hotel, "market": "US"})
agoda = resolve("/v1/ota/agoda/search", hotel)
expedia = resolve("/v1/ota/expedia/search", {**hotel, "market": "US"})
ids = {
"booking_pagename": booking and booking["pagename"],
"booking_country": booking and booking["country"],
"hotels_property_id": hotels and hotels["property_id"],
"agoda_property_id": agoda and agoda["property_id"],
"expedia_property_id": expedia and expedia["property_id"],
}
print(ids)Compare in one call
Pass whichever ids you have — at least one is required. Sources run in parallel and fail independently: one OTA timing out does not fail the others.
curl -X POST "https://api.scrapercompany.com/v1/ota/compare" \
-H "x-api-key: $SCRAPERCOMPANY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"check_in": "2026-11-30",
"nights": 1,
"currency": "USD",
"booking_pagename": "moxy-boston-downtown",
"booking_country": "us",
"hotels_property_id": "38766175",
"hotels_market": "US",
"agoda_property_id": "8795952",
"expedia_property_id": "38766175",
"expedia_market": "US"
}'Response (shortened)
{
"comparison": {
"status": "comparable",
"comparable": true,
"reason": "Sources price on different bases (booking_display_average, stay_headline, total_including_taxes_and_fees); the headline minimum mixes them. Compare within `by_price_basis`.",
"lowest_price": 285,
"lowest_source": "booking",
"spread": 59,
"prices": {
"booking": 285,
"hotels": 301,
"agoda": 332,
"expedia": 344
},
"currencies": {
"booking": "USD",
"hotels": "USD",
"agoda": "USD",
"expedia": "USD"
},
"price_basis_consistent": false,
"by_price_basis": {
"booking_display_average": {
"sources": [
"booking"
],
"currencies": [
"USD"
],
"lowest_source": "booking",
"lowest_price_per_night": 285
},
"stay_headline": {
"sources": [
"hotels"
],
"currencies": [
"USD"
],
"lowest_source": "hotels",
"lowest_price_per_night": 301
},
"total_including_taxes_and_fees": {
"sources": [
"agoda",
"expedia"
],
"currencies": [
"USD"
],
"lowest_source": "agoda",
"lowest_price_per_night": 332
}
}
},
"sources": {
"booking": {
"status": "ok",
"price_per_night": 285,
"currency": "USD",
"price_basis": "booking_display_average"
},
"hotels": {
"status": "ok",
"price_per_night": 301,
"currency": "USD",
"price_basis": "stay_headline"
},
"agoda": {
"status": "ok",
"price_per_night": 332,
"currency": "USD",
"price_basis": "total_including_taxes_and_fees"
},
"expedia": {
"status": "ok",
"price_per_night": 344,
"currency": "USD",
"price_basis": "total_including_taxes_and_fees"
}
},
"meta": {
"requested": 4,
"ok": 4,
"elapsed_s": 15.89
}
}Alternative: Google offers
POST/v1/offers (3 credits) returns every seller Google shows for one night — OTAs and the hotel's own site — with per-room prices, from a single Google property token. It is cheaper and needs no OTA ids, but it reflects what Google displays rather than each OTA's own inventory. Use compare when you need the OTAs' own numbers; use offers for a quick market view.
Checking parity
- Compare like with like:
currencyapplies to Booking.com and Agoda, while Hotels.com and Expedia price in the currency of their point of sale (hotels_market,expedia_market). Checkcomparison.comparableandcurrenciesbefore comparing numbers, and keep the markets constant between runs. - Sources also price on different bases: Booking.com's display average, Hotels.com's stay headline, a tax-inclusive total on Agoda and Expedia. Each priced source reports its
price_basis. Whencomparison.price_basis_consistentis false, the headlinelowest_pricemixes bases (andreasonsays so): compare withincomparison.by_price_basis, which groups the sources that share a basis with their own lowest price. - Expedia's compared price is its stay total divided by the nights, because its own nightly figure is before taxes. For its rooms and rate plans, use Expedia rates.
- Add the hotel's own price with official-site rates to see whether an OTA undercuts the direct channel.
- Tripadvisor's metasearch offers (POST/v1/ota/tripadvisor) need a
location_idtaken from the hotel's Tripadvisor URL (the digits after-d); its name search is not available yet.