Get Expedia and Vrbo rates
Find a hotel's Expedia id and price its rooms, and search and quote Vrbo vacation rentals.
Overview
| Task | Calls | Credits |
|---|---|---|
Price a hotel on Expedia | POST/v1/ota/expedia/search once, then POST/v1/ota/expedia per stay | 1 once, then 8 per stay |
Find and quote a Vrbo rental | POST/v1/ota/vrbo/search, then POST/v1/ota/vrbo per rental | 1 per search, 8 per quote |
Both brands run on the same Expedia Group platform, so their offers share one shape. Stays are 1-28 nights: send check_in plus nights (default 1) or check_out.
Price a hotel on Expedia
Resolve the hotel to Expedia's numeric id once and store it. Expedia ids match Hotels.com's only for newer properties (Hilton Chicago is 12570 on Expedia), so resolve each site separately. market is the point of sale and decides the currency: US (USD) or CA (CAD).
import os
import requests
API = "https://api.scrapercompany.com"
HEADERS = {"x-api-key": os.environ["SCRAPERCOMPANY_API_KEY"]}
def post(path, body):
r = requests.post(f"{API}{path}", headers=HEADERS, json=body, timeout=120)
r.raise_for_status()
return r.json()
found = post("/v1/ota/expedia/search", {"name": "Hilton Chicago", "city": "Chicago", "market": "US"})
match = found["recommended_match"] # None unless one candidate is a confident match
if match is None:
raise SystemExit(f"{found['search_status']}: {[m['name'] for m in found['matches']]}")
rates = post("/v1/ota/expedia", {
"property_id": match["property_id"], "check_in": "2026-11-30", "nights": 1, "adults": 2, "market": "US",
})
prop = rates["property"]
print(prop["available"], prop["total"], prop["currency"], prop.get("unavailable_reason"))
for room in rates["rooms"][:3]:
for offer in room["offers"]:
print(room["name"], offer["total"], offer["payment_model"], offer["refundable"], offer["cancellation_text"])rooms lists every room type with its offers: one per rate plan and payment model (a plan sold both pay-now and pay-later is two offers). Send rooms: false for the headline price only; it costs the same.
What the prices mean
{
"plan_id": "266071239",
"total": 742,
"nightly": 599,
"taxes_and_fees": 143,
"currency": "USD",
"total_text": "$742 total",
"nightly_text": "$599 nightly",
"taxes_and_fees_included": true,
"payment_model": "PAY_LATER",
"hotel_collect": true,
"refundable": true,
"cancellation_text": "Fully refundable before Nov 14",
"extras": null,
"member_only": false
}| Field | Meaning |
|---|---|
total | The stay total for all nights, as Expedia states it; taxes_and_fees_included repeats its "Total with taxes and fees" label. |
nightly | Expedia’s average nightly price before taxes and fees, rounded to whole units. |
taxes_and_fees | Derived, not read: total - nightly × nights, only when the total is labelled tax-inclusive. Accurate to within nights currency units; Expedia does not itemize taxes vs fees. |
refundable | true, false, or null when the offer states nothing (null is not "non-refundable"). cancellation_text is for display. |
payment_model | PAY_NOW, PAY_LATER or PAY_LATER_WITH_DEPOSIT; hotel_collect is true when the property takes the payment. |
The headline in property follows the point of sale's own display, which differs by market (US cards lead with the total, CA cards with the nightly price), so prices are not comparable across markets; keep market constant.
Search and quote Vrbo
Search by destination and dates (US market, up to 50 listings). Each listing has the numeric property_id that the quote takes, plus Vrbo's own nightly and stay prices.
search = post("/v1/ota/vrbo/search", {
"destination": "South Lake Tahoe, California", "check_in": "2026-11-30", "nights": 3, "adults": 2, "limit": 10,
})
print(search["summary"]["matched_properties"], "matched")
for listing in search["listings"]:
print(listing["name"], listing["total"], listing["currency"], listing["property_id"])
quote = post("/v1/ota/vrbo", {
"property_id": search["listings"][0]["property_id"], "check_in": "2026-11-30", "nights": 3, "adults": 2,
})
rental = quote["property"]
print(rental["available"], rental["total"], rental["fees_included"], rental["payment_model"], rental.get("unavailable_reason")){
"property_id": "122942416",
"listing_id": "20218736ha",
"name": "Tahoe Keys Sunset Reflections",
"summary": "House · 4 bedrooms · 4 Queen Beds",
"url": "https://www.vrbo.com/20218736ha",
"nightly": 462,
"total": 1386,
"currency": "USD",
"total_text": "$1,386 for 3 nights",
"fees_included": true,
"rank": 0
}Have a Vrbo URL instead? Pass its id as listing_id (20218736ha in vrbo.com/20218736ha). It is resolved to the property_id in the same call (one extra page load, same 8 credits); store the returned property.property_id to skip that next time.
Vrbo's total is its own figure, and fees_included repeats its "All fees included" label. Vrbo does not itemize cleaning fees, service fees or taxes for this request, so they are not invented, and refundable is always null.
When a stay can't be sold
If the stay can't be booked as asked, both quotes return 200 with property.available: false and the site's own unavailable_reason, for example "This property requires you to stay at least 5 nights". Read it and adjust the stay.
Add Expedia to a comparison
POST/v1/ota/compare takes expedia_property_id (and expedia_market, default US) next to the Booking.com, Hotels.com and Agoda ids, and asks Expedia for its headline only. Expedia's compared price_per_night is its stay total divided by the nights, because its own nightly figure is before taxes. Each priced source reports its price_basis, and comparison.by_price_basis groups sources that share one, so you compare like with like. See Checking parity.