# Airbnb Daily Rate & Comp-Set Price Tracker (`apt_marble/airbnb-price-tracker`) Actor

Track Airbnb nightly rates for any market or pinned comp set across many check-in dates and stay lengths. Every row carries the nightly and total price, discounts, promotions, ratings and the move since your last run, plus a median, quartile and spread summary for each date. No account needed.

- **URL**: https://apify.com/apt\_marble/airbnb-price-tracker.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

$0.50 / 1,000 row saveds

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 Daily Rate & Comp-Set Price Tracker

Watch what Airbnb is actually charging. Pick a market — "Paris, France",
"Austin, Texas", a neighbourhood drawn on the map — choose how far ahead you want
to look and how long a stay you want quoted, and every run comes back with one
row per listing per date window: the nightly figure, the stay total, the discount
and how big it is, the promotions and badges on the card, the rating and review
count, and how the price moved since the last time you looked. Each date window
also gets a market summary row with the median, quartile, minimum and maximum
nightly figures, so you can see the whole distribution and not just your own
neighbours. Pin a comp set of listings and you get a rate sheet for exactly those
properties, every day, with the ones that sold out flagged instead of silently
missing. No Airbnb account, no login, nothing to connect.

### What you can do with it

- **Price your own listing against its real comp set.** Pin the ten properties
  you lose bookings to, run it every morning, and see the day they cut their
  nightly figure.
- **Read the booking curve.** Price the same market 7, 14, 30, 60 and 90 days out
  in one run and watch how far in advance rates firm up in your area.
- **Catch discounts the day they start.** `newlyDiscounted` and `discountEnded`
  fire only on the run where the change happened, so a daily digest is short and
  readable.
- **Benchmark a whole market.** The per-window summary gives you the median,
  the quartiles and the spread for every date you price, so you can say
  "we are 12 % over the median for late September" with a number behind it.
- **Compare neighbourhoods.** Add map areas and each one gets its own median for
  the same dates.
- **Track weekend versus midweek pricing.** Every row carries the check-in day of
  the week and the stay length, so a two-night Friday and a five-night Monday are
  separate, comparable series.
- **Feed a pricing model or a dashboard.** Schedule it, keep the tracker name
  stable, and each run appends a fresh, dated slice with the change already
  computed.

### What you get

One row per listing per date window. Abridged example:

```json
{
  "recordType": "priceRow",
  "listingId": "1458002116719302934",
  "listingUrl": "https://www.airbnb.com/rooms/1458002116719302934",
  "listingName": "Central sunny studio- terrace",
  "title": "Apartment in 2nd Arrondissement",
  "roomTypeText": "Apartment",
  "latitude": 48.86977,
  "longitude": 2.34259,
  "checkIn": "2026-09-10",
  "checkOut": "2026-09-14",
  "nights": 4,
  "quotedNights": 4,
  "leadTimeDays": 30,
  "dayOfWeekCheckIn": "Thursday",
  "nightlyAmount": 196.57,
  "totalAmount": 787,
  "originalTotalAmount": null,
  "discountAmount": null,
  "discountPct": null,
  "priceLineType": "QualifiedDisplayPriceLine",
  "priceLabel": "$787 for 4 nights",
  "currency": "USD",
  "promotions": [
    { "type": "PAY_ZERO_UPFRONT_HIGHLIGHT", "text": "Pay $0 today" },
    { "type": "FREE_CANCELLATION_HIGHLIGHT", "text": "Free cancellation" }
  ],
  "badges": ["Guest favorite"],
  "isGuestFavorite": true,
  "freeCancellation": true,
  "ratingLocalized": "4.95 (42)",
  "ratingValue": 4.95,
  "reviewCount": 42,
  "previousNightlyRate": 188.25,
  "priceChangePct": 4.42,
  "comparisonBasis": "sameLeadTime",
  "newlyDiscounted": false,
  "discountEnded": false,
  "firstSeen": "2026-07-24T06:02:11.104Z",
  "lastSeen": "2026-07-30T06:01:58.771Z",
  "runsObserved": 7,
  "notFoundInWindow": false,
  "market": "Paris, France",
  "priceBandUsed": "80-400",
  "scrapedAt": "2026-07-30T06:01:58.771Z"
}
```

