Skip to content
Google Hotels

Calendar

Forward horizon of nightly rates — the cheap bulk path.

POST/v1/calendar
5+ credits5, plus 3 per night with basis: mainstream and 3 per verify_sources spot-check (failed requests are free)

Request

POSThttps://api.scrapercompany.com/v1/calendar

Authenticate with your API key in the x-api-key header (see Authentication).

Body

JSON object. Unknown fields are rejected with 422.

  • tokenstringrequiredmin length 8

    Google property token. Get one from POST /v1/search — it is the token on each match. Looks like ChUIoben2Mv6-CYaCi9tLzA3czVwbjQQAQ.

  • marketstring | null

    Market/country, e.g. US, MX, AU, GB. Sets gl and the default tax basis. Omit to derive it from currency.

    18 allowed values

    AG, AT, AU, BE, BZ, CA, CH, DE, ES, FR, GB, IE, IT, MX, NL, NZ, PT, US

  • countrystring | null

    Two-letter gl override. Normally set by market.

    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

  • currencystring | null

    ISO currency for the returned prices.

    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

  • daysintegerdefault 90min 1, max 330

    Nights to price, starting at start. Max 330.

  • startstring (date) | null

    First stay date. Defaults to tomorrow.

  • adultsintegerdefault 2min 1, max 8

    Occupancy: number of adults, 1-8. Children are not supported by the Google calendar.

  • losintegerdefault 1min 1, max 30

    Length of stay per quote. Nightly price genuinely moves with LOS.

  • rates_include_taxboolean | null

    Overrides the market default; pass the property's own setting

  • is_hostelbooleandefault false

    Hostels are priced per bed rather than per room, so occupancy changes the rate differently. Set this and the per-person maths is applied.

  • probe_min_staybooleandefault true

    Re-ask for any night that will not price as a single night. Google returns a two-night-minimum night identically to a sold-out one — empty, with no reason and no min-stay field — so without this, weekends silently vanish. On a beach market in August that was every Friday and Saturday on half the comp set. Filled rows carry min_length_of_stay and a per-night rate derived from the longer stay. Costs one extra call per probe level, not per date.

  • basisstringdefault cheapestpattern ^(cheapest|mainstream)$

    What the nightly figure should mean. `cheapest` (default) is the fast calendar path: one small request for the whole horizon. It returns Google's lowest bookable rate, including aggregators. The base/fees/taxes/total split and SearchAPI-compatible rate_before_taxes_with_fees remain populated. `mainstream` also returns rate_mainstream_total / rate_mainstream_base / mainstream_source / aggregator_discount — the cheapest rate on Booking, Expedia, Hotels.com, Priceline, Tripadvisor or Agoda, which is what a comp-set audit means by 'the OTA rate'. rate_total is left untouched, so both bases are available. This is intentionally expensive: it samples multi-megabyte offers pages and, when an aggregator sets the floor, re-prices each date. Measured on a 14-day request it took about 8s versus 0.4s for cheapest; use it selectively. basis_applied reports which path ran.

    One ofcheapestmainstream

  • verify_sourcesintegerdefault 0min 0, max 5

    Spot-check this many nights against the per-OTA offers page and return a source_check block. The Google calendar gives one price per night and never says which source it came from — usually a mainstream OTA, but where a discounter undercuts them it reports the discounter. Measured on four properties: three matched the cheapest mainstream OTA to the cent, the fourth's figure was Super.com's at 15% under. Costs one ~3 MB offers fetch per sampled night, so it is a sample, not every date.

Example request

curl -X POST "https://api.scrapercompany.com/v1/calendar" \
  -H "x-api-key: $SCRAPERCOMPANY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "token": "ChUIoben2Mv6-CYaCi9tLzA3czVwbjQQAQ",
    "market": "US",
    "currency": "USD",
    "days": 90
  }'

Response

200 — One row per stay date. ~30 KB for a 90-night window. Metered responses carry x-credits-charged and x-credits-remaining headers (see credit headers).

