# Airbnb Market Data Scraper: Stay Prices & Geo (`luminar/airbnb-market-geo-intelligence`) Actor

Export deduplicated Airbnb listings with stay-specific price snapshots, normalized property types, market summaries, and transparent coverage. Supported Tivat searches also include municipality, settlement, and coast-distance context.

- **URL**: https://apify.com/luminar/airbnb-market-geo-intelligence.md
- **Developed by:** [Luka](https://apify.com/luminar) (community)
- **Categories:** Business, Real estate, Travel
- **Stats:** 2 total users, 1 monthly users, 72.7% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.70 / 1,000 unique market listings

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?

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

**Use this Airbnb market data scraper to turn a destination and stay period into a deduplicated market dataset with comparable prices, normalized property types, free coverage evidence, and ready-to-use summaries.** Tivat searches can also add municipality scope, settlement, and approximate coast-distance bands from the bundled geo layer.

Use this Actor as an **Airbnb price scraper** for short-term-rental market research, supply comparisons, pricing snapshots, location segmentation, and repeatable exports. Prices are search snapshots for the requested dates: every row keeps its price basis and whether the quote was verified. The Actor does not silently present a capped or filtered search as the complete market.

![Actual Airbnb market output with Tivat listing prices, ratings, and transparent coverage](https://api.apify.com/v2/key-value-stores/DJgPR6wwS2tIaLDQW/records/airbnb_market_geo--c675986d96a870d8-airbnb-market-results.png)

*Actual Store-default output: 50 deduplicated Tivat listings, 25 priced seven-night stays, and explicit capped coverage.*

### 🚀 Start in 60 seconds

Start with one destination, one result page, and ten listings:

```json
{
  "locations": ["Tivat, Montenegro"],
  "maxListings": 10,
  "maxPagesPerDateWindow": 1,
  "pageConcurrency": 1,
  "maxRequests": 5
}
```

With both exact dates blank, the Actor automatically checks a seven-night stay starting 30 days after the run date. The chosen dates are written to every listing row and to `OUTPUT`, so scheduled runs remain useful and transparent. Set both `checkIn` and `checkOut` when you need specific dates.

Run the Actor, then open the default dataset:

- **Market** shows one deduplicated listing per paid row.
- **Market summaries & coverage** shows free run evidence and aggregate rows.
- `OUTPUT` in the default key-value store contains the machine-readable run report.

For a deeper search, raise `maxPagesPerDateWindow` before raising `maxListings`. The default of three pages is the balanced setting; one page keeps a quick scan's source work low.

### 🎯 Choose the right workflow

#### Exact stay — one comparable date window

Use `dateMode: "exact_stay"`. Set both `checkIn` and `checkOut` for a specific trip, or leave both blank for the documented rolling seven-night default 30 days ahead. Every listing row keeps the effective dates, stay length, displayed stay total, derived nightly price, currency, and price basis.

This is the simplest choice when you need a market snapshot for a specific trip.

#### Sampled period — explore flexible dates cheaply

Use `dateMode: "period_scan"` with `periodStart`, `periodEnd`, and `stayLengthNights`, then keep `periodCoverage: "sampled"`. The Actor checks selected eligible check-ins at the `sampleEveryDays` interval and always includes the first and last eligible check-in.

The result is the lowest **observed** price among sampled windows, not a claim that every possible date was checked.

#### Exhaustive period — compare every eligible check-in

Choose `periodCoverage: "exhaustive"` to generate every eligible check-in for the requested stay length. This can multiply the number of searches quickly. The complete destination × date-window × page plan must fit `maxRequests` before source work starts; the Actor fails clearly instead of silently dropping requested windows.

#### Tivat market segmentation

Keep `marketBoundaryMode: "strict_supported_boundary"` to exclude source spillover outside the supported Tivat municipality boundary before summaries. Choose `source_search_results` when you want to retain all source results; outside-market rows remain visible with their scope status and without invented Tivat geo fields.

Settlement and coast-distance filters apply only where the versioned geo package can make a supported assignment. Source coordinates may be approximate, so distance bands are analysis signals rather than exact addresses or walking routes.

### 📦 What you get

The default dataset contains three record types.

| Record | What it contains | Charge |
| --- | --- | ---: |
| `listing` | Stable listing identity, URL, source and normalized types, capacity, rating, host signals, requested-stay prices, geo context, and provenance | `listing_result` |
| `coverage` | Pages, caps, duplicates, filters, supported-market counts, geo assignment rates, warnings, and billing counts | Free |
| `summary` | Price and supply aggregates for the delivered rows, segmented by supported dimensions | Free |

A representative listing row looks like this:

```json
{
  "recordType": "listing",
  "listingId": "example-123456789",
  "listingUrl": "https://www.airbnb.com/rooms/123456789",
  "title": "Example apartment with sea view",
  "normalizedPropertyClass": "apartment",
  "normalizedRoomClass": "entire_place",
  "priceCheckIn": "2026-09-15",
  "priceCheckOut": "2026-09-18",
  "stayNights": 3,
  "stayTotal": 360,
  "nightlyPrice": 120,
  "currency": "EUR",
  "marketScopeStatus": "inside_supported_market",
  "municipality": "Tivat",
  "settlement": "Donja Lastva",
  "coastDistanceBand": "250–500 m"
}
```

Missing source values remain `null`; they are not converted to zero or guessed. Listing rows are deduplicated by canonical listing identity before delivery and charging.

### 🎛️ Input guide

#### Search, dates, and guests

| Input | Use it when | Work and result impact |
| --- | --- | --- |
| `locations` | You need one or more destination searches | Each destination adds date-window and page work |
| `dateMode` | You are choosing exact dates or a flexible period | Period scans can create many more source searches |
| `checkIn`, `checkOut` | You need specific exact-stay dates | Set both together; if both are blank, the Actor uses a seven-night stay 30 days ahead |
| `periodStart`, `periodEnd`, `stayLengthNights` | You need flexible-date comparison | All three define the generated stay windows |
| `periodCoverage`, `sampleEveryDays` | You need sampled or exhaustive coverage | Exhaustive checks every eligible check-in and costs more work |
| `adults`, `children`, `infants`, `pets` | Availability must match the party | Sent with every generated search window |

#### Filters and geo controls

Price, beds, bedrooms, bathrooms, Superhost, and Instant Book controls narrow the source result where supported. Keyword, amenity, normalized-class, rating, and review-count controls are deterministic post-filters; the free coverage row reports their effect.

`keywordsAny` needs one matching phrase, `keywordsAll` needs every phrase, and `excludeKeywords` removes matches. `keywordFields` controls which extracted fields are searched.

For Tivat, `settlements` and `coastDistanceBands` use the supported geo package. Unsupported destinations remain searchable but do not receive a silent Tivat assignment.

#### Limits and coverage

- `maxListings` caps unique paid listing rows across the whole run.
- `maxPagesPerDateWindow` controls search depth for each destination and date window.
- `pageConcurrency` parallelizes later cursor pages while preserving deterministic output order.
- `maxRequests`, `maxRuntimeSecs`, and `maxSourceBytes` stop new work before a configured buyer-visible safety ceiling is exceeded.
- `requestTimeoutSecs` bounds one live request. A timeout becomes explicit partial or failed coverage, not a confirmed empty market.

### 💰 Pricing

This Actor uses pay per event. The prices below are the current configured Store prices.

```text
total = $0.005 actor start after a usable verified source outcome
      + delivered unique listing rows × your Apify-tier listing price
```

| Apify plan | Per delivered listing | Per 1,000 delivered listings |
| --- | ---: | ---: |
| Free | $0.00190 | $1.90 |
| Bronze | $0.00150 | $1.50 |
| Silver | $0.00110 | $1.10 |
| Gold | $0.00070 | $0.70 |
| Platinum | $0.00050 | $0.50 |
| Diamond | $0.00022 | $0.22 |

**Free example:** 50 delivered listings cost `$0.005 + (50 × $0.00190) = $0.1000`.

**Gold example:** the same 50 listings cost `$0.005 + (50 × $0.00070) = $0.0400`.

Gold reduces the listing event by exactly `$0.00120` per row, or `$1.20 per 1,000`, compared with Free. That is a **63.16% unit discount**. At 50 delivered listings, the fixed start makes the complete-run saving `$0.0600`, or **60.00%**.

Coverage rows, summary rows, duplicates, filtered-out rows, and suppressed rows are free. A listing is charged only when the unique useful row is accepted and written to the default dataset. The one-time start is charged only after the Actor receives usable source listings or positively proves an exhausted empty result. A blocked, failed, challenged, or unverified no-source run does not charge the start. Your Apify run-level maximum charge can still stop work or delivery earlier; coverage records the resulting cap.

### ✅ Coverage you can trust

Coverage describes the result that was actually observed:

- `search_pages_exhausted` means the Actor reached the end advertised by the source for this exact query snapshot. It does not claim every listing in the wider geographic market was exposed.
- `capped_by_page_limit` means another page was available when the configured page limit stopped the search.
- `capped_by_delivery_limit` means `maxListings` stopped delivery.
- `capped_by_charge_limit` means the buyer run-charge ceiling stopped more paid rows; no uncharged listing row is delivered.
- `partial_source_error` means at least part of the requested source work could not be verified.

The coverage record also reports duplicates removed, rows rejected by filters, date partitions, geo assignment rates, supported-market spillover, and intended versus charged PPE events. A cap, block, timeout, or source error is never relabeled as verified empty inventory.

### 🔌 API and automation

Use the same input through the Apify API. Store your Actor ID and token as environment variables rather than hard-coding them:

```bash
curl -X POST "https://api.apify.com/v2/acts/$ACTOR_ID/runs?token=$APIFY_TOKEN&waitForFinish=300" \
  -H "Content-Type: application/json" \
  -d '{
    "locations": ["Tivat, Montenegro"],
    "dateMode": "exact_stay",
    "checkIn": "2026-09-15",
    "checkOut": "2026-09-18",
    "maxListings": 10,
    "maxPagesPerDateWindow": 1
  }'
```

After the run finishes, read `defaultDatasetId` from the run and fetch:

```text
GET https://api.apify.com/v2/datasets/{defaultDatasetId}/items?clean=true&format=json
```

For scheduled market tracking, keep destination, date logic, filters, currency, locale, and geo settings stable so snapshots remain comparable. The Actor itself does not create schedules.

### ❓ FAQ and support

#### Are the prices checkout-verified totals?

No. They are source-search snapshots for the requested stay. Output preserves `priceBasis`, `quoteVerified`, dates, stay total, nights, and currency so downstream users can apply the correct interpretation.

#### Why did I receive fewer listings than requested?

Open the free coverage row. The source may have ended, a page or delivery cap may have been reached, geo or text filters may have removed rows, duplicates may have collapsed, or a request may have failed.

#### Does every destination get settlement and coast-distance data?

No. The current versioned municipality, settlement, and coast package is for the supported Tivat market. Other destinations still return market rows without invented local geo assignments.

#### What should I include in a support report?

Include the Apify run ID, input with tokens or secrets removed, the expected result, the observed result, and the relevant `coverage` row or `OUTPUT` report. That is enough to reproduce most problems without sharing private credentials.

# Actor input Schema

## `locations` (type: `array`):

Enter one or more Airbnb destination queries, for example Tivat, Montenegro. Each destination adds search work; Maximum live source requests remains the hard run-wide ceiling.

## `dateMode` (type: `string`):

Exact stay costs one search request per location. Leave both exact dates blank to use a rolling seven-night stay starting 30 days after each run, or set both dates yourself. Period scan repeats market-wide searches across eligible check-ins.

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

Used only in Exact stay mode. Set it together with check-out, or leave both blank for the rolling seven-night default 30 days ahead. The effective dates are reported in every listing and in OUTPUT.

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

Used in Exact stay mode. Set it together with check-in, or leave both blank. Output keeps stay total, nights, derived nightly price and the effective dates.

## `periodStart` (type: `string`):

Start of the period in which the guest may arrive.

## `periodEnd` (type: `string`):

End boundary of the scan. Only stays fully contained in the period are generated.

## `stayLengthNights` (type: `integer`):

Every compared quote uses this same stay length, so the lowest price comparison remains valid.

## `periodCoverage` (type: `string`):

Sampled returns the lowest observed price and says it is sampled. Exhaustive checks every possible check-in and can support a true period minimum subject to listing-inventory coverage.

## `sampleEveryDays` (type: `integer`):

Used only for sampled period scans. First and final eligible check-in are always included.

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

Adults. The coverage record reports how this filter affects delivered rows.

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

Children. The coverage record reports how this filter affects delivered rows.

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

Infants. The coverage record reports how this filter affects delivered rows.

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

Pets. The coverage record reports how this filter affects delivered rows.

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

Optional lower price bound sent to the source search. Leave blank to keep the full source price range; coverage shows the filters applied.

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

Maximum source price. The coverage record reports how this filter affects delivered rows.

## `minBeds` (type: `integer`):

Minimum beds. The coverage record reports how this filter affects delivered rows.

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

Minimum bedrooms. The coverage record reports how this filter affects delivered rows.

## `minBathrooms` (type: `number`):

Minimum bathrooms. The coverage record reports how this filter affects delivered rows.

## `keywordsAny` (type: `array`):

Accent-insensitive deterministic match after extraction. Useful for sea view, jacuzzi, parking, Porto Montenegro and similar wording.

## `keywordsAll` (type: `array`):

Must contain every keyword or phrase. The coverage record reports how this filter affects delivered rows.

## `excludeKeywords` (type: `array`):

Exclude keyword or phrase. The coverage record reports how this filter affects delivered rows.

## `keywordFields` (type: `array`):

Fields searched for keywords. The coverage record reports how this filter affects delivered rows.

## `normalizedPropertyClasses` (type: `array`):

Normalized property classes. The coverage record reports how this filter affects delivered rows.

## `normalizedRoomClasses` (type: `array`):

Room classes. The coverage record reports how this filter affects delivered rows.

## `minRating` (type: `number`):

Minimum rating. The coverage record reports how this filter affects delivered rows.

## `minReviews` (type: `integer`):

Minimum review count. The coverage record reports how this filter affects delivered rows.

## `superhostOnly` (type: `boolean`):

Turn on to keep only rows explicitly marked as Superhost. Rows without a positive source flag are excluded, and coverage reports the reduction.

## `instantBookOnly` (type: `boolean`):

Turn on to keep only rows explicitly marked as Instant Book. Rows without a positive source flag are excluded, and coverage reports the reduction.

## `requiredAmenities` (type: `array`):

Required amenities. The coverage record reports how this filter affects delivered rows.

## `marketBoundaryMode` (type: `string`):

For Tivat, strict mode removes source results outside the supported municipality boundary before summaries. Keep all source results retains them with an outside-market status and empty Tivat geo fields. Other destinations still work without Tivat-specific enrichment.

## `settlements` (type: `array`):

Applied only when a versioned geo boundary package can assign the settlement. No guessing from an exact address.

## `coastDistanceBands` (type: `array`):

Approximate distance-to-coast bands. The coverage record reports how this filter affects delivered rows.

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

Three-letter display currency requested from Airbnb, for example EUR or USD. Each row preserves the currency actually returned by the source.

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

Source locale such as en, de or fr. It can change localized labels while the Actor preserves source-provided values.

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

Maximum unique listing rows written across all destinations and date windows. Reaching this limit is reported as capped rather than source-complete.

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

Hard total request budget across destinations, date windows, pages and retries. The Actor validates the full planned page fanout before source access.

## `maxRuntimeSecs` (type: `integer`):

Fail-closed wall-clock budget checked before every live source request.

## `maxSourceBytes` (type: `integer`):

Aggregate response-byte budget. A conservative response reservation is made before each request.

## `maxCostUsd` (type: `number`):

Internal conservative compute-and-transfer guard; this is not the buyer PPE charge.

## `maxPagesPerDateWindow` (type: `integer`):

3 is the measured balanced default; 1 is quick/cheapest and up to 20 is an advanced deep scan. The Actor follows Airbnb cursors, reserves location × date windows × this value against maxRequests before source access, and reports whether the page cap truncated coverage.

## `pageConcurrency` (type: `integer`):

Fetches later cursor pages in bounded parallel batches while preserving deterministic output order. Use 1 for sequential troubleshooting; 3 is the balanced default.

## `requestTimeoutSecs` (type: `integer`):

Maximum wait for one live source request. A timeout is reported in coverage and is never relabeled as a confirmed empty market.

## Actor input object example

```json
{
  "locations": [
    "Tivat, Montenegro"
  ],
  "dateMode": "exact_stay",
  "stayLengthNights": 7,
  "periodCoverage": "sampled",
  "sampleEveryDays": 7,
  "adults": 2,
  "children": 0,
  "infants": 0,
  "pets": 0,
  "keywordFields": [
    "title",
    "description",
    "sourcePropertyType",
    "amenities",
    "locationLabel"
  ],
  "superhostOnly": false,
  "instantBookOnly": false,
  "marketBoundaryMode": "strict_supported_boundary",
  "currency": "EUR",
  "locale": "en",
  "maxListings": 50,
  "maxRequests": 20,
  "maxRuntimeSecs": 180,
  "maxSourceBytes": 25000000,
  "maxCostUsd": 0.25,
  "maxPagesPerDateWindow": 3,
  "pageConcurrency": 3,
  "requestTimeoutSecs": 45
}
```

# Actor output Schema

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

The default dataset containing paid listing rows plus free aggregate summary and coverage records. Use its Overview and Market views for listings, or the Summary view for run evidence and market aggregates.

## `runReport` (type: `string`):

The OUTPUT record with run-wide source coverage, caps, filters, supported-market and geo assignment counts, warnings, and intended versus charged event totals.

# 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 = {
    "locations": [
        "Tivat, Montenegro"
    ],
    "maxListings": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("luminar/airbnb-market-geo-intelligence").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 = {
    "locations": ["Tivat, Montenegro"],
    "maxListings": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("luminar/airbnb-market-geo-intelligence").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 '{
  "locations": [
    "Tivat, Montenegro"
  ],
  "maxListings": 50
}' |
apify call luminar/airbnb-market-geo-intelligence --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,luminar/airbnb-market-geo-intelligence"
        }
    }
}

```

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/qjfe2xSv4WudNIXuI/builds/eeZeZ0EFqMlqzISWH/openapi.json
