Airbnb Occupancy Rate Scraper — Calendar Availability & ADR avatar

Airbnb Occupancy Rate Scraper — Calendar Availability & ADR

Pricing

$2.00 / 1,000 occupancy row (listing × month)s

Go to Apify Store
Airbnb Occupancy Rate Scraper — Calendar Availability & ADR

Airbnb Occupancy Rate Scraper — Calendar Availability & ADR

Airbnb occupancy rate per listing and month from the public availability calendar, with a sampled nightly rate (ADR proxy) for each month. Search a place or give listing ids; get days available, days blocked or booked, occupancy % and the rate. Short term rental data at a fraction of a cent per row.

Pricing

$2.00 / 1,000 occupancy row (listing × month)s

Rating

0.0

(0)

Developer

Tedj MEABIOU

Tedj MEABIOU

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

9 days ago

Last modified

Share

An airbnb occupancy rate per listing and month, read from the listing's public availability calendar, with a sampled nightly rate for each month — the two numbers behind every short term rental data subscription, as rows you own. Point it at a place, a pasted search URL or your own listing ids and every listing comes back as one row per calendar month: days still available, days blocked or booked, occupancy_pct, the typical minimum stay, the next free night, and rate_nightly from the first bookable stay of that month. It is an airbnb calendar scraper built for the question investors and hosts actually ask, and an airdna alternative priced per row rather than per market. No login, no API key, no browser.

Last verified working: 2026-08-29.

What does the airbnb occupancy rate scraper do?

