# Changelog of Airbnb Calendar & Availability Scraper (`integrative_tangent/airbnb-calendar-scraper`) Actor

- **URL**: https://apify.com/integrative\_tangent/airbnb-calendar-scraper/changelog.md
- **Full Actor documentation**: https://apify.com/integrative\_tangent/airbnb-calendar-scraper.md

## Changelog

### 1.2.0 — 2026-08-12 — listing identity, for free

The quote endpoint added in 1.1.0 returns `sections.metadata` regardless of
which section you ask for. That block carries the listing's name, rating,
review count and coordinates — so all of the following costs **zero additional
requests**. Run time is unchanged from 1.1.0.

#### Added

- **`title` is populated.** It has been empty in the listing-ID and URL modes
  since launch, because those modes have no search payload to draw a name from.
  The quote response has the host's own wording in `seoFeatures.title`; the
  SEO tail ("… - Aparthotels for Rent in Punta Cana … - Airbnb") is stripped,
  and Airbnb's site-wide default is rejected rather than stored as a name.
- **`reviewCount` and `rating`** at the top level.
- **`isLikelyNewListing`** — true when the listing has no reviews.

  Treat it as a signal, not a fact: Airbnb does not publish a creation date.
  Zero reviews usually means a recent launch, but it also covers a listing
  that has been up a while and never converted. Pair it with `occupancyRate`
  to separate them — no reviews plus heavy forward blocking is a new listing
  with traction; no reviews plus a wide-open calendar is not.
- **Coordinates in every input mode.** `location.lat/lng` previously existed
  only when listings came from a search; supplying bare IDs produced none. They
  now come from the quote response, which makes mapping an arbitrary set of
  listings possible.
- `listingDetails` gains `propertyType`, `locationName` and `subtitle`
  (Airbnb's generated descriptor, e.g. "Aparthotel in Punta Cana · ★4.97 ·
  1 bedroom"), kept separate from the host's own title.
- The Overview table shows reviews, rating and the new-listing flag.
- 19 more parser assertions against the live-captured metadata payload
  (36 total), including the range guards that stop an impossible rating or
  coordinate from being stored.

#### Note

These fields ride on the pricing call, so `includePricing: false` turns them
off too — that mode returns exactly what 1.0.x did.

### 1.1.0 — 2026-08-12 — nightly prices

Pricing has been the headline of this Actor's Store page since launch — the
title reads "Airbnb Availability Calendar & **Pricing** Scraper", the feature
list promises "Nightly prices in your chosen currency", and the example output
shows `"price": 85.00`. It never worked. Every `price`, `avgNightlyPrice`,
`minPrice` and `maxPrice` came back `null`.

#### Fixed

- **Nightly rates now come back populated.** The cause: `PdpAvailabilityCalendar`
  returns availability, minimum-stay rules and check-in eligibility, but carries
  no price — Airbnb only quotes once a date range is named, which is why its own
  listing page says "Add dates for prices". Rates now come from
  `StaysPdpSections` with an explicit check-in/check-out, ported from the working
  implementation in the sister `airbnb-demand-tracker` Actor.

- **The Overview table was hiding the data.** The view asked for
  `summary.occupancyRate`, `summary.avgNightlyPrice` and three more dotted
  paths; Apify Console does not resolve dotted paths in a table view and drops
  them silently. What a first-time user saw on a trial run was a listing ID, an
  empty title and a URL — an Actor that looks broken. The headline metrics are
  now also emitted as top-level fields (`occupancyRate`, `daysAvailable`,
  `daysBlocked`, `nightlyRate`, `avgNightlyPrice`, `currency`) and the view
  points at those.

- **`listingDetails` is no longer emitted as an empty object** in the listing-ID
  and URL modes, which have no search payload to fill it from.

#### Added

- `includePricing` (default **on**) and `pricingSamples` (1–4, default 1).

  Pricing costs **one extra request per listing** and roughly doubles run time
  — ~10 min → ~20 min for 1,000 listings. That is unavoidable: a price requires
  dates, and dates require another call. Set `includePricing: false` for an
  availability-only scan.

  `pricingSamples > 1` quotes several lead times (14/30/60/90 days out) and
  returns the curve. A property whose 90-day-out rate sits well above its
  14-day-out rate is being priced for a season — a signal a single number
  cannot carry.

- A `pricing` block per listing with every sample attempted, so the method is
  auditable rather than a bare number, plus `allDatesUnavailable` to separate
  "fully booked or inactive listing" from "we failed to get a price". That
  distinction matters as soon as nulls enter a market-wide average.

- 17 parser tests against payloads captured live from Airbnb on 2026-08-12
  (listing 1628404036635042929). They cover the cases that fail quietly: Airbnb
  stretching a 2-night quote to meet a 5-night minimum (dividing by the wrong
  night count overstates the rate 2.5x), discounted vs struck-through prices,
  thousands separators, and refusing to read a review count from the wrong
  section as a nightly rate.

#### Safety

Pricing is strictly additive. If the quote endpoint fails, the price fields stay
`null` and availability is untouched — byte-for-byte the behaviour before this
release. If Airbnb rotates the persisted query hash, the Actor logs it once,
disables pricing for the remainder of the run, and keeps scraping calendars
rather than repeating a doomed call per listing.