And one summary row per date window:

```json
{
  "recordType": "windowAggregate",
  "market": "Paris, France",
  "checkIn": "2026-09-10",
  "checkOut": "2026-09-14",
  "nights": 4,
  "leadTimeDays": 30,
  "listingsPriced": 187,
  "medianNightly": 300.05,
  "p25Nightly": 238.01,
  "p75Nightly": 340.36,
  "minNightly": 196.57,
  "maxNightly": 380.16,
  "discountingSharePct": 37.5,
  "medianDiscountPct": 12.98,
  "freeCancellationSharePct": 87.5,
  "currency": "USD",
  "medianNightlyPreviousRun": 291.4,
  "medianChangePct": 2.97,
  "medianComparisonBasis": "sameLeadTime",
  "scrapedAt": "2026-07-30T06:01:58.771Z"
}
```

### Input reference

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `query` | string | `Paris, France` | The market to price, written the way you would type it into Airbnb. Always used, including alongside map areas — without it the rows lose their property-type line and review counts. |
| `bboxes` | list | empty | Optional map areas, each priced separately so a big city can be split into neighbourhoods. Give `"neLat,neLng,swLat,swLng"` or an object with those four keys. Up to 10. |
| `listingIds` | list of strings | empty | Optional comp set: numeric listing ids or room addresses. Only these listings are written as rate rows, and one with no availability in a window is still written with `notFoundInWindow` set. Up to 500. |
| `leadTimeDays` | list of integers | `[7, 14, 30, 60, 90]` | How far ahead of today each check-in sits. Up to 8 values, each 0–365. |
| `stayLengths` | list of integers | `[2, 5]` | How many nights each quoted stay covers. Up to 4 values, each 1–28. |
| `adults` | integer | `2` | Guest count the prices are quoted for. Keep it stable between runs or the series stops being comparable. |
| `priceMin` / `priceMax` | integer | empty | Restrict the comp set to a nightly price band. Bands also let you see deeper into a busy market: run the same market once per band and merge. Up to 100,000. |
| `roomTypes` | list | empty | Entire home or apartment, private room, shared room, hotel room. |
| `minBedrooms` | integer | empty | Only price listings with at least this many bedrooms. Up to 20. |
| `maxListingsPerWindow` | integer | `200` | How many listings to price for each check-in and stay length. Up to 1,000. |
| `stateKey` | string | `default` | Names the price history this run compares against. Same name builds a series; a different name tracks a second market independently. |
| `minChangePctToFlag` | integer | `1` | How far a nightly figure has to move before it counts as a change. Up to 100. |
| `emitUnchanged` | boolean | `true` | On, every priced listing is written every run. Off, only listings whose price moved, whose discount started or ended, or that are newly seen — a much smaller daily digest. The per-window market summaries are written either way, so the medians never have a gap. |
| `maxRequests` | integer | `400` | Safety cap on how much work one run may do, up to 4,000. Checked before anything starts: coverage inside each window is trimmed first, then the longest-lead dates are dropped, so the near-term dates always survive. |
| Date windows in parallel | integer | `4` | How many date windows are priced at the same time, up to 8. Higher is faster and heavier on the source site. |
| `currency` | string | `USD` | Currency every price is quoted in. Pin it and keep it fixed. |
| `locale` | string | `en` | Language for the text Airbnb returns. English is the tested option. |
| `proxyCountry` | string | `us` | Country to appear to browse from. |

### Output fields