For every listing it reads the availability calendar for the next months months (1–12) and turns each month into one occupancy row:

  • Availabilitydays_total (the month's days from today onward; past days are never counted, so the current month is partial), days_available, days_unavailable and occupancy_pct = unavailable ÷ total × 100, to one decimal.
  • Bookabilitycheckin_days (available days a stay may also start on), nights_min (the most common minimum stay across the month), next_available (the first free night, null when the month is full), first_day and last_day.
  • Rate — with includeRates on (the default), the actor finds the first bookable stay of the month — check-in on the first available check-in day, the listing's minimum nights, capped at seven — and prices it for adults guests. The row carries rate_check_in, rate_check_out, rate_nights, rate_total and rate_nightly (total ÷ nights), plus rate_status so a missing number is explained (no_stay, unavailable when the page refuses the stay for that guest count, unpriced, error) rather than silently zero.
  • Who the listing isid, url, name, title, room_type, location, lat, lng, rating, reviews_count, and for search results property_type, bedrooms, beds, bathrooms, is_superhost, is_guest_favorite and the query that found it.

Two free row types travel with it: an optional listing row per listing (includeListingRow) for spreadsheets that want one line per property, and a status row per place, URL or id with counts (listings, months, filtered, rates, duplicates, calendar_errors, rate_errors, detail_errors) and the resolved_place Airbnb actually searched. Neither is ever charged.

Airbnb occupancy data: what the calendar can and cannot tell you

The public calendar answers one question per night — can a guest book it? — and nothing else. A night is unavailable because a guest booked it or because the host blocked it for their own use, maintenance or a channel-manager sync. The calendar does not distinguish the two, so occupancy_pct is an occupancy proxy: it counts blocked nights as occupied. Across a market the proxy tracks real occupancy closely, because blocked nights are a small, fairly constant share; for a single listing with a host who blocks whole months it will overstate. Read the number the way the data vendors quietly do — they model from the same calendars — and keep the days_unavailable count next to it so nobody mistakes the proxy for a booking ledger.

Three things follow from that:

  • Future months are a forward-booking curve, not history. Occupancy for next month is "how full it is so far". Run the actor on a schedule and each run appends another point, which is how you get the pace of bookings for a market.
  • The current month is partial on purpose. days_total starts today; a row for the 29th of a month has two or three days in it and says so in days_total and first_day.
  • nights_min matters as much as occupancy_pct. A listing that is 40 % available but only in one-night gaps between bookings is effectively full for a three-night minimum; checkin_days and next_available are there to make that visible.

Airbnb ADR: a nightly rate sample per month

There is no rate on the calendar for a logged-out visitor, so the actor prices a stay instead. For each month it takes the first available check-in day, the listing's minimum stay for that day (one to seven nights), checks every night of the stay is free, and asks the listing page for the price of exactly that stay and guest count. rate_nightly is the resulting total divided by the nights — the average daily rate airbnb hosts and investors compare, sampled at the start of the month rather than averaged over it. rate_total and the sampled dates are on the row so the number can be reproduced on the site.

It is one extra request per listing and month. Turn includeRates off for an availability-only run at roughly half the requests; every row then carries rate_status: "skipped" and null rate fields, and the price per row is the same.

Short term rental data for investors: comps, pace and revenue estimates

A market run — a place in locationQueries, months: 6, maxListingsPerQuery: 100 — returns 600 rows: for every listing the site ranks for that place, six months of occupancy and a rate for each. That is the raw material of airbnb market data:

  • Comps — filter by room_type, bedrooms and rating, then compare occupancy_pct and rate_nightly for the listings that look like yours. Airbnb comps are a group-by, not a subscription tier.
  • Airbnb occupancy rate by city or neighbourhood — one run per place, AVG(occupancy_pct) by month. Neighbourhood queries ("Alfama, Lisbon") resolve the way they do on the site; the status row tells you what the site searched.
  • Airbnb revenue estimatedays_unavailable × rate_nightly per month is the crude one; with a schedule you replace the crude one with the observed pace: how many nights went from available to unavailable between runs, at what rate.
  • Airbnb investment analysis — put the candidate's neighbours in listingIds, run monthly, and you have the occupancy and rate history the deal model needs, at a few cents per listing per month.

The rows are per listing, dated and yours. The str occupancy rate dashboards and short term rental analytics suites sell the aggregate of exactly this and keep the history; here the history is whatever you have run, straight from each listing's airbnb availability calendar.

AirDNA alternative: what changes and what does not

Data vendors (AirDNA, AirROI, Airbtics, Mashvisor)This actor
OccupancyModelled from calendars and reviews, by marketRead from each listing's calendar, per month, with the blocked-vs-booked caveat stated
RateModelled ADRA priced stay per month: rate_nightly with the dates it was priced for
CoverageTheir markets and sampleAny place the site serves, any listing id, any currency and locale
RefreshMonthly or weeklyWhen you run it — daily on a schedule if you like
HistoryTheirsYours: every run appends dated rows
PriceSubscription per marketPer occupancy row; unknown places, filtered months and errors free
AccessDashboard, paid APIApify API, scheduler, webhooks, n8n, Make, Zapier, MCP for AI agents

So as an airdna alternative free of a per-market subscription it is not a dashboard; it is the airbnb availability api layer — an airbnb calendar api you call with ids or places — that a dashboard, a model or a spreadsheet sits on. If you need estimates for a market you have never scraped, a vendor is faster. If you need the actual calendar of actual listings, this is the cheaper and more honest source.

Vacation rental occupancy on a schedule

Schedule the run and the dataset becomes a time series. Because every row carries fetched_at, month and the listing id, the query "how has July's occupancy for these 40 listings moved over the last eight weeks" is a pivot. Two patterns work well:

  • Portfolio watch — your listings and your competitive set in listingIds, months: 3, weekly. Alert when a competitor's rate_nightly drops under yours or when their occupancy_pct for next month passes yours.
  • Market pace — a place, maxListingsPerQuery: 50, months: 4, twice a week. Chart AVG(occupancy_pct) by month over fetched_at; the slope is the booking pace for the season.

minOccupancyPct turns a scheduled run into a filter: minOccupancyPct: 80 delivers only months at or above 80 % — the listings that are nearly full — and, because the filter runs before billing, a run that finds no such month costs nothing beyond compute. Relative scheduling needs no dates at all: the actor always starts at the current month.

Input

FieldWhat it does
locationQueriesPlaces to search as you would type them on the site: "Lisbon, Portugal", "Brooklyn, NY". An unrecognised place produces a status row with unknown_area and costs nothing.
startUrlsPasted search URLs (their filters are honoured: room type, price band, bedrooms, map bounds) or listing URLs.
listingIdsNumeric listing ids — your portfolio or a competitive set. No search needed.
maxListingsPerQueryDistinct listings per place or search URL, up to about 280 (the site's cap for one search). The main cost control for a market run.
monthsCalendar months to read per listing, 1–12, starting with the current month. One row per listing and month.
includeRatesSample a nightly rate per month (one extra request per listing and month). Default on.
adultsGuests the sampled stay is priced for; prices depend on the guest count. Default 2.
minOccupancyPct0 = deliver every month. N = deliver only months at or above N %; the rest are dropped before billing.
includeListingRowAlso emit one free listing row per listing.
currencyCurrency for the sampled rates (USD, EUR, GBP, …), echoed on every row.
localeSite locale for listing names (en, fr, de, pt, …).
sessions, perIpParallel proxy sessions and the pace of each. The defaults work.
proxyConfigurationApify Proxy; datacenter proxies are accepted for these calls.

Example: occupancy and rates for a market, six months out

{ "locationQueries": ["Lisbon, Portugal"], "maxListingsPerQuery": 100, "months": 6, "currency": "EUR" }

Example: your listings and their competitors, weekly

{ "listingIds": ["17088279", "34832567", "13055012"], "months": 3, "adults": 2, "includeListingRow": true }

Example: only the nearly-full listings of a neighbourhood

{ "locationQueries": ["Alfama, Lisbon"], "maxListingsPerQuery": 60, "months": 2, "minOccupancyPct": 80, "includeRates": false }

Example: a pasted search with its own filters

{ "startUrls": ["https://www.airbnb.com/s/Porto--Portugal/homes?room_types%5B%5D=Entire%20home%2Fapt&min_bedrooms=2"], "maxListingsPerQuery": 40, "months": 4 }

Output

One occupancy row per listing and month:

{
"type": "occupancy", "id": "17088279", "url": "https://www.airbnb.com/rooms/17088279",
"name": "Bairro Alto Refuge", "title": "Apartment in Lisbon", "property_type": "Apartment", "room_type": "Entire home/apt",
"location": "Lisbon", "lat": 38.71209, "lng": -9.14346, "rating": 4.91, "reviews_count": 374,
"bedrooms": 1, "beds": 1, "bathrooms": 1, "person_capacity": 4, "is_superhost": false, "is_guest_favorite": true, "query": "Lisbon, Portugal",
"month": "2026-09", "days_in_month": 30, "days_total": 30, "days_available": 12, "days_unavailable": 18, "occupancy_pct": 60.0,
"checkin_days": 9, "nights_min": 3, "next_available": "2026-09-08", "first_day": "2026-09-01", "last_day": "2026-09-30",
"currency": "USD", "rate_status": "ok", "rate_check_in": "2026-09-08", "rate_check_out": "2026-09-11", "rate_nights": 3, "rate_total": 791, "rate_nightly": 263.67,
"fetched_at": "2026-08-29T08:40:12+00:00"
}

A month with no bookable stay keeps its availability figures and explains the missing rate:

{ "month": "2026-10", "days_total": 31, "days_available": 0, "days_unavailable": 31, "occupancy_pct": 100.0, "next_available": null, "rate_status": "no_stay", "rate_nightly": null }

And one status row per job:

{
"type": "status", "query": "Lisbon, Portugal", "url": null, "listing_id": null, "status": "ok",
"resolved_place": "Lisbon, Portugal", "canonical_url": "/s/Lisbon--Portugal/homes", "pages": 1,
"listings": 3, "months": 6, "filtered": 0, "rates": 5, "duplicates": 0, "calendar_errors": 0, "rate_errors": 0, "detail_errors": 0, "error": null,
"fetched_at": "2026-08-29T08:40:20+00:00"
}

The dataset has four views in the Console — Overview, Occupancy by month, Listings and Run status — and exports to CSV, Excel or JSON.

How much does it cost?

Pay per event, one event per delivered occupancy row — one per listing and month. Listing rows, status rows, months dropped by minOccupancyPct, unknown places and listings whose calendar failed are free, and nothing a spending limit refused is ever delivered.

Some worked sizes at the listed per-row price:

  • One listing, 12 months: 12 rows.
  • A portfolio of 20 with their 40 competitors, 3 months, weekly: 180 rows a run, about 780 a month.
  • A market of 100 listings, 6 months: 600 rows.
  • A minOccupancyPct: 80 run that finds nothing: 0 rows, $0 beyond a fraction of a cent of compute.

Each listing costs one calendar request plus, with rates on, one request per month. The verified runs behind this listing used $0.0002–$0.004 of platform compute each. Exact per-row pricing is on this page's pricing tab.

How to use it from Python, JavaScript, curl, n8n, Make or an AI agent

Run it and read the dataset. In Python:

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("kestrel/airbnb-occupancy-scraper").call(run_input={
"locationQueries": ["Lisbon, Portugal"], "maxListingsPerQuery": 50, "months": 3, "currency": "EUR",
})
rows = [r for r in client.dataset(run["defaultDatasetId"]).iterate_items() if r["type"] == "occupancy"]
by_month = {}
for r in rows:
by_month.setdefault(r["month"], []).append(r["occupancy_pct"])
for month, pcts in sorted(by_month.items()):
print(month, round(sum(pcts) / len(pcts), 1), "% occupied across", len(pcts), "listings")

In JavaScript (Node):

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('kestrel/airbnb-occupancy-scraper').call({
listingIds: ['17088279', '34832567'], months: 3, includeListingRow: true,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const r of items.filter((i) => i.type === 'occupancy')) console.log(r.name, r.month, r.occupancy_pct, r.rate_nightly, r.currency);

With curl, synchronously, getting the dataset straight back:

curl -X POST "https://api.apify.com/v2/acts/kestrel~airbnb-occupancy-scraper/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
-H 'Content-Type: application/json' \
-d '{"listingIds":["17088279"],"months":2,"currency":"USD"}'

For n8n and Make (and Zapier), a portfolio watch is three steps: a Schedule trigger, an HTTP Request to the run-sync-get-dataset-items URL above with the JSON body, and a Google Sheets append (or a Slack message when any row's occupancy_pct or rate_nightly crosses a line you set). With minOccupancyPct set, an empty array means "nothing that full this week".

For AI agents, the actor is callable over Apify's MCP server, so an agent asked "how full are two-bedroom listings in Alfama next month, and at what rate" can run it and read the rows back with no glue code.

This reads the same public calendar and listing page a logged-out visitor sees, with no login and no account. The rows describe properties, not people: availability, a price and the listing's public attributes. Host names, contact details and guest identities are not collected. Reading public pages that carry no personal data is broadly accepted in the EU and the US (hiQ v. LinkedIn is the usual reference for public data), and the actor runs under limited permissions.

That is not the whole story: the site's terms discourage automated access, so this is a terms question rather than a criminal one, and the responsibility for a run sits with whoever runs it. Facts are not copyrightable, but a compiled database can attract sui generis protection in the EU — do not resell the raw dataset as a product. If you are unsure how this applies to your use, ask a lawyer; this is not legal advice.

Limits and honest notes

  • Occupancy is a proxy. Blocked nights and booked nights look identical in the calendar. Every number here counts both as occupied; the README, the schema and the row descriptions say so, and the count of unavailable days sits next to the percentage for that reason.
  • The rate is a sample, not an average. One stay per month — the first bookable one at the listing's minimum length, capped at seven nights, for adults guests. A month with weekend premiums or a stay priced mid-month can differ; run adults and months to match the stay you care about.
  • A search returns the site's ranking up to its cap (about 280 listings). Cover a city with neighbourhood queries or a pasted URL with map bounds.
  • A calendar covers twelve months from the current month; months is capped there.
  • Delisted or snoozed listings return a status row with an error and no charge.
  • The query hashes the site uses change occasionally. The actor refreshes them from the site's bundles, a daily canary run watches it, and failures appear as status rows rather than silent zeros.

FAQ

Does it need an Airbnb API key, login or cookies?

No. There is no public airbnb availability api for guests, no key to request and no login involved. The actor uses the public key every visitor's browser uses and no session at all.

Is occupancy the same as bookings?

No — see the section above. The calendar shows availability; a night can be unavailable because a guest booked it or because the host blocked it. occupancy_pct counts both. For a market the difference is small and steady; for a single listing check days_unavailable and the host's pattern before treating it as revenue.

How do I get the airbnb occupancy rate by city?

Put the city in locationQueries with a maxListingsPerQuery large enough to be representative, choose months, and average occupancy_pct by month over the rows. For a neighbourhood, query it by name; the status row's resolved_place confirms what the site searched.

Where does the nightly rate come from if the calendar has no prices?

From the listing page, priced for a real stay: the first bookable check-in of the month with the listing's minimum nights, for the adults you set. rate_check_in, rate_check_out and rate_nights are on the row so you can reproduce the quote; rate_status explains any missing number.

Can I track airbnb booked dates over time?

Not the dates of individual bookings — the calendar only says whether a night is free. What you can track is the change: schedule the run and compare days_available for the same listing and month across fetched_at. Nights that went from available to unavailable between runs are the booking pace.

Can I get airbnb nightly rate history?

Not retroactively — the site does not publish it. You can build it: a scheduled run appends a dated rate_nightly per listing and month every time it runs.

How is this different from the Airbnb Scraper?

The Airbnb Scraper prices a specific stay for every listing and can attach a full day-by-day calendar and reviews; it bills per listing, per calendar month and per review. This one skips the stay price, reads the calendar for everyone, folds each month into one occupancy row with a rate sample, and bills only those rows — the cheaper shape for market and portfolio occupancy work. Use both when you want the stay price and the occupancy together.

Which currencies and languages?

Any ISO currency for the rate sample and any site locale for names. currency is echoed on every row so a mixed export never guesses.

Is there a free tier?

Listing rows, status rows, filtered months, unknown places and failed calendars are always free, and Apify's free plan includes monthly platform credit you can spend on the paid rows.

From the same publisher, with the same pay-per-delivered-row billing:

All of them bill per delivered row, never charge for rows a filter or a spending limit removed, and write an Apify dataset you can export to CSV, Excel or JSON.