# Airbnb Scraper & Price Monitor - Listing Details, Availability (`neverempty/airbnb-listing-price-monitor`) Actor

For hosts and revenue managers tracking competitor listings: Airbnb listing details by room URL or ID with total and nightly price for your dates, availability and the reason if not, rating, reviews, host, Superhost, bedrooms, amenities. Monitor returns only price, availability or rating changes.

- **URL**: https://apify.com/neverempty/airbnb-listing-price-monitor.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (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 $3.60 / 1,000 listing returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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 Scraper & Price Monitor - Listing Details, Availability

Get the details of any **Airbnb listing** from its room URL or listing number, together with **the price for your dates and guests**: **total price, price per night, nightly rate and price lines**, whether it is **available** (and if not, Airbnb's own reason such as "Those dates are not available" or "Minimum stay is 3 nights"), plus **title, property type, guests, bedrooms, beds, bathrooms, rating, category ratings, number of reviews, host display name, Superhost, area and amenities**.

Turn on **monitor mode** and each run returns **only the listings whose total price, availability, rating or number of reviews changed** since the last run, with the previous values. Every change is read twice before it is reported, and small currency-conversion moves are ignored.

Unofficial. Reads only public Airbnb listing pages (`airbnb.com/rooms/...`) and the price box those pages load - the parts Airbnb's robots.txt allows. No login, no Airbnb account, no reviews pages, no photo pages, no search pages.

### What you get

| Column | Example |
|---|---|
| `listingId`, `listingUrl`, `bookingPageUrl` | 53997462, https://www.airbnb.com/rooms/53997462 |
| `title`, `titleOriginal` | Cozy apartment at historical center (`titleOriginal` is the host's own language when Airbnb shows an English translation) |
| `headline`, `propertyType`, `roomType` | Entire condo in Thessaloniki, Greece; Entire condo; Entire home/apt |
| `personCapacity`, `bedrooms`, `beds`, `bathrooms`, `bathroomLabel`, `overviewItems` | 3 guests, 1 bedroom, 2 beds, 1 bath (`bedrooms` is 0 for a studio; `null` when Airbnb does not show it, as for many private rooms) |
| `checkIn`, `checkOut`, `nights`, `adults`, `children`, `infants`, `pets`, `currency` | the stay the price is for |
| `available`, `unavailableReason` | `true` - or `false` with "Those dates are not available", "Minimum stay is 100 nights", "Maximum number of guests is 1" |
| `totalPrice`, `totalPriceText`, `priceQualifier`, `nightsQuoted` | 216, "$216", "for 3 nights", 3 |
| `pricePerNight`, `nightlyRate`, `nightlyRateText` | 72 (total ÷ nights), 71.7, "$71.70" |
| `priceLines` | `[{ "description": "3 nights x $71.70", "amountText": "$215.10", "amount": 215.1 }]` - the price details Airbnb shows |
| `originalTotalPrice`, `priceNote`, `displayPriceStyle`, `stayAsRequested` | the struck-through price when Airbnb shows a discount; Airbnb's note; `stayAsRequested` is `false` if Airbnb priced a different number of nights than you asked |
| `rating`, `reviewCount`, `categoryRatings` | 4.95 from 75 reviews; `{ cleanliness 4.92, accuracy 4.89, checkIn 4.97, communication 5, location 4.99, value 4.93 }` |
| `isGuestFavorite`, `isNewListing` | true, false |
| `hostName`, `isSuperhost`, `hostYearsHosting` | Alexia, true, 5 - the host's display name only |
| `location`, `city`, `latitudeApprox`, `longitudeApprox` | Thessaloniki, Greece; 40.63, 22.95 (rounded to 2 decimals, about 1 km) |
| `amenities` | Kitchen, Wifi, Dedicated workspace, Air conditioning, ... (only the ones marked available) |
| `checkInTime`, `checkOutTime`, `maxGuestsRule`, `petsRule`, `thumbnailUrl` | Check-in after 3:00 PM; Checkout before 11:00 AM; 3 guests maximum; No pets |
| `changeType`, `changedFields`, `previousValues`, `previousCheckedAt` | with a watch: `changed`, `["totalPrice"]`, `{ "totalPrice": 216 }` |

`totalPrice` is exactly the total Airbnb shows in the booking box for the stay (for example "$216 for 3 nights"). Airbnb rounds the displayed total; `priceLines` keeps the unrounded lines it shows. Whether taxes are included depends on the country and on how Airbnb displays prices for that stay; `displayPriceStyle` and `priceNote` carry what Airbnb says.

### Input

| Field | What it does |
|---|---|
| `listings` | Airbnb listing URLs or listing numbers, one per line. Any Airbnb country domain works (`airbnb.co.uk`, `airbnb.de`, ...) and anything after `?` is ignored. Up to 500 per run. Empty = three example listings. Search pages, experiences, wishlists, host profiles and `/h/` short links are not read. |
| `checkIn`, `checkOut` | Dates of the stay: `YYYY-MM-DD` or `+N` days from today. Empty = 30 days from today for 2 nights; only `checkIn` = 1 night. At most 90 nights. |
| `adults`, `children`, `infants`, `pets` | Guests. Default 1 adult. Too many guests or pets comes back as unavailable with Airbnb's reason. |
| `currency` | 3-letter code such as USD, EUR, GBP, CAD, AUD, JPY. Default USD. |
| `onlyChanges` | Monitor mode. Return only listings that changed (or are new to the watch). The first run returns every listing as the starting point. |
| `watchName` | Name of the remembered state, so different lists can be watched on their own schedules. Different dates, guests or currency are remembered separately. |
| `minPriceChangePercent` | Monitor mode: smallest total price move to report, in percent. Default 1. 0 reports every change. |
| `resetMonitoringState` | Forget what the watch remembered and start over. |

Example - one listing for fixed dates:

```json
{ "listings": ["https://www.airbnb.com/rooms/53997462"], "checkIn": "2026-12-18", "checkOut": "2026-12-21", "adults": 2, "currency": "EUR" }
```

Example - a daily price and availability monitor for a competitor set, always 14 days ahead for 2 nights:

```json
{ "listings": ["53997462", "12937", "1142713268362716050"], "checkIn": "+14", "checkOut": "+16", "adults": 2, "onlyChanges": true, "watchName": "comp-set" }
```

With relative dates (`+14`), the watch compares "the stay 14 days from today" run after run: a price change then also includes the effect of the date moving forward. Use fixed dates to follow one exact stay.

### Monitor mode: what counts as a change

Compared fields: `totalPrice`, `available`, `rating`, `reviewCount`.

- **Every change is confirmed by reading the listing a second time**; only fields that show the same new value in both reads are reported. If the second read fails, the change is not reported, charged or remembered, and a free `unreadable` row says so; the next run checks the listing again.
- **Small price moves are ignored**: a total price move smaller than `minPriceChangePercent` (default 1%) is not reported. Airbnb converts prices into your currency with a rate that moves every day, so a listing priced in euros shown in dollars moves by cents daily. The remembered price stays the same until the move passes the threshold, so a slow drift is still reported once it adds up.
- Going from unavailable to available (or back) is reported as a change of `available` and `totalPrice`.
- Not compared: title, amenities, host, photos and the text of the unavailability reason.

### How it behaves when something goes wrong

- **The listing does not exist or was removed** (Airbnb shows its "404 Page Not Found" page): a free `not-found` row. Not retried.
- **Airbnb redirects the listing to a search or another page**: a free `redirected` row that says where it was sent.
- **The page or the price could not be read** after asking again (twice directly, twice through a datacenter IP, then a residential IP - at most 10 residential requests per run): a free `unreadable` row. A listing without a readable price is **never sold without its price** and never reported as "unavailable". It is not remembered, so a later run returns it.
- **A check page / CAPTCHA**: this Actor does not solve or bypass it. It stops, writes a free `blocked` row and charges nothing for that listing.
- **Your maximum charge per run is reached**: it stops before reading listings it could not bill, says so in a free `budget-reached` row and does not remember them.
- **Bad input** (a search URL, a date in the past, check-out before check-in, an unknown currency code): a free `bad-input` row, nothing is requested.
- **Airbnb answers in another currency than you asked for** (it silently falls back to US dollars for codes it does not support): the price is not sold under the wrong currency name; a free `unreadable` row says which price was shown.
- A run that could not read anything and charged nothing ends with a failed status, so your integration sees it.

### Pricing

Pay per event:

- **Run start** - once per run that returns at least one listing (in monitor mode: once per run that read and compared at least one listing).
- **Listing returned** - per listing row (available or not - "not available for these dates" is an answer).
- **Listing checked** - monitor mode only, per listing that was read and had no change (the listing is not returned as a row).

Rows that explain a missing listing, an unreadable page, bad input, no change or a reached limit are free. The exact prices are on the Pricing tab.

### Notes and limits

- Data is what Airbnb shows on the public listing page, in English (en-US). The price is what Airbnb shows a signed-out visitor for your dates, guests and currency; a signed-in guest can see other prices (for example member discounts).
- Measured on 2026-09-25 from inside Apify: listing pages 10 of 10 read directly, 10 of 10 through a US datacenter IP and 10 of 10 through a residential IP; the price box 10 of 10 on each. No check page appeared. 18 listings took 50 seconds (about 3 seconds per listing, all read directly).
- Checked against the Airbnb page in a browser for 16 listings (title, rating, reviews, host, and the price or the unavailability message) in USD, GBP and JPY: all matched.
- One listing is two requests (the listing page and its price box), about 90 KB.
- Coordinates are rounded to 2 decimals on purpose. Host contact details are not collected; only the display name.
- This Actor does not read reviews, photo pages or search results (Airbnb's robots.txt disallows them). Bring the listings you want to read.
- Two schedules writing the same `watchName` at the same moment can overwrite each other's memory; give each schedule its own watch name.
- The watch memory and the run's position are saved every 25 listings and again when Apify signals that it is stopping or moving the run; a run that Apify moves to another server continues from the saved position instead of starting over.

### Support

Found a listing that is read wrongly? Open an issue in the **Issues** tab with the listing URL, your input and the run ID.

# Actor input Schema

## `listings` (type: `array`):

Airbnb listings (rooms), one per line: a listing URL such as https://www.airbnb.com/rooms/53997462 (any Airbnb country domain works, anything after ? is ignored) or just the listing number 53997462. Search pages, experiences, wishlists, host profiles and /h/ short links are not read. Up to 500 listings per run. If empty, three example listings are used.

## `checkIn` (type: `string`):

Arrival date for the price: YYYY-MM-DD (for example 2026-12-18) or +N for N days from today (for example +14). If empty, the stay starts 30 days from today.

## `checkOut` (type: `string`):

Departure date: YYYY-MM-DD or +N days from today. If empty: 1 night after checkIn, or 2 nights when checkIn is also empty. At most 90 nights.

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

Number of adults for the price and availability (1-16). If empty, 1.

## `children` (type: `integer`):

Number of children (0-15). If empty, 0.

## `infants` (type: `integer`):

Number of infants (0-5). If empty, 0.

## `pets` (type: `integer`):

Number of pets (0-5). If empty, 0. Listings that do not allow pets answer as unavailable.

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

3-letter currency code Airbnb shows prices in, such as USD, EUR, GBP, CAD, AUD, JPY. If empty, USD.

## `onlyChanges` (type: `boolean`):

Return only listings whose total price, availability, rating or number of reviews changed since the last run with the same watch name and the same dates, guests and currency, plus listings new to the watch. Each changed row lists changedFields and the previous values. A change is read twice before it is returned. The first run returns every listing as the starting point. An unchanged listing is not returned as a row; it is charged only the small listing-checked fee.

## `watchName` (type: `string`):

Name of the remembered state used to compare runs (letters, digits, dot, dash, underscore; up to 40). Setting it (or turning on monitor mode) fills changeType, changedFields and previousValues. Use a different name for each list of listings you track on its own schedule. With monitor mode on and no name, the name "default" is used. Different dates, guests or currency are remembered separately.

## `minPriceChangePercent` (type: `number`):

In monitor mode, a total price move smaller than this percentage is not reported (currency conversion moves prices a little every day). Small moves add up: the remembered price stays the same until the move passes this size. 0 reports every change. If empty, 1.

## `resetMonitoringState` (type: `boolean`):

Start this watch over: forget the remembered listings before this run, so every listing is returned as a first check.

## Actor input object example

```json
{
  "listings": [
    "https://www.airbnb.com/rooms/53997462",
    "https://www.airbnb.com/rooms/12937"
  ],
  "checkIn": "+30",
  "checkOut": "+32",
  "currency": "USD",
  "onlyChanges": false,
  "resetMonitoringState": false
}
```

# Actor output Schema

## `results` (type: `string`):

One row per Airbnb listing: title, property and room type, guests, bedrooms, beds, bathrooms, rating and category ratings, review count, host display name, Superhost, area and rounded coordinates, amenities, check-in and check-out times, and for your dates and guests: available or the reason it is not, total price, price per night, nightly rate and price lines. With a watch: changeType, changedFields and the previous values. A missing listing, a page that could not be read, an unusable input, a run with no change or a run that hit its maximum charge comes back as a free row that says why.

# 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 = {
    "listings": [
        "https://www.airbnb.com/rooms/53997462",
        "https://www.airbnb.com/rooms/12937"
    ],
    "checkIn": "+30",
    "checkOut": "+32",
    "currency": "USD"
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/airbnb-listing-price-monitor").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 = {
    "listings": [
        "https://www.airbnb.com/rooms/53997462",
        "https://www.airbnb.com/rooms/12937",
    ],
    "checkIn": "+30",
    "checkOut": "+32",
    "currency": "USD",
}

# Run the Actor and wait for it to finish
run = client.actor("neverempty/airbnb-listing-price-monitor").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 '{
  "listings": [
    "https://www.airbnb.com/rooms/53997462",
    "https://www.airbnb.com/rooms/12937"
  ],
  "checkIn": "+30",
  "checkOut": "+32",
  "currency": "USD"
}' |
apify call neverempty/airbnb-listing-price-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/airbnb-listing-price-monitor"
        }
    }
}
```

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/aTdmusKHzUtIddxTM/builds/8vvGrNN0Ydm5GVzoV/openapi.json