| Field | Type | Description |
| --- | --- | --- |
| `recordType` | string | `priceRow` for a listing, `windowAggregate` for a market summary. |
| `listingId` / `listingUrl` | string | Airbnb listing id and a direct link to it. |
| `listingName` / `title` / `roomTypeText` | string | Listing name, Airbnb's property-and-area line, and the property type pulled out of it. |
| `latitude` / `longitude` | number | Listing coordinates. |
| `checkIn` / `checkOut` / `nights` / `leadTimeDays` / `dayOfWeekCheckIn` | date / number / string | The exact stay you asked to have priced, and how far ahead of the run it sat. `nights` always equals `checkOut` minus `checkIn`. |
| `quotedNights` | number | The stay length Airbnb's own price line described. Normally identical to `nights`; when it differs, the figures on that card are for a different stay length than the one you asked for. |
| `nightlyAmount` / `totalAmount` | number | Price per night and for the whole stay. |
| `originalTotalAmount` / `discountAmount` / `discountPct` | number | Pre-discount total and the size of the discount, only when there genuinely is one. |
| `priceLineType` / `priceLabel` | string | Which of Airbnb's two price presentations the row used, and the exact wording it showed. |
| `currency` | string | Currency the figures are in. |
| `promotions` | array | Promotion pills on the card, such as *Pay $0 today* or *Free cancellation*. |
| `badges` / `isGuestFavorite` / `freeCancellation` | array / boolean | Badges shown on the card and the two flags most people filter on. |
| `ratingLocalized` / `ratingValue` / `reviewCount` | string / number | Rating as Airbnb wrote it, and as numbers. A brand-new listing shows `New` and no numeric rating. |
| `previousNightlyRate` / `priceChangePct` | number | What this listing cost on the previous run of the same tracker, and the percentage move. |
| `comparisonBasis` | string | How that comparison was made: `sameDates` when the very same stay dates were priced before, `sameLeadTime` when the previous run looked the same distance ahead for the same stay length, and empty when there was nothing to compare against. |
| `newlyDiscounted` / `discountEnded` | boolean | True only on the run where the discount started or stopped. |
| `firstSeen` / `lastSeen` / `runsObserved` | date / number | When this listing-and-date pair first and last had a price, and how many runs have priced it. |
| `notFoundInWindow` | boolean | True when a pinned listing had no availability for those dates. |
| `market` / `priceBandUsed` | string | Which market or map area produced the row, and the price band it was collected under. |
| `listingsPriced` | number | Summary rows: how many listings the distribution is based on. |
| `medianNightly` / `p25Nightly` / `p75Nightly` / `minNightly` / `maxNightly` | number | Summary rows: the market distribution for that date window. |
| `discountingSharePct` / `medianDiscountPct` / `freeCancellationSharePct` | number | Summary rows: how much of the market is discounting, by how much, and how much offers free cancellation. |
| `medianNightlyPreviousRun` / `medianChangePct` / `medianComparisonBasis` | number / string | Summary rows: the market median on the previous run, the move, and which of the two comparisons above produced it. |
| `scrapedAt` / `actorRunId` | date / string | When the row was collected, and which run produced it — so several days of output can be merged and still be told apart. |

### Pricing

This actor is **pay per event**, and there is exactly one event: **Row saved**,
at **$0.0005** — $0.50 per 1,000 rows. It fires once for every row written to the
dataset, whether that row is a priced listing, a pinned listing that had no
availability, or a market summary. Nothing else is ever charged.

A 200-listing comp set across 10 date windows produces 2,000 rows and costs about
**$1.00**. A daily digest with unchanged rows switched off costs a fraction of
that, because only the listings that actually moved are written. A window where
Airbnb has nothing to show writes nothing and therefore costs nothing.

Single-tier pricing is deliberate. Every row this actor produces arrives dozens
at a time in the same batch, so a second, dearer charge would be a markup with no
extra effort behind it. Change detection is free: comparing today's rate sheet
with yesterday's costs nothing to do, so it is not billed.

### Limits & what this actor cannot do

- **Airbnb shows only a few hundred listings for any one search, and repeats
  itself past that point.** A wide market is covered by splitting it into price
  bands and map areas and merging the results, which reduces but does not remove
  the ceiling. A run over a very large city is a large, well-distributed sample,
  not a census.
- **Cleaning and service fees are not itemised.** The nightly figure Airbnb
  publishes is already all-in for the stay it quotes, and taxes are not shown at
  all. There is no way to split it into a base rate plus fees from public
  information.
- **A pinned listing with no availability for your dates will not appear** in that
  window's market results, and is written with `notFoundInWindow` set instead. A
  blank there means "not bookable for these dates", not "no longer on Airbnb".
- **Change detection compares against the previous run of the same tracker
  name**, so the first run reports no changes at all, and renaming the tracker
  starts a fresh history.
- **Dates move with the run.** You choose how many days ahead to look, so a
  tracker run again tomorrow is looking at stays one day later. When the very
  same dates have been priced before, the comparison is date-for-date; otherwise
  it is like-for-like — the same listing, the same distance ahead, the same stay
  length — which is the comparison a revenue manager actually wants. Every row
  says which of the two it used, so the two are never silently mixed.
- **Prices are what Airbnb shows for the dates, guest count and currency you
  choose.** Change any of those between runs and the series stops being
  comparable — which is why the guest count and the currency are pinned inputs.
- **Airbnb's own results move between identical searches**, so a market median is
  a snapshot of what was on offer at that moment, not an audited figure.
- **English is the tested language.** Other languages are accepted but the text
  Airbnb returns for them has not been verified.
- **Speed depends on the size of the job and on Airbnb's own response times.** No
  fixed throughput is promised.
- **Airbnb's terms prohibit automated access.** You are responsible for using the
  data lawfully and in line with the source site's terms, including how you store
  and share it.

### FAQ

**Do I need an Airbnb account?**
No. None, and there is nothing to connect.

**Does it need a login or any personal details?**
No. Just run it — the only thing you supply is what you want priced.

**Can I schedule it?**
Yes, and that is where it is most useful. Keep `stateKey` the same between runs
and each run compares itself against the one before: `previousNightlyRate`,
`priceChangePct`, `newlyDiscounted` and `discountEnded` are filled in for you.
Because the dates you ask for are relative to the day of the run, a daily
tracker's comparison is normally like-for-like — the same listing at the same
number of days ahead for the same stay length — and it switches to a
date-for-date comparison whenever those exact dates have been priced before.
`comparisonBasis` tells you which happened on every row. Switch `emitUnchanged`
off and a daily run writes only what moved, plus the market summary for each date
so your medians stay unbroken.

**Is the data complete?**
For a pinned comp set, yes — those listings are looked for in every window you
ask for, and one that is missing is reported as missing. For a whole market, no:
Airbnb shows only a few hundred listings per search, so a market run is a large
sample, widened by price bands and map areas. Every row says which market or map
area and which price band produced it, so you always know what the numbers cover.

**Why does a listing sometimes show no discount when Airbnb shows a crossed-out
price?**
A discount is only reported when the pre-discount total is genuinely higher than
what is being charged. Equal figures are not a discount, so nothing is reported
rather than a misleading 0 %.

**Can I track more than one market or comp set?**
Yes. Give each one its own `stateKey` and their histories stay separate, even
running on the same schedule.

# Actor input Schema

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

The place whose rates you want to track, written the way you would type it into Airbnb — for example "Paris, France", "Austin, Texas" or "Lake Como". This is always used, including when you add map areas below, because without it results lose their property-type line and review counts.

## `bboxes` (type: `array`):

Optional map areas to price separately, so a large city can be split into neighbourhoods that each get their own median. One area per row, as four corner coordinates: "neLat,neLng,swLat,swLng". Up to 10 rows.

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

Optional comp set. Paste the listings you care about as numeric ids or room addresses; only those listings are written as rate rows, and one that has no availability in a window is still written with notFoundInWindow set to true. Leave empty to write every listing found in the market. Up to 500 listings.

## `leadTimeDays` (type: `array`):

How far ahead of today each check-in date sits, one value per row. 7, 14, 30, 60 and 90 price next week, a fortnight out, a month out, two months out and a quarter out — the classic booking curve. Up to 8 rows, each a whole number of days from 0 to 365.

## `stayLengths` (type: `array`):

How many nights each stay covers, one value per row. 2 and 5 give you a short-break rate and a week-ish rate for every check-in date. Up to 4 rows, each from 1 to 28 nights.

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

Guest count the prices are quoted for. Airbnb's prices change with occupancy, so keep this the same between runs if you want a comparable series.

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

Only price listings at or above this nightly figure, in the currency you pin below. Price bands are the best way to see deeper into a busy market: run the same market once per band and merge the results.

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

Only price listings at or below this nightly figure, in the currency you pin below.

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

Restrict the comp set to specific property types, exactly as Airbnb labels them.

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

Only price listings with at least this many bedrooms.

