OTA compare
Fan out across every source given an identifier.
/v1/ota/compareFail-soft per source: this returns 200 with per-source status even when one OTA is blocked, because losing three good answers to one bad one is worse than a partial result the caller can see. comparison.comparable is false for a single priced source or mixed currencies; mixed-currency minima and spreads are not calculated.
Request
https://api.scrapercompany.com/v1/ota/compareAuthenticate with your API key in the x-api-key header (see Authentication).
Body
JSON object. Unknown fields are rejected with 422.
- check_instring (date)required
First night of the stay,
YYYY-MM-DD. - check_outstring (date) | null
Departure date. An alternative to
nights— if both are given, this wins. - nightsintegerdefault
1min 1, max 30Length of stay. Ignored when
check_outis supplied. - adultsintegerdefault
2min 1, max 8Adults in the room. Two is the hotel default; pass one for a hostel, which is priced per bed. Any value from 1 to 8 is accepted.
- currencystringdefault
USDISO 4217 code for the returned prices. Major currencies include AED, AUD, BRL, CAD, CHF, CNY, EUR, GBP, HKD, INR, JPY, KRW, MXN, NZD, SGD, USD; all 39 supported codes are listed in the enum.
39 allowed values
AED, ARS, AUD, BRL, CAD, CHF, CLP, CNY, COP, CZK, DKK, EGP, EUR, GBP, HKD, HUF, IDR, ILS, INR, JPY, KRW, MXN, MYR, NOK, NZD, PEN, PHP, PLN, QAR, RON, SAR, SEK, SGD, THB, TRY, TWD, USD, VND, ZAR
- booking_pagenamestring | null
Booking slug. Omit to skip Booking entirely.
- booking_countrystringdefault
usTwo-letter segment before the slug in the Booking URL.
251 allowed values
ad, ae, af, ag, ai, al, am, ao, aq, ar, as, at, au, aw, ax, az, ba, bb, bd, be, bf, bg, bh, bi, bj, bl, bm, bn, bo, bq, br, bs, bt, bv, bw, by, bz, ca, cc, cd, cf, cg, ch, ci, ck, cl, cm, cn, co, cr, cu, cv, cw, cx, cy, cz, de, dj, dk, dm, do, dz, ec, ee, eg, eh, er, es, et, fi, fj, fk, fm, fo, fr, ga, gb, gd, ge, gf, gg, gh, gi, gl, gm, gn, gp, gq, gr, gs, gt, gu, gw, gy, hk, hm, hn, hr, ht, hu, id, ie, il, im, in, io, iq, ir, is, it, je, jm, jo, jp, ke, kg, kh, ki, km, kn, kp, kr, kw, ky, kz, la, lb, lc, li, lk, lr, ls, lt, lu, lv, ly, ma, mc, md, me, mf, mg, mh, mk, ml, mm, mn, mo, mp, mq, mr, ms, mt, mu, mv, mw, mx, my, mz, na, nc, ne, nf, ng, ni, nl, no, np, nr, nu, nz, om, pa, pe, pf, pg, ph, pk, pl, pm, pn, pr, ps, pt, pw, py, qa, re, ro, rs, ru, rw, sa, sb, sc, sd, se, sg, sh, si, sj, sk, sl, sm, sn, so, sr, ss, st, sv, sx, sy, sz, tc, td, tf, tg, th, tj, tk, tl, tm, tn, to, tr, tt, tv, tw, tz, ua, ug, uk, um, us, uy, uz, va, vc, ve, vg, vi, vn, vu, wf, ws, xk, ye, yt, za, zm, zw
- hotels_property_idstring | null
Hotels.com numeric id. Omit to skip Hotels.com.
- hotels_marketstringdefault
USHotels.com point-of-sale; also picks its currency.
One of
AUCADEEUFRGBIEITNLNZUS - agoda_property_idstring | null
Agoda numeric id. Omit to skip Agoda.
- expedia_property_idstring | null
Expedia numeric id from
POST /v1/ota/expedia/search. Omit to skip Expedia. - expedia_marketstringdefault
USExpedia point-of-sale; also picks its currency.
One of
CAUS
Example request
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
200 — One night per source. Fail-soft: a blocked OTA is a row with a status. Metered responses carry x-credits-charged and x-credits-remaining headers (see credit headers).
{
"search_parameters": {
"engine": "ota_compare",
"check_in_date": "2029-02-05",
"check_out_date": "2029-02-06",
"adults": 2,
"currency": "USD",
"booking_pagename": "moxy-boston-downtown",
"agoda_property_id": "8795952"
},
"comparison": {
"status": "comparable",
"comparable": true,
"identifiers": {
"booking": "moxy-boston-downtown",
"agoda": "8795952"
},
"lowest_price": 285,
"lowest_source": "booking",
"spread": 47,
"prices": {
"booking": 285,
"agoda": 332
},
"currencies": {
"booking": "USD",
"agoda": "USD"
},
"price_provenance": {},
"reason": "Sources price on different bases (booking_display_average, total_including_taxes_and_fees); the headline minimum mixes them. Compare within `by_price_basis`.",
"price_basis_consistent": false,
"by_price_basis": {
"booking_display_average": {
"sources": [
"booking"
],
"currencies": [
"USD"
],
"lowest_source": "booking",
"lowest_price_per_night": 285
},
"total_including_taxes_and_fees": {
"sources": [
"agoda"
],
"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"
},
"agoda": {
"status": "ok",
"price_per_night": 332,
"currency": "USD",
"price_basis": "total_including_taxes_and_fees"
}
},
"meta": {
"requested": 2,
"ok": 2,
"elapsed_s": 15.89
}
}Response fields
Fields marked required are always present; others appear when they apply.
- search_parametersobjectrequired
Show 9 child fieldsHide child fields
- search_parameters.enginestringrequired
One of
ota_compare - search_parameters.check_in_datestring (date)required
- search_parameters.check_out_datestring (date)required
- search_parameters.adultsintegerrequired
- search_parameters.currencystringrequired
Requested currency; used by Booking.com and Agoda. Hotels.com and Expedia price in their market's currency.
- search_parameters.booking_pagenamestring
Present only when supplied.
- search_parameters.hotels_property_idstring
Present only when supplied.
- search_parameters.agoda_property_idstring
Present only when supplied.
- search_parameters.expedia_property_idstring
Present only when supplied.
- comparisonobjectrequired
Show 12 child fieldsHide child fields
- comparison.statusstringrequired
One of
no_pricessingle_sourcemixed_currencycomparable - comparison.comparablebooleanrequired
True when at least two sources priced in one currency.
- comparison.reasonstring | nullrequired
Why the prices are not (fully) comparable. Null only when
statusiscomparableand every price shares one basis; when bases differ it says the headline minimum mixes them. - comparison.identifiersobjectrequired
Echo of the identifiers used, keyed
booking/hotels/agoda/expedia(supplied ones only). - comparison.lowest_pricenumber | nullrequired
Null when nothing priced or currencies are mixed. May span different price bases; see
price_basis_consistent. - comparison.lowest_sourcestring | nullrequired
- comparison.spreadnumber | nullrequired
Highest minus lowest price per night; non-null only when
comparableis true. - comparison.pricesobjectrequired
Source -> price per night (priced sources only).
- comparison.currenciesobjectrequired
Source -> upper-case currency code (priced sources only).
- comparison.price_basis_consistentbooleanrequired
True when every priced source shares one price basis (also true when at most one priced).
- comparison.by_price_basisobjectrequired
Priced sources grouped by price basis (keys as in
sources.<name>.price_basis), so like is compared with like. Empty when nothing priced. - comparison.price_provenanceobjectrequired
Source -> provenance of the compared price (priced sources only).
- sourcesobjectrequired
One entry per requested source (
booking,hotels,agoda,expedia). Fail-soft: a failed source is an entry withstatus: error, not a failed request. - metaobjectrequired
Show 3 child fieldsHide child fields
- meta.requestedintegerrequired
- meta.okintegerrequired
Sources that returned a price.
- meta.elapsed_snumberrequired
Errors
Errors return a JSON body with a detail field. Failed requests are not charged. See Errors for the full list and retry advice.
| Status | Meaning | Retry? |
|---|---|---|
401Unauthorized | Missing or invalid API key. {"detail":"missing or invalid API key"} | No, fix the request |
402Payment Required | Not enough credits for this request. Only returned once credit enforcement is switched on; during the beta metering runs in shadow mode and never blocks. {"detail":"insufficient credits: this request costs 5, 0 available. Credits renew 2026-11-01."} | No, fix the request |
422Unprocessable Content | Request validation failed. {"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]} | No, fix the request |
429Too Many Requests | Too many requests: the key's requests-per-minute limit was exceeded, or (once credit enforcement is on) the plan's concurrent-request limit. No rate-limit or Retry-After headers are sent; back off and retry. {"detail":"rate limit 60/min exceeded"} | Yes, with backoff |
502Bad Gateway | The upstream source failed, blocked the request or returned an unusable answer. Safe to retry later; failed requests are not charged. {"detail":"RuntimeError"} | Yes, with backoff |
Try it
- Export your key:
export SCRAPERCOMPANY_API_KEY=sk_...(no key yet? request access). - Copy the cURL example above and run it in a terminal.
- Or open the interactive playground on api.scrapercompany.com, paste your key and pick this endpoint.