Core concepts
The ideas every endpoint shares: property identifiers, markets and currencies, how a price is broken down, provenance, batch jobs and request IDs.
Every endpoint returns a different source, but they share a handful of ideas. Read this page once and the reference pages will read faster.
Property identifiers
Each source names a hotel its own way. Resolve a property once, store its identifiers next to your own hotel record, and reuse them on every later call. Identifiers are stable, so there is no reason to search again before each price request.
| Source | Identifier | Where it comes from |
|---|---|---|
Google Hotels | token | |
Booking.com | pagename (URL slug) | POST /v1/ota/booking/search |
Expedia / Hotels.com | numeric property id, per site | POST /v1/ota/expedia/search, /v1/ota/hotels/search |
Agoda | Agoda hotel id | POST /v1/ota/agoda/search |
Tripadvisor | location_id | the number after -d in the property URL |
Airbnb | listing_id | airbnb.com/rooms/<id>, or Airbnb search |
Vrbo | property_id | Vrbo search, or resolved from a listing id |
Expedia and Hotels.com ids only match for newer properties, so resolve each site separately. When a name is ambiguous, confirm the match by address with POST /v1/hotels/resolve (see Identify a property).
Markets and currencies
A market is the point of sale a price is collected for, such as US, GB or AU. It decides the country the source sees and the default tax basis. currency sets the currency of the returned prices. GET /v1/markets lists the configured markets with their currency and whether their published rates include tax, and it is free.
How a price is broken down
Hotel prices keep their parts apart, so you can compare like with like across markets that show tax differently. A calendar row carries:
| Field | Meaning |
|---|---|
rate_base | Room-only price before tax and mandatory fees. |
tax | Taxes on the stay. |
fees | Mandatory fees. |
rate_before_taxes_with_fees | rate_base + fees, the basis many SERP APIs call extracted_price_before_taxes. |
rate_total | All-in price including tax and fees. |
rate | The figure to store for the market: rate_total when rates_include_tax is true, otherwise the pre-tax base. |
min_length_of_stay | Set when a night only sells with a longer stay; rate is then the per-night figure from that stay. |
{
"token": "ChUIoben2Mv6-CYaCi9tLzA3czVwbjQQAQ",
"stay_date": "2026-11-17",
"rate": 168.9,
"rate_base": 168.9,
"rate_before_taxes_with_fees": 174.9,
"rate_total": 199.0,
"tax": 24.1,
"fees": 6.0,
"currency": "USD",
"market": "US",
"rates_include_tax": false,
"los": 1,
"min_length_of_stay": null,
"provenance": {
"observation_id": "rateobs_…",
"collection_id": "ratecol_…",
"observed_at": "2026-11-01T06:00:12Z",
"source": "google_hotels_calendar",
"source_kind": "calendar",
"requested_currency": "USD",
"returned_currency": "USD",
"price_basis": "room_base_before_taxes_and_fees",
"derivation": "normalized_upstream"
}
}Other sources follow their own conventions, and the API does not invent missing parts. Airbnb's nightly rate is all-in before taxes, so it depends on the length of the stay. Vrbo returns its own total without an itemized fee breakdown. Each guide notes what its source provides.
Provenance
Every calendar row carries a provenance object that records where, when and how the price was observed. Store it with the price and you can always trace a number in your warehouse back to its collection.
| Field | What it tells you |
|---|---|
observed_at | UTC time of the observation. |
source | The upstream source, for example google_hotels_calendar. |
observation_id | Deterministic id of this observation ( rateobs_…). |
collection_id | Shared by every price from one upstream fetch ( ratecol_…). |
requested_currency | With returned_currency, shows whether a conversion was needed. |
price_basis | What the price represents, for example room_base_before_taxes_and_fees. |
derivation | How the figure was produced; derived_fields lists fields computed by ScraperCompany rather than read from the source. |
Live and stored rates
Price endpoints collect from the source when you call them, so a response reflects what the source showed at observed_at. To read observations that were already collected without calling a source again, use GET /v1/rates/stored, which returns freshness metadata and costs nothing.
Batch jobs and idempotency
For many properties at once, submit a job instead of looping. POST /v1/jobs takes up to 50 calendar items, stores the job before it answers and keeps running across API restarts. Poll GET /v1/jobs/{job_id}, or pass a callback_url and receive a signed webhook when the job finishes.
Every submission needs an Idempotency-Key header. Resending the same key with the same body returns the original job, so a retry after a timeout never starts a second run. The same key with a different body returns 409. See Monitor a comp set.
Request IDs
Every response carries an x-request-id header (req_…). It matches the id in your request history (GET /v1/requests) and the request_id on billing ledger entries. Include it when you contact support.
What gets billed
Each operation has a fixed credit cost per successful call, listed on its reference page. A few scale with the work: per page of results, per priced night, or per calendar surcharge. Failed requests (any 4xx or 5xx) and empty results cost nothing. Metered responses report the charge in x-credits-charged and the balance in x-credits-remaining. See Credits & billing.
Questions about this page?
Send the page link and your question, and the team will answer.