# Airbnb Reviews Scraper (`luminar/airbnb-reviews-scraper-private-v1`) Actor

Export flat, deduplicated Airbnb reviews with ratings, public reviewer fields, host responses, practical filters, per-listing coverage, and optional repeat-run change tracking.

- **URL**: https://apify.com/luminar/airbnb-reviews-scraper-private-v1.md
- **Developed by:** [Luka](https://apify.com/luminar) (community)
- **Categories:** Business, Marketing, Travel
- **Stats:** 2 total users, 1 monthly users, 90.9% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.79 / 1,000 delivered airbnb reviews

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 reviews scraper to convert listing URLs into flat, deduplicated review rows with stable identity, original text, ratings, public reviewer fields, host responses, and explicit per-listing coverage.** Choose a one-time **Airbnb review export** for analysis or repeat-run comparison to write only new and updated reviews.

The Actor is designed for reputation research, host-response tracking, review datasets, and recurring monitoring. It distinguishes a genuinely exhausted source from a capped, challenged, or failed window, so incomplete collection is never presented as complete or empty.

![Airbnb review rows with rating, public reviewer, change state, and coverage](https://api.apify.com/v2/key-value-stores/DJgPR6wwS2tIaLDQW/records/airbnb_reviews--a3c3119085a1e622-airbnb-review-data.png)

### 🚀 Start in 60 seconds

Paste one public Airbnb room URL and keep the 25-review prefill:

```json
{
  "listingUrls": [
    { "url": "https://www.airbnb.com/rooms/12937" }
  ],
  "maxReviewsPerListing": 25,
  "scrapeMode": "fast_recent",
  "incrementalMode": "raw"
}
```

Run the Actor, then open:

- **Reviews overview** for the most useful review, rating, reply, state, and coverage fields.
- **Full review export** for every schema-backed field.
- `OUTPUT` in the default key-value store for per-listing coverage, limits, warnings, and billing counts.

The first request to a listing can take longer than later pages. Keep the first test to one listing; wider batches can then use the default listing concurrency.

### 🎯 Choose the right workflow

#### One-time export

Keep:

```json
{
  "incrementalMode": "raw"
}
```

Every accepted review is written with `state: "CURRENT"`. This is the clearest choice for a single export, an analysis dataset, or a workflow that manages its own history.

#### Track changes across runs

Use:

```json
{
  "incrementalMode": "compare",
  "includeUnchanged": false,
  "stateNamespace": "weekly-reputation-monitor"
}
```

The first changes-only Compare run creates an Actor-scoped V2 baseline and writes no `review_result` rows. This prevents a release migration or a new namespace from rebilling historical reviews as changes. Later compatible runs classify stable review identities as `NEW`, `UPDATED`, or `UNCHANGED`; with `includeUnchanged: false`, only `NEW` and `UPDATED` rows are written.

On repeat runs with the same listing URLs, extraction settings, and `stateNamespace`, the Actor compares the new trusted window with that V2 baseline. It writes only genuinely `NEW` or `UPDATED` rows when unchanged output is off; unchanged reviews remain free, and failed or unverified targets do not advance state.

Reuse the same listing, extraction settings, and `stateNamespace` for a compatible comparison. Different namespaces keep independent monitors separate. Partial or capped windows never infer that a missing review was deleted.

#### Fast recent or full pagination

- `fast_recent` makes the minimum requests needed for the requested newest window. It is recommended for quick exports and monitoring.
- `full_pagination` continues until the requested cap, a hard safety ceiling, or positive source exhaustion. Reaching the cap is reported as partial or capped coverage.

Sorting and filters can change which reviews are observed. Use `MOST_RECENT` with a lower date boundary when you want the Actor to stop after it reaches older reviews.

### 📦 What you get

Each paid dataset item is one unique review row. Core fields include:

- stable buyer-facing `reviewId` for deduplication and repeat-run results;
- deterministic `deliveryId` for idempotent restart recovery;
- canonical `listingId` and `listingUrl`;
- original and localized review text where exposed;
- publication timestamp, source language, rating, and stay context;
- optional public reviewer fields;
- optional host response text and date;
- repeat-run state and comparison validity;
- canonical coverage status;
- collection timestamp and warnings.

A representative row shape:

```json
{
  "deliveryId": "airbnb-review-delivery:synthetic-example",
  "reviewId": "501",
  "listingId": "12937",
  "listingUrl": "https://www.airbnb.com/rooms/12937",
  "text": "Representative review text",
  "createdAt": "2026-08-20T10:00:00Z",
  "language": "en",
  "rating": 5,
  "reviewerName": "Ana",
  "hostResponseText": "Thank you.",
  "state": "CURRENT",
  "coverageState": "COMPLETE",
  "coverageStatus": "COMPLETE"
}
```

Missing source fields remain `null`. Duplicate listing targets are processed once, and duplicate review identities are not delivered or charged twice.

### 🎛️ Input guide

#### Review selection

| Input | Use it when | Work and result impact |
| --- | --- | --- |
| `listingUrls` | You have one or more public Airbnb room URLs | Each unique listing adds source work; the schema accepts up to 100 entries |
| `maxReviewsPerListing` | You need a bounded number of rows per listing | Higher values can require more pages and runtime |
| `scrapeMode` | You want a recent window or deeper pagination | Full pagination continues until exhaustion, a cap, or a safety stop |
| `sortBy` | You need newest, relevance, or rating order | Date-based early stopping is available only with Most recent |
| `sinceDate`, `untilDate` | You need a publication window | Deterministic post-filters; the lower boundary can save pages with Most recent |
| `reviewLanguages` | You need specific original languages | Empty keeps every language |
| `minRating`, `maxRating` | You need a rating range | Applied deterministically to extracted rows |

#### Optional fields

`locale` controls localized source labels and dates; it does not translate review text. Turn off `includeReviewerPublicFields` for a smaller row shape. Turn off `includeHostResponses` when replies are not needed; V1 does not make a separate request for them.

#### Monitoring controls

`incrementalMode`, `includeUnchanged`, and `stateNamespace` work together. Changes-only delivery means `incrementalMode: "compare"` and `includeUnchanged: false`. Failed, blocked, and unverified targets are not charged a `monitor_target` event and do not advance monitoring state.

#### Limits and safeguards

- `pageSize: 50` is the efficient default; smaller values usually increase request count.
- `concurrency` parallelizes listings while pages within one listing remain ordered.
- `maxRequests` and `maxRuntimeSecs` stop new work before a hard buyer-visible ceiling.
- `requestTimeoutSecs` bounds one source request.

### 💰 Pricing

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

| Apify plan | Per delivered review | Per 1,000 delivered reviews |
| --- | ---: | ---: |
| Free | $0.00319 | $3.19 |
| Bronze | $0.00287 | $2.87 |
| Silver | $0.00251 | $2.51 |
| Gold | $0.00179 | $1.79 |
| Platinum | $0.00135 | $1.35 |
| Diamond | $0.00076 | $0.76 |

#### One-time export formula

```text
total = actor_start at $0.0043 after a usable verified source outcome
      + delivered review rows × your Apify-plan review_result price
```

**Free example:** 25 delivered reviews cost `$0.0043 + (25 × $0.00319) = $0.08405`.

**Gold example:** the same 25 reviews cost `$0.0043 + (25 × $0.00179) = $0.04905`.

Gold reduces the review event by exactly `$0.00140` per row, or `$1.40 per 1,000`, compared with Free. That is a **43.89% unit discount**. At 25 delivered reviews, the complete-run saving is `$0.03500`, or **41.64%**, because the `$0.0043` start remains flat.

#### Changes-only formula

```text
total = actor_start at $0.0043
      + verified non-failed unique targets × monitor_target at $0.0008
      + delivered NEW or UPDATED review rows × your Apify-plan review_result price
```

One verified target with no changed reviews costs `$0.0043 + $0.0008 = $0.0051` on every plan. With five changed rows, the complete charge is `$0.02105` on Free and `$0.01405` on Gold.

Suppressed unchanged rows, coverage, warnings, duplicates, and failed, blocked, or unverified targets are free. The one-time start is charged only after at least one target returns usable source observations or positively proves an exhausted empty review set. A failed, blocked, challenged, or no-collection run does not charge it. The first changes-only Compare run can charge `actor_start` and one `monitor_target` per verified target, but writes and charges zero `review_result` rows while creating the Actor-scoped V2 baseline.

### ✅ Coverage you can trust

Every review row carries per-target coverage context, and `OUTPUT` contains the full run report.

- `COMPLETE` requires positive source exhaustion for the requested extraction shape.
- `PARTIAL` or `CAPPED` means the review limit, request limit, runtime, charge limit, challenge, timeout, or other stop condition prevented a complete claim.
- `EMPTY_CONFIRMED` requires verified source evidence that no matching reviews exist for the requested shape.
- `FAILED` or `BLOCKED` is explicit and never converted to empty output success.

`sourceTotalReviews`, `sourceOffset`, `sourcePage`, `sourceExhausted`, and warnings help explain what was actually observed. Post-filters can legitimately produce zero delivered rows even when source reviews were seen; coverage keeps those cases distinct.

Monitoring state is fail-closed: an unverified target does not replace the last safe snapshot, and deletion is never inferred from partial or capped collection.

### 🔌 API and automation

Use the same input through the Apify API. Keep the Actor ID and token in environment variables:

```bash
curl -X POST "https://api.apify.com/v2/acts/$ACTOR_ID/runs?token=$APIFY_TOKEN&waitForFinish=300" \
  -H "Content-Type: application/json" \
  -d '{
    "listingUrls": [{"url":"https://www.airbnb.com/rooms/12937"}],
    "maxReviewsPerListing": 25,
    "scrapeMode": "fast_recent",
    "incrementalMode": "raw"
  }'
```

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

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

For recurring monitoring, keep the same `stateNamespace` and compatible extraction settings on each scheduled call. The Actor does not create schedules itself.

### ❓ FAQ and support

#### Why did the first changes-only Compare run return no review rows?

There is no Actor-scoped V2 snapshot yet. The first run establishes a free review-row baseline so a release migration or new namespace cannot rebill historical reviews. Later compatible runs can identify and emit genuinely new or updated content. Use one-time export mode when you want the current review rows immediately.

#### Can changes-only monitoring cost money when zero rows are written?

Yes. A verified non-failed target uses one `monitor_target` event because source work and safe state comparison still occurred. No `review_result` event is charged when no changed row is delivered.

#### Why is coverage partial when I received the number of reviews requested?

Reaching `maxReviewsPerListing` proves the requested delivery cap was met, not that the source had no more reviews. Choose full pagination and a sufficiently high cap only when you need stronger exhaustion evidence.

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

Include the Apify run ID, input with tokens or secrets removed, the listing that failed, the expected and observed result, and the relevant row warnings or `OUTPUT` report. Never send an API token in a support message.

# Actor input Schema

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

Paste one or more public Airbnb /rooms/{id} URLs. Duplicate listing IDs are processed once. Start with one URL and the prefilled 25-review limit; large batches remain bounded by the safety ceilings below.

## `maxReviewsPerListing` (type: `integer`):

Maximum review rows considered for each listing. Higher values can require more pages and runtime. Reaching this cap is reported as partial or bounded coverage, never source-complete.

## `scrapeMode` (type: `string`):

Fast recent reviews makes the minimum requests for the requested newest window. Full pagination continues until the review cap, a safety ceiling or positive source exhaustion.

## `sortBy` (type: `string`):

Choose the source review order. Most recent is recommended for monitoring and is the only order that can stop early after crossing the lower date boundary.

## `sinceDate` (type: `string`):

Deterministic post-filter. With MOST\_RECENT, older source pages can be skipped after the boundary is crossed.

## `untilDate` (type: `string`):

Deterministic post-filter applied to the source-returned review timestamp.

## `reviewLanguages` (type: `array`):

Optional source-backed language-code allowlist, for example en, de or fr. Empty keeps every language.

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

Deterministic post-filter on the source review rating.

## `maxRating` (type: `number`):

Deterministic post-filter on the source review rating.

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

Controls localized source labels and dates. The review text remains in its original source language.

## `includeReviewerPublicFields` (type: `boolean`):

Keeps only reviewer fields publicly exposed in the review response. Disable for a smaller data footprint.

## `includeHostResponses` (type: `boolean`):

Keeps host response text/date when exposed. This does not trigger a separate request in V1.

## `incrementalMode` (type: `string`):

Raw mode always emits CURRENT. A first changes-only Compare run creates a free review-row baseline; compatible repeats classify and emit NEW or UPDATED rows. Compare with unchanged output enabled still returns the full observed window.

## `includeUnchanged` (type: `boolean`):

In Track changes mode, turn this off to write only NEW and UPDATED rows. The first actor-scoped V2 baseline writes no review\_result rows, preventing an upgrade from rebilling historical reviews. Compatible repeats emit changed rows; each verified non-failed target uses one monitor\_target event.

## `stateNamespace` (type: `string`):

Use a short stable label to keep independent monitoring workflows separate. Reuse the same namespace and extraction settings for compatible comparisons.

## `pageSize` (type: `integer`):

Reviews requested per source page. Keep 50 for normal runs; smaller values usually increase request count and are intended for troubleshooting only.

## `concurrency` (type: `integer`):

Listings run concurrently; pagination within each listing stays ordered and bounded.

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

Hard total across all public-source work. The run stops before the next request would exceed this value.

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

Hard wall-clock ceiling checked before each live request.

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

Stops new source work when the conservative run-cost estimate reaches this amount. This safeguard is separate from the Actor price.

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

Maximum wait for one source request before the target is marked partial or failed.

## Actor input object example

```json
{
  "listingUrls": [
    {
      "url": "https://www.airbnb.com/rooms/12937"
    }
  ],
  "maxReviewsPerListing": 25,
  "scrapeMode": "fast_recent",
  "sortBy": "MOST_RECENT",
  "locale": "en-US",
  "includeReviewerPublicFields": true,
  "includeHostResponses": true,
  "incrementalMode": "raw",
  "includeUnchanged": true,
  "stateNamespace": "default",
  "pageSize": 50,
  "concurrency": 3,
  "maxRequests": 60,
  "maxRuntimeSecs": 240,
  "maxCostUsd": 0.25,
  "requestTimeoutSecs": 45
}
```

# Actor output Schema

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

One deduplicated review per row with stable identity, original text, rating, optional public reviewer and host-response fields, repeat-run state, and coverage context.

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

The OUTPUT record with per-listing outcomes, source exhaustion and caps, warnings, changed and suppressed row counts, and accepted billing-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 = {
    "listingUrls": [
        {
            "url": "https://www.airbnb.com/rooms/12937"
        }
    ],
    "maxReviewsPerListing": 25
};

// Run the Actor and wait for it to finish
const run = await client.actor("luminar/airbnb-reviews-scraper-private-v1").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 = {
    "listingUrls": [{ "url": "https://www.airbnb.com/rooms/12937" }],
    "maxReviewsPerListing": 25,
}

# Run the Actor and wait for it to finish
run = client.actor("luminar/airbnb-reviews-scraper-private-v1").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 '{
  "listingUrls": [
    {
      "url": "https://www.airbnb.com/rooms/12937"
    }
  ],
  "maxReviewsPerListing": 25
}' |
apify call luminar/airbnb-reviews-scraper-private-v1 --silent --output-dataset

```

## MCP server setup

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

```

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/hSKs25yGazR4KwPAc/builds/b4lTdiPgGLkN2ezuQ/openapi.json