200 response
{
  "token": "ChUIoben2Mv6-CYaCi9tLzA3czVwbjQQAQ",
  "market": "US",
  "collection_id": "ratecol_0123456789abcdef0123456789abcdef",
  "observed_at": "2026-08-07T01:23:45.678Z",
  "requested_days": 90,
  "coverage": 0.9333,
  "wire_bytes": 29804,
  "elapsed_s": 0.31,
  "calendar_elapsed_s": 0.14,
  "unpriced_dates": [
    "2029-01-10"
  ],
  "validation": {
    "ok": true,
    "errors": [],
    "warnings": []
  },
  "tax_profile": {
    "rate": 0.2032,
    "confidence": "high",
    "itemised": 84,
    "derived": 0
  },
  "rates": [
    {
      "stay_date": "2028-12-26",
      "rate": 332.01,
      "rate_base": 332.01,
      "rate_total": 424.48,
      "tax": 67.47,
      "fees": 25,
      "currency": "USD",
      "market": "US",
      "rates_include_tax": false,
      "adults": 2,
      "los": 1,
      "implied_tax_rate": 0.203217,
      "provenance": {
        "schema_version": 1,
        "observation_id": "rateobs_0123456789abcdef0123456789abcdef",
        "collection_id": "ratecol_0123456789abcdef0123456789abcdef",
        "observed_at": "2026-08-07T01:23:45.678Z",
        "source": "google_hotels_calendar",
        "source_kind": "calendar",
        "collector": "scrapingme.google_calendar",
        "source_property_id": "ChUIoben2Mv6-CYaCi9tLzA3czVwbjQQAQ",
        "requested_market": "US",
        "requested_currency": "USD",
        "returned_currency": "USD",
        "egress_mode": "direct",
        "price_basis": "room_base_before_taxes_and_fees",
        "derivation": "normalized_upstream"
      }
    }
  ]
}

Response fields

Fields marked required are always present; others appear when they apply.

  • tokenstringrequired
  • collection_idstringrequired

    Shared by every row of this collection (ratecol_...).

  • observed_atstringrequired

    UTC observation time, ISO-8601.

  • marketstringrequired
  • requested_daysintegerrequired

    Nights requested (capped at 330).

  • coveragenumberrequired

    Share of requested nights that returned a priced row, 0-1 rounded to 4 decimals (e.g. 0.9333 = 84 of 90). A number, not an N/M string.

  • wire_bytesintegerrequired
  • elapsed_snumberrequired

    End-to-end seconds for this request.

  • calendar_elapsed_snumberrequired

    Seconds spent on the upstream calendar fetch.

  • egress_modestringrequired

    One ofdirectproxy_fallback

  • tax_profileobject | nullrequired

    The property's effective tax profile, derived from the returned nights.

    Show 4 child fields
    • tax_profile.ratenumberrequired

      Median tax / base ratio observed across nights.

    • tax_profile.confidencestringrequired

      One ofhighlownone

    • tax_profile.itemisedintegerrequired

      Nights where the tax split was itemised.

    • tax_profile.derivedintegerrequired

      Nights where only a total was given and the split was derived.

  • unpriced_datesarray of string (date)required

    Requested nights with no bookable offer.

  • validationobjectrequired

    Plausibility checks on the returned rows.

    Show 3 child fields
    • validation.okbooleanrequired

      False when any error-level finding exists.

    • validation.errorsarray of stringrequired
    • validation.warningsarray of stringrequired
  • booking_windowobject

    How far out the property actually sells. Present on successful collections.

    Show 6 child fields
    • booking_window.requested_daysintegerrequired
    • booking_window.priced_daysintegerrequired
    • booking_window.last_priced_dayinteger | nullrequired

      1-based day of the last priced night.

    • booking_window.contiguous_daysinteger

      Priced nights from the start with no gap (absent when nothing priced).

    • booking_window.dry_after_dayintegerrequired
    • booking_window.notestringrequired
  • basis_appliedstring

    Present only with basis: mainstream.

    One ofmainstream:annotatedmainstream:calendar-already-matched

  • source_checkobject

    Present only when verify_sources > 0 or basis: mainstream sampled nights against the offers page.

    Show 6 child fields
    • source_check.sampledintegerrequired
    • source_check.mainstream_sourcesarray of stringrequired
    • source_check.calendar_matches_mainstreambooleanrequired
    • source_check.notestringrequired
    • source_check.checksarray of objectrequired
      Show 6 child fields
      • source_check.checks[].stay_datestring (date)required
      • source_check.checks[].calendar_totalnumber | nullrequired
      • source_check.checks[].cheapest_mainstreamnumber | nullrequired
      • source_check.checks[].cheapest_any_sourcenumberrequired
      • source_check.checks[].cheapest_sourcestringrequired
      • source_check.checks[].calendar_is_mainstreambooleanrequired
    • source_check.repriceobject

      Present when mainstream re-pricing ran.

      Show 2 child fields
      • source_check.reprice.replacedintegerrequired
      • source_check.reprice.dropped_no_mainstream_offerintegerrequired
  • ratesarray of objectrequired

    One row per priced night.

    Show 20 child fields
    • rates[].tokenstringrequired

      Google property token.

    • rates[].stay_datestring (date)required

      Stay (check-in) date.

    • rates[].ratenumberrequired

      The market-correct figure to store: all-in (rate_total) when rates_include_tax is true, otherwise the pre-tax base. For POST /v1/stay it is the whole-stay figure.

    • rates[].rate_basenumber | nullrequired

      Room-only price before tax and mandatory fees.

    • rates[].rate_before_taxes_with_feesnumber | nullrequired

      rate_base + fees (SearchAPI's extracted_price_before_taxes basis).

    • rates[].rate_totalnumber | nullrequired

      All-in price including tax and fees.

    • rates[].taxnumber | nullrequired
    • rates[].feesnumber | nullrequired
    • rates[].currencystringrequired
    • rates[].marketstringrequired
    • rates[].rates_include_taxbooleanrequired

      Which basis rate uses.

    • rates[].adultsintegerrequired
    • rates[].losintegerrequired

      Length of stay the price was quoted for.

    • rates[].min_length_of_stayinteger | nullrequired

      Set when the night only prices at a longer stay; rate is then a per-night figure derived from that stay.

    • rates[].rate_mainstream_basenumber | nullrequired

      Cheapest mainstream-OTA base price (only populated with basis: mainstream).

    • rates[].rate_mainstream_totalnumber | nullrequired

      Cheapest mainstream-OTA all-in price (only populated with basis: mainstream).

    • rates[].mainstream_sourcestring | nullrequired

      OTA that produced the mainstream figure.

    • rates[].aggregator_discountnumber | nullrequired

      rate_mainstream_total - rate_total when both exist.

    • rates[].implied_tax_ratenumber | nullrequired

      tax / rate_base.

    • rates[].provenanceobjectrequired

      Provenance of one calendar row.

      Show 17 child fields
      • rates[].provenance.schema_versionintegerrequired

        Provenance schema version (currently 1).

      • rates[].provenance.observation_idstringrequired

        Deterministic id of this price observation (rateobs_...).

      • rates[].provenance.collection_idstringrequired

        Id shared by every price from one upstream fetch (ratecol_...).

      • rates[].provenance.observed_atstringrequired

        UTC observation time, ISO-8601.

      • rates[].provenance.sourcestringrequired

        Upstream source, e.g. google_hotels_calendar.

      • rates[].provenance.source_kindstringrequired

        Kind of source, e.g. calendar, offer, ota_calendar, official.

      • rates[].provenance.collectorstringrequired

        Identifier of the collector that produced the price.

      • rates[].provenance.source_property_idstringrequired

        Property identifier at the source.

      • rates[].provenance.requested_marketstring | nullrequired
      • rates[].provenance.requested_currencystring | nullrequired
      • rates[].provenance.returned_currencystring | nullrequired
      • rates[].provenance.egress_modestringrequired

        How the request reached the source, e.g. direct.

      • rates[].provenance.price_basisstringrequired

        What the price represents, e.g. room_base_before_taxes_and_fees.

      • rates[].provenance.derivationstringrequired

        How the figure was derived, e.g. normalized_upstream.

      • rates[].provenance.upstream_rate_idstring | nullrequired

        Source-native rate/room id when available.

      • rates[].provenance.derived_fieldsarray of stringrequired

        Fields computed by ScraperCompany rather than read from the source.

      • rates[].provenance.mainstreamobject

        Present only when mainstream figures were added (basis: mainstream).

Errors

Errors return a JSON body with a detail field. Failed requests are not charged. See Errors for the full list and retry advice.

StatusMeaningRetry?
400Bad Request

Bad request: the parameters are well-formed but unusable (for example an unknown market or a malformed token).

{"detail":"end must be on or after start"}
No, fix the request
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
404Not Found

The property, stay, job or record was not found.

{"detail":"job not found"}
No, fix the request
422Unprocessable Content

Request validation failed.

{"detail":[{"loc":["body","token"],"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

  1. Export your key: export SCRAPERCOMPANY_API_KEY=sk_... (no key yet? request access).
  2. Copy the cURL example above and run it in a terminal.
  3. Or open the interactive playground on api.scrapercompany.com, paste your key and pick this endpoint.

Credits

From 5 credits per successful call (5, plus 3 per night with basis: mainstream and 3 per verify_sources spot-check (failed requests are free)). Failed, blocked and empty results are free. Credit metering is in beta and does not block requests yet.