# Airbnb Occupancy Rate Scraper — Calendar Availability & ADR (`kestrel/airbnb-occupancy-scraper`) Actor

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.

- **URL**: https://apify.com/kestrel/airbnb-occupancy-scraper.md
- **Developed by:** [Tedj MEABIOU](https://apify.com/kestrel) (community)
- **Categories:** Travel, Real estate, Automation
- **Stats:** 2 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

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

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Airbnb Occupancy Rate Scraper — Calendar Availability & ADR

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:

- **Availability** — `days_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.
- **Bookability** — `checkin_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 is** — `id`, `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 estimate** — `days_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 |
|---|---|---|
| Occupancy | Modelled from calendars and reviews, by market | Read from each listing's calendar, per month, with the blocked-vs-booked caveat stated |
| Rate | Modelled ADR | A priced stay per month: `rate_nightly` with the dates it was priced for |
| Coverage | Their markets and sample | Any place the site serves, any listing id, any currency and locale |
| Refresh | Monthly or weekly | When you run it — daily on a schedule if you like |
| History | Theirs | Yours: every run appends dated rows |
| Price | Subscription per market | Per occupancy row; unknown places, filtered months and errors free |
| Access | Dashboard, paid API | Apify 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

| Field | What it does |
| --- | --- |
| `locationQueries` | Places 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. |
| `startUrls` | Pasted search URLs (their filters are honoured: room type, price band, bedrooms, map bounds) or listing URLs. |
| `listingIds` | Numeric listing ids — your portfolio or a competitive set. No search needed. |
| `maxListingsPerQuery` | Distinct 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. |
| `months` | Calendar months to read per listing, 1–12, starting with the current month. One row per listing and month. |
| `includeRates` | Sample a nightly rate per month (one extra request per listing and month). Default on. |
| `adults` | Guests the sampled stay is priced for; prices depend on the guest count. Default 2. |
| `minOccupancyPct` | `0` = deliver every month. `N` = deliver only months at or above N %; the rest are dropped before billing. |
| `includeListingRow` | Also emit one free `listing` row per listing. |
| `currency` | Currency for the sampled rates (`USD`, `EUR`, `GBP`, …), echoed on every row. |
| `locale` | Site locale for listing names (`en`, `fr`, `de`, `pt`, …). |
| `sessions`, `perIp` | Parallel proxy sessions and the pace of each. The defaults work. |
| `proxyConfiguration` | Apify Proxy; datacenter proxies are accepted for these calls. |

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

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

#### Example: your listings and their competitors, weekly

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

#### Example: only the nearly-full listings of a neighbourhood

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

#### Example: a pasted search with its own filters

```json
{ "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:

```json
{
  "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:

```json
{ "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:

```json
{
  "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**:

```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):

```javascript
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:

```bash
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.

### Is it legal to scrape Airbnb occupancy and availability?

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](https://apify.com/kestrel/airbnb-prices-calendar) 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.

### Related scrapers

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

- **[Airbnb Scraper](https://apify.com/kestrel/airbnb-prices-calendar)** — prices for exact dates and guests, day-by-day calendars, listing details and reviews.
- **[Airbnb Reviews Scraper](https://apify.com/kestrel/airbnb-reviews-scraper)** — every guest review of a listing, with a rating filter that bills only the rows you keep.
- **[Google Hotels Prices Scraper](https://apify.com/kestrel/google-hotels-prices)** — hotel prices and every booking site's rate for a stay, for the hotel side of the same market.
- **[Hotel Rate Parity Checker](https://apify.com/kestrel/hotel-rate-parity)** — one row per hotel with every source side by side.
- **[Agoda Reviews Scraper](https://apify.com/kestrel/agoda-reviews-scraper)** — hotel reviews with separate positives and negatives.

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.

# Changelog

This Actor's version history is a separate document: https://apify.com/kestrel/airbnb-occupancy-scraper/changelog.md

# Actor input Schema

## `locationQueries` (type: `array`):

Places to search exactly as you would type them on Airbnb: "Lisbon, Portugal", "Algarve", "Brooklyn, NY". Every listing found gets an occupancy row per month. An unrecognised place produces a status row (unknown\_area) and costs nothing.

## `startUrls` (type: `array`):

Airbnb search URLs (their filters are honoured: room type, price band, bedrooms, map bounds) or listing URLs (https://www.airbnb.com/rooms/12345678).

## `listingIds` (type: `array`):

Numeric Airbnb listing ids — your own portfolio or a competitive set. No search needed; one occupancy row per id and month.

## `maxListingsPerQuery` (type: `integer`):

Distinct listings to take per place or search URL (Airbnb returns at most about 280 for one search; split big areas into neighbourhoods for more). The main cost control for a market run.

## `months` (type: `integer`):

How many calendar months to read per listing, starting with the current month (1–12). One occupancy row per listing and month.

## `includeRates` (type: `boolean`):

Price the first bookable stay of each month (check-in on the first available day, the listing's minimum nights) and put its nightly rate on the occupancy row — an ADR proxy. One extra request per listing and month; turn off for a cheaper, availability-only run.

## `adults` (type: `integer`):

Guests the sampled stay is priced for; Airbnb prices depend on guest count.

## `minOccupancyPct` (type: `number`):

0 = deliver every month. N = deliver only months whose occupancy is at or above N %. Months under the line are dropped BEFORE billing, so a run looking for busy listings pays only for the busy months.

## `includeListingRow` (type: `boolean`):

Also emit one free listing row per listing (name, type, location, rating, reviews) — handy when you export the dataset to a sheet.

## `currency` (type: `string`):

Currency for the sampled rates (USD, EUR, GBP, …). Echoed on every occupancy row.

## `locale` (type: `string`):

Airbnb locale for listing names (en, fr, de, es, pt, it, ja, …).

## `proxyConfiguration` (type: `object`):

Airbnb accepts Apify datacenter proxies for these calls; keep the default.

## `sessions` (type: `integer`):

Concurrent egress IPs; each paced at perIp requests per second.

## `perIp` (type: `number`):

Pace per session. Airbnb tolerated 1 request/second and short bursts in tests.

## Actor input object example

```json
{
  "locationQueries": [
    "Lisbon, Portugal"
  ],
  "startUrls": [],
  "listingIds": [],
  "maxListingsPerQuery": 3,
  "months": 2,
  "includeRates": true,
  "adults": 2,
  "minOccupancyPct": 0,
  "includeListingRow": false,
  "currency": "USD",
  "locale": "en",
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "sessions": 4,
  "perIp": 1
}
```

# Actor output Schema

## `dataset` (type: `string`):

One row per listing and month with days available, days blocked or booked, occupancy %, minimum stay and the month's sampled nightly rate; plus optional listing rows and one status row per job. Occupancy rows are billed as delivered; listing and status rows are always free.

## `summary` (type: `string`):

One JSON record with jobs, listings, occupancy rows delivered and charged, months filtered by minOccupancyPct, rate samples taken, error counts and request stats.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "locationQueries": [
        "Lisbon, Portugal"
    ],
    "maxListingsPerQuery": 3,
    "months": 2,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("kestrel/airbnb-occupancy-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "locationQueries": ["Lisbon, Portugal"],
    "maxListingsPerQuery": 3,
    "months": 2,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("kestrel/airbnb-occupancy-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "locationQueries": [
    "Lisbon, Portugal"
  ],
  "maxListingsPerQuery": 3,
  "months": 2,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call kestrel/airbnb-occupancy-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kestrel/airbnb-occupancy-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/CCScAz8wmKLa9k6y6/builds/gMMOyo0o6jTgu3uL5/openapi.json
