# Airbnb Occupancy & Revenue Estimator (`apt_marble/airbnb-occupancy-revenue-estimator`) Actor

Score any Airbnb market or your own listings on a full year of availability: 30/60/90-day and 12-month occupancy, minimum-stay rules, first free night, plus estimated revenue, ADR and RevPAR from nightly rates observed for the dates you pick. Per-month breakdowns and market medians included.

- **URL**: https://apify.com/apt\_marble/airbnb-occupancy-revenue-estimator.md
- **Developed by:** [Hamza](https://apify.com/apt_marble) (community)
- **Categories:** Travel, Real estate, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.60 / 1,000 result rows

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Airbnb Occupancy & Revenue Estimator

Turn any Airbnb market — or your own list of listings — into a spreadsheet of
occupancy and earning power. For every listing you get a full year of
availability condensed into one row: how booked up it is over the next 30, 60 and
90 days and across twelve months, its minimum-stay rules, its first free night,
its longest open gap, and an estimated revenue figure built from the nightly rate
that listing was actually advertising for the dates you choose. Built for
short-let investors, property managers, revenue managers and analysts who need to
know what a street, a neighbourhood or a competitor is really earning.

### What you can do with it

- **Underwrite a purchase.** Pull every comparable listing in a postcode and read
  median occupancy, median nightly rate and implied RevPAR before you commit.
- **Benchmark your own listings.** Drop your portfolio in and see, listing by
  listing, whether you are more or less booked than the market around you.
- **Find the soft months.** The per-month breakdown shows exactly where a
  property empties out, so you can move pricing or minimum stays before it does.
- **Size a market.** Market rollups give you occupancy and rate medians split by
  month, by bedroom count and by map area, without any spreadsheet work.
- **Spot the professional operators.** Long minimum stays, year-round saturation
  and no gaps look very different from an occasional host renting out a spare room.
- **Track a market over time.** Schedule it weekly and keep a history of how
  occupancy and rates move in the areas you care about.

### What you get

One row per listing. Abridged, with real values:

```json
{
  "recordType": "listing",
  "listingId": "1458002116719302934",
  "listingUrl": "https://www.airbnb.com/rooms/1458002116719302934",
  "listingName": "Central sunny studio- terrace",
  "title": "Apartment in 2nd Arrondissement",
  "neighbourhood": "2nd Arrondissement",
  "latitude": 48.86977,
  "longitude": 2.34123,
  "roomType": "Entire home/apt",
  "propertyType": "Entire rental unit",
  "personCapacity": 4,
  "bedrooms": 1,
  "beds": 1,
  "bathrooms": 1,
  "ratingLocalized": "4.95 (42)",
  "ratingValue": 4.95,
  "reviewCount": 42,
  "isGuestFavorite": true,
  "badges": ["Guest favorite"],

  "nightlyRate": 196.57,
  "currency": "USD",
  "rateSource": "datedSearch",
  "rateObservedForCheckIn": "2026-09-10",
  "rateObservedForCheckOut": "2026-09-14",
  "totalForRateWindow": 787,
  "originalTotalForRateWindow": null,
  "discountPct": null,
  "promotions": [],

  "calendarStart": "2026-08-01",
  "calendarEnd": "2027-07-31",
  "nightsObserved": 365,
  "nightsElapsedSkipped": 0,
  "availableNights": 112,
  "unavailableNights": 253,
  "occupancyRate": 0.6932,
  "occupancyNext30": 0.4,
  "occupancyNext60": 0.45,
  "occupancyNext90": 0.4556,
  "firstAvailableDate": "2026-08-01",
  "longestAvailableRunNights": 76,
  "medianMinNights": 3,
  "modalMinNights": 3,
  "minNightsDistribution": [
    { "minNights": 1, "nights": 12 },
    { "minNights": 2, "nights": 15 },
    { "minNights": 3, "nights": 338 }
  ],
  "maxNightsObserved": 45,
  "closedToArrivalNights": 0,
  "closedToDepartureNights": 0,
  "calendarNightsWithPublishedPrice": 0,

  "monthly": [
    {
      "month": "2026-08",
      "nightsObserved": 31,
      "availableNights": 19,
      "unavailableNights": 12,
      "occupancyRate": 0.3871,
      "estimatedRevenue": 2358.84
    }
  ],

  "estimatedRevenue12mo": 49732.21,
  "estimatedRevenueNext30": 2358.84,
  "estimatedRevenueNext90": 8059.37,
  "estimatedAdr": 196.57,
  "estimatedRevPar": 136.25,
  "revenueIsEstimate": true,
  "revenueMethod": "unavailableNights × observed nightly rate",
  "bookedVsBlockedSeparable": false,

  "market": "Paris, France",
  "bboxUsed": null,
  "scrapedAt": "2026-07-30T09:14:22.108Z"
}
```

Alongside the listing rows, every run also writes **market rollup** rows
(`recordType: "marketRollup"`) holding median occupancy, median nightly rate,
implied RevPAR and listing counts — for the whole market, for each month, for
each bedroom count and for each map area you covered. Turning on
`includeNightRows` adds one row per listing-night (`recordType: "night"`) with
that night's availability, check-in and check-out eligibility and stay limits.

### Input reference

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `query` | string | `Paris, France` | The market to study — a city, neighbourhood, region or postcode, written the way you would type it into the site. Always used, including alongside a map area. |
| `bbox` | object | — | A map area to restrict the study to, as `neLat`, `neLng`, `swLat`, `swLng`. |
| `listingUrls` | array | `[]` | Study a fixed set of listings instead of a market. Accepts room addresses. Up to 2,000. |
| `listingIds` | array | `[]` | The same, as plain numeric listing ids. Up to 2,000. |
| `rateCheckIn` | date | today + 30 days | The check-in date the nightly rate is observed for. Left blank it rolls forward automatically, so a scheduled run never goes stale. |
| `rateStayLength` | integer | `4` | Length of the stay the rate is observed for, in nights. Max 28. |
| `adults` | integer | `2` | Guest count the rate is quoted for. Max 16. |
| `horizonMonths` | integer | `12` | How many months of availability to read, up to a year. |
| `calendarStartMonth` | string | current month | First month of the availability window, as `YYYY-MM`. |
| `priceMin` / `priceMax` | integer | — | Restrict the study to a nightly price window. Also used to divide a large market into slices. Max 100,000. |
| `roomTypes` | array | `[]` | Restrict to particular room types. `Entire home/apt` is the confirmed value; others are passed through as given. |
| `minBedrooms` | integer | — | Only listings with at least this many bedrooms. Max 20. |
| `maxListings` | integer | `200` | How many listings to score. Max 2,000. |
| `includeMonthlyBreakdown` | boolean | `true` | Nest the month-by-month occupancy and revenue inside each listing row. Costs nothing extra. |
| `includeNightRows` | boolean | `false` | Also write one row per listing-night. **This multiplies your row count by up to 365 per listing** — leave it off unless you need night-level detail. |
| `includePdpDetails` | boolean | `false` | Also read each listing's own summary for guest capacity, property type, bedrooms, beds and bathrooms. Adds work per listing. |
| `maxRequests` | integer | `1200` | Upper limit on how much work a single run may do, as a safety net. Max 12,000. |
| Parallel listings | integer | `4` | How many listings are worked on at the same time. Max 8. |
| `currency` | string | `USD` | Currency all prices are reported in. |
| `locale` | string | `en` | Language for listing names and labels. |
| `proxyCountry` | string | — | Country to appear to browse from. |

### Output fields

| Field | Type | Description |
| --- | --- | --- |
| `recordType` | string | `listing`, `marketRollup` or `night` |
| `listingId` / `listingUrl` / `listingName` / `title` | string | Identity, address and the listing's own headline and room-type line |
| `latitude` / `longitude` / `neighbourhood` | number / string | Location |
| `roomType` / `propertyType` / `personCapacity` | string / number | Category and guest capacity |
| `bedrooms` / `beds` / `bathrooms` | number | Layout, from the listing's own summary line |
| `ratingLocalized` / `ratingValue` / `reviewCount` | string / number | Rating as shown, as a number, and the review count |
| `isGuestFavorite` / `badges` | boolean / array | Recognition the listing carries |
| `nightlyRate` / `currency` | number / string | The nightly rate observed for your chosen dates |
| `rateSource` | string | Where the rate came from; blank when none was observed |
| `rateObservedForCheckIn` / `rateObservedForCheckOut` | date | The exact window the rate was observed in |
| `totalForRateWindow` / `originalTotalForRateWindow` / `discountPct` | number | Stay total, the pre-discount total where one was shown, and the saving |
| `promotions` | array | Any promotion wording shown on the listing |
| `calendarStart` / `calendarEnd` / `nightsObserved` | date / number | The availability window actually measured, counted forward from the day the run starts |
| `nightsElapsedSkipped` | number | Nights the published window contained that had already passed when the run began, and were therefore left out |
| `availableNights` / `unavailableNights` | number | Open nights and nights not open to the public |
| `occupancyRate` | number | `unavailableNights` over `nightsObserved`, 0–1 |
| `occupancyNext30` / `occupancyNext60` / `occupancyNext90` | number | The same over the next 30, 60 and 90 nights, counted from the day the run starts. Blank if the window you chose is shorter than that, rather than a shorter figure under a longer name |
| `firstAvailableDate` / `longestAvailableRunNights` | date / number | First free night and the longest unbroken open stretch |
| `medianMinNights` / `modalMinNights` / `minNightsDistribution` / `maxNightsObserved` | number / array | Minimum-stay profile across the year |
| `closedToArrivalNights` / `closedToDepartureNights` | number | Nights that accept no arrival or no departure |
| `calendarNightsWithPublishedPrice` | number | How many nights carried a published price — reported so you can see it for yourself rather than take our word for it |
| `monthly` | array | Per-month nights, occupancy and estimated revenue |
| `estimatedRevenue12mo` / `estimatedRevenueNext30` / `estimatedRevenueNext90` | number | Estimated revenue over each window |
| `estimatedAdr` / `estimatedRevPar` | number | Estimated average daily rate and revenue per available night |
| `revenueIsEstimate` / `revenueMethod` | boolean / string | Always set, and always states how the figure was reached |
| `bookedVsBlockedSeparable` | boolean | Always `false` — see the limits below |
| `market` / `bboxUsed` / `scrapedAt` | string / date | What was studied and when |
| `scope` / `scopeValue` / `listingCount` / `medianOccupancyRate` / `medianAdr` / `impliedRevPar` | string / number | Market rollup rows only |

### Pricing

This actor is pay-per-event, so you pay for output rather than for run time.

| What you pay for | Price |
| --- | --- |
| Each row written — listing scorecards, market rollups and, if you enable them, per-night rows | **$0.0006** ($0.60 per 1,000 rows) |
| Each listing whose full year of availability was retrieved and turned into an occupancy scorecard | **$0.012** |

The second charge applies once per listing, and only when a full year of
availability was genuinely retrieved for it. A listing that could not be read is
never charged for it.

**Worked example.** Scoring **200 listings with a full year each** costs about
**$2.52** — $0.12 of rows plus $2.40 of full-year scorecards.

The row price is kept deliberately low because `includeNightRows` can multiply
your row count by up to 365 per listing. The year-of-availability charge sits
where the real work is: one occupancy scorecard replaces a year of manually
checking a calendar, and it costs many times more to produce than an ordinary row.

### Limits & what this actor cannot do

Read this section before you rely on the numbers.

- **Revenue and ADR are estimates, not booked revenue.** Airbnb's availability
  calendar does not publish nightly prices. The nightly rate on every row is read
  from live market results for the dates *you* choose, and combined with the
  availability calendar. Every row carries `revenueIsEstimate` and
  `revenueMethod` so the basis is never in doubt. Change the dates and the
  estimate changes.
- **Unavailable nights cannot be split into "booked" and "blocked by the host".**
  A night the host closed off looks exactly like a night a guest reserved, and
  nothing public tells the two apart. That is why the field is
  `unavailableNights` and never `bookedNights`, and why every row reports
  `bookedVsBlockedSeparable: false`. Treat occupancy as an upper bound.
- **A listing that is not on the market for your dates gets no rate.** Its
  occupancy is still measured, but `nightlyRate` and every revenue field are
  blank rather than guessed at.
- **Occupancy always looks forward from the day the run starts.** Airbnb
  publishes availability a whole month at a time, so nights earlier in the
  current month are left out of every figure instead of being counted as taken —
  a night that has already gone is not a night somebody booked. Each row reports
  how many nights were left out for this reason.
- **Availability reaches twelve months ahead and no further.** That is as far as
  Airbnb publishes, so a longer horizon is not available at any price.
- **A market sweep is not exhaustive.** Airbnb shows only a few hundred listings
  for any single search and repeats itself past that point, so the actor covers a
  market by slicing it into price bands and map areas and merging the results. For
  guaranteed coverage of specific properties, supply them explicitly by address or
  id; for a full city inventory, use a dedicated city census actor.
- **Bedroom and bathroom counts come from the listing's own summary line** and
  are missing for some listings. Turning on `includePdpDetails` fills in more of
  them, but not all.
- **Ratings and review counts are as the listing displays them.** A brand-new
  listing shows no rating at all, and that is reported as a new listing rather
  than as a zero score.
- **Speed depends on the size of the job and on how quickly the source site
  responds.** No fixed throughput is promised.
- **Airbnb's terms prohibit automated access.** You are responsible for using
  this data lawfully and in line with the source site's terms, and for any
  onward use of it.

### FAQ

**Do I need an Airbnb account?**
No. None at all.

**Does it need a login, or any personal details?**
No. There is nothing to connect and nothing to sign in to — set your market and
run it.

**Can I schedule it?**
Yes. Leave `rateCheckIn` blank and the rate window rolls forward automatically
with each run, so a weekly schedule keeps producing a comparable series instead of
drifting into the past. Each run writes a fresh set of rows, so you build a
history you can chart occupancy and rate movement against.

**Is the data complete?**
For a list of listings you supply, yes — every one you name is scored. For a
market, no, and no tool can honestly claim otherwise: Airbnb shows only a few
hundred listings per search, so the actor slices the market into price bands and
map areas to see past that, and the result is a large, representative sample
rather than a proven-complete inventory.

**Why is the revenue figure an estimate?**
Because the only nightly price Airbnb publishes is the one it quotes for specific
dates, and the availability calendar carries no prices at all. So the actor reads
a real advertised rate for the dates you pick and combines it with real
availability. That is an honest estimate of earning power, not a report of money
actually taken.

**Can I get night-by-night detail?**
Yes — turn on `includeNightRows`. Be aware it adds up to 365 rows per listing, so
a 200-listing run becomes a 73,000-row dataset.

**Which currency are the prices in?**
Whichever you set in `currency`. It is applied consistently across the whole run,
so figures stay comparable.

# Actor input Schema

## `query` (type: `string`):

The city, neighbourhood or region to score, written the way you would type it into Airbnb's own search box — for example "Paris, France" or "Austin, Texas". This is always used, including when you also give a map area, because it makes the results carry their full details.

## `bbox` (type: `object`):

Restrict the market to a rectangle on the map. Give all four corners: {"neLat": 48.90, "neLng": 2.42, "swLat": 48.81, "swLng": 2.25}. The north-east corner must be north and east of the south-west one.

## `listingUrls` (type: `array`):

Score exactly these listings instead of searching a market — one Airbnb room address per line, for example https://www.airbnb.com/rooms/5398166. Up to 2,000. Nightly rates are only filled in for listings that also show up in the market results for your dates.

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

The same thing as the addresses above, but as plain numeric listing IDs — one per line. Up to 2,000.

## `rateCheckIn` (type: `string`):

The check-in date whose nightly rate is used for every revenue figure. Leave empty for 30 days from the run date, which keeps a scheduled run rolling forward. Past dates are moved forward automatically because they return no rates.

## `rateStayLength` (type: `integer`):

How many nights the rate window covers. Short windows read like weekend pricing, longer ones pick up weekly discounts.

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

Guest count used when the rate is read. Larger parties can see different prices and fewer listings.

## `horizonMonths` (type: `integer`):

How far ahead to read availability. Airbnb publishes 12 months and no further, which is the maximum here.

## `calendarStartMonth` (type: `string`):

Where the availability window starts, as YYYY-MM (for example 2026-09). Leave empty for the current month. A month in the past is moved forward, since nothing is published behind today.

## `priceMin` (type: `integer`):

Only score listings priced at or above this for your rate window. Also used to split a large market into price bands so more of it can be reached.

## `priceMax` (type: `integer`):

Only score listings priced at or below this for your rate window.

## `roomTypes` (type: `array`):

Restrict to whole homes, private rooms, shared rooms or hotel rooms. "Entire home/apt" is the value confirmed to filter as expected; the others are passed through as given and may be ignored by the site.

## `minBedrooms` (type: `integer`):

Only score listings with at least this many bedrooms.

## `maxListings` (type: `integer`):

How many listings to score at most. Airbnb shows only a few hundred listings for any single search, so large numbers are reached by splitting the market into price bands.

## `includeMonthlyBreakdown` (type: `boolean`):

Nest a per-month table inside every listing row: nights, unavailable nights, occupancy and estimated revenue for each of the 12 months. Free — it adds no extra rows.

## `includeNightRows` (type: `boolean`):

Also write a separate row for every single night of every listing, with availability, check-in and check-out rules and the minimum stay. This multiplies the number of rows by up to 365, so leave it off unless you need night-level data.

## `includePdpDetails` (type: `boolean`):

Also read each listing's own summary for guest capacity, property type, bedrooms, beds and bathrooms. Adds one extra read per listing, so it makes the run slower.

## `maxRequests` (type: `integer`):

A hard ceiling on how much work one run may do. The projected total is printed before the run starts and the run stops cleanly when the budget is spent.

## `maxConcurrency` (type: `integer`):

How many listings to work on at the same time. Higher is faster but more likely to be turned away by the site; 4 is a safe default.

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

Currency for every price and revenue figure, as a three-letter code such as USD, EUR or GBP. Currencies other than USD are untested.

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

Language for listing text, such as en, fr or de. Languages other than English are untested.

## `proxyCountry` (type: `string`):

Two-letter country code, for example us, gb or fr. Leave empty to let the actor choose. Airbnb tailors prices and availability by region, so pick the market your buyers come from.

## Actor input object example

```json
{
  "query": "Paris, France",
  "bbox": {},
  "listingUrls": [],
  "listingIds": [],
  "rateStayLength": 4,
  "adults": 2,
  "horizonMonths": 12,
  "calendarStartMonth": "",
  "roomTypes": [],
  "maxListings": 100,
  "includeMonthlyBreakdown": true,
  "includeNightRows": false,
  "includePdpDetails": false,
  "maxRequests": 1200,
  "maxConcurrency": 4,
  "currency": "USD",
  "locale": "en",
  "proxyCountry": ""
}
```

# Actor output Schema

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

No description

# 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 = {
    "query": "Paris, France",
    "bbox": {},
    "rateStayLength": 4,
    "adults": 2,
    "horizonMonths": 12,
    "maxListings": 100,
    "maxRequests": 1200,
    "maxConcurrency": 4,
    "currency": "USD",
    "locale": "en"
};

// Run the Actor and wait for it to finish
const run = await client.actor("apt_marble/airbnb-occupancy-revenue-estimator").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 = {
    "query": "Paris, France",
    "bbox": {},
    "rateStayLength": 4,
    "adults": 2,
    "horizonMonths": 12,
    "maxListings": 100,
    "maxRequests": 1200,
    "maxConcurrency": 4,
    "currency": "USD",
    "locale": "en",
}

# Run the Actor and wait for it to finish
run = client.actor("apt_marble/airbnb-occupancy-revenue-estimator").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 '{
  "query": "Paris, France",
  "bbox": {},
  "rateStayLength": 4,
  "adults": 2,
  "horizonMonths": 12,
  "maxListings": 100,
  "maxRequests": 1200,
  "maxConcurrency": 4,
  "currency": "USD",
  "locale": "en"
}' |
apify call apt_marble/airbnb-occupancy-revenue-estimator --silent --output-dataset

```

## MCP server setup

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

```

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/H5dhHCsdiQzjCV5pU/builds/LgNE9CK9IYJjcwZf6/openapi.json