## `maxListingsPerWindow` (type: `integer`):

How many listings to price for each check-in date and stay length. 200 gives a solid median for a city; raise it for a wider sample, lower it for a faster, cheaper run.

## `stateKey` (type: `string`):

Names the price history this run compares against. Keep it the same between runs to build a series; use a different name to track a second market or comp set independently.

## `minChangePctToFlag` (type: `integer`):

A rate has to move by at least this percentage before it counts as a change. Useful for ignoring rounding noise.

## `emitUnchanged` (type: `boolean`):

On, every priced listing is written every run, so you always get the full picture. Off, only listings whose rate moved, whose discount started or ended, or that are newly seen are written — a much smaller daily digest.

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

Safety cap on how much work one run may do. The plan is checked against it before anything starts: coverage inside each window is trimmed first, and only then are the longest-lead dates dropped, so the near-term dates always survive.

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

How many date windows to price at the same time. 4 is a good balance; higher is faster but heavier on the source site.

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

Currency every price is quoted in. Pin it and keep it fixed, or a series stops being comparable.

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

Language for the text Airbnb returns. English is the tested option; other languages are accepted but not verified.

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

Country to appear to browse from, as a two-letter code such as us, gb or fr. Leave empty for the default.

## Actor input object example

```json
{
  "query": "Paris, France",
  "bboxes": [
    "48.8892,2.3652,48.8492,2.3152"
  ],
  "listingIds": [
    "5398166",
    "https://www.airbnb.com/rooms/1695566169788697651"
  ],
  "leadTimeDays": [
    "7",
    "14",
    "30",
    "60",
    "90"
  ],
  "stayLengths": [
    "2",
    "5"
  ],
  "adults": 2,
  "roomTypes": [],
  "maxListingsPerWindow": 200,
  "stateKey": "default",
  "minChangePctToFlag": 1,
  "emitUnchanged": true,
  "maxRequests": 400,
  "maxConcurrency": 4,
  "currency": "USD",
  "locale": "en",
  "proxyCountry": "us"
}
```

# 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",
    "bboxes": [],
    "listingIds": [],
    "leadTimeDays": [
        "7",
        "14",
        "30",
        "60",
        "90"
    ],
    "stayLengths": [
        "2",
        "5"
    ],
    "adults": 2,
    "roomTypes": [],
    "maxListingsPerWindow": 200,
    "stateKey": "default",
    "minChangePctToFlag": 1,
    "emitUnchanged": true,
    "maxRequests": 400,
    "maxConcurrency": 4,
    "currency": "USD",
    "locale": "en",
    "proxyCountry": "us"
};

// Run the Actor and wait for it to finish
const run = await client.actor("apt_marble/airbnb-price-tracker").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",
    "bboxes": [],
    "listingIds": [],
    "leadTimeDays": [
        "7",
        "14",
        "30",
        "60",
        "90",
    ],
    "stayLengths": [
        "2",
        "5",
    ],
    "adults": 2,
    "roomTypes": [],
    "maxListingsPerWindow": 200,
    "stateKey": "default",
    "minChangePctToFlag": 1,
    "emitUnchanged": True,
    "maxRequests": 400,
    "maxConcurrency": 4,
    "currency": "USD",
    "locale": "en",
    "proxyCountry": "us",
}

# Run the Actor and wait for it to finish
run = client.actor("apt_marble/airbnb-price-tracker").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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",
  "bboxes": [],
  "listingIds": [],
  "leadTimeDays": [
    "7",
    "14",
    "30",
    "60",
    "90"
  ],
  "stayLengths": [
    "2",
    "5"
  ],
  "adults": 2,
  "roomTypes": [],
  "maxListingsPerWindow": 200,
  "stateKey": "default",
  "minChangePctToFlag": 1,
  "emitUnchanged": true,
  "maxRequests": 400,
  "maxConcurrency": 4,
  "currency": "USD",
  "locale": "en",
  "proxyCountry": "us"
}' |
apify call apt_marble/airbnb-price-tracker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=apt_marble/airbnb-price-tracker",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/J0vaBkmSROf07CMg2/builds/0kEANXOWoCTrdpDcM/openapi.json
