# Airbnb Search Scraper — Stays, Prices & Ratings (`thenetaji/airbnb-search-scraper`) Actor

Search Airbnb anywhere and export every stay it returns. Set dates and guest counts and each row comes back priced for that trip, with the rating, review count, coordinates and photos. Turn on stay details to add amenities, the host and per-category scores.

- **URL**: https://apify.com/thenetaji/airbnb-search-scraper.md
- **Developed by:** [The Netaji](https://apify.com/thenetaji) (community)
- **Categories:** Travel, Real estate, Business
- **Stats:** 2 total users, 1 monthly users, 87.5% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.02 / 1,000 results

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/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 Search Scraper

The Actor searches Airbnb for a destination and returns every stay the search yields, priced for
the dates supplied. Each record carries the stay's name, its price for that trip, its rating and
review count, its coordinates, and the photographs shown on the search card. Enabling
`enrichStayDetails` adds the whole stay page to every row.

### Accepted input

`destination` is required and is free text, written as it would be typed into Airbnb: a city, a
region, a neighbourhood, or an address. It is the only required field.

`checkin` and `checkout` are ISO dates and must be supplied together. Airbnb ignores a lone
check-in date and returns undated results instead of reporting an error, so the Actor rejects a
half-specified stay before the request is made, as it does a `checkout` on or before `checkin`.
When both dates are present, every price returned covers that exact stay length.

`adults` accepts 1 to 16, `children` 0 to 15, and `infants` 0 to 5. Guest counts change both which
stays Airbnb shows and what they cost. Fields left empty are omitted from the request rather than
sent blank, because Airbnb treats an empty guest value as malformed and discards the entire guest
set.

`maxItems` bounds the number of records saved and defaults to `50`. The bound cannot exceed `270`;
see `Why a run stops at 270 stays` below. A value of `0` requests as many as the search will give,
which is the same 270 ceiling rather than an unlimited walk.

```json
{
  "destination": "San Francisco, CA",
  "checkin": "2026-10-05",
  "checkout": "2026-10-09",
  "adults": 2,
  "maxItems": 20,
  "enrichStayDetails": false
}
```

### Response fields

Every record carries `id`, `url`, `name`, `title`, `subtitle`, `price`, `price_original`,
`price_qualifier`, `price_label`, `rating`, `reviews`, `latitude`, `longitude`, `image_url`,
`images`, `destination_matched`, and `destination_resolved`.

`price` is the total for the dates searched, in the currency Airbnb displayed, and
`price_qualifier` states what it covers. `price_original` is populated only on a discounted stay
and is `null` otherwise. `price_label` preserves Airbnb's own full price sentence, such as
`$197 for 5 nights, originally $253`.

`rating` and `reviews` are `null` on a stay that has not been reviewed yet, rather than `0`; a
stay with no reviews and a stay rated zero are different things and are returned differently.

`id` is the stay's numeric identifier and is the value the Airbnb Listing Scraper accepts.

```json
{
  "id": "1348596009084608060",
  "url": "https://www.airbnb.com/rooms/1348596009084608060",
  "name": "Hotel Stratford",
  "subtitle": "Hotel in Downtown San Francisco-Union Square",
  "price": "$624",
  "price_qualifier": "for 5 nights",
  "price_label": "$624 for 5 nights",
  "rating": 4.54,
  "reviews": 243,
  "latitude": 37.787,
  "longitude": -122.408,
  "destination_matched": true,
  "destination_resolved": "San Francisco, CA"
}
```

### Why every row reports whether the destination matched

Airbnb does not fail an unrecognisable destination. A search for a place it cannot resolve returns
a full page of ordinary, real stays for somewhere else entirely, with no notice of any kind on the
page: no "no results" message, no "showing nearby stays" banner, nothing in the visible text that
distinguishes it from a successful search.

`destination_matched` is `false` on every row of such a run, and the run log carries a warning
naming the destination that failed to resolve. `destination_resolved` reports the place Airbnb
actually searched, which is worth checking even on a successful run, since a vague destination can
resolve to somewhere other than intended.

A run that returns plausible stays for the wrong city is the failure mode this field exists to
make visible. Filtering on `destination_matched` is the reliable way to discard one.

### Behaviour on partial results

Pagination is performed upstream in a single request, so a run issues one search regardless of
`maxItems`. Fewer records than requested means the search itself was exhausted, not that the run
was cut short.

### Why a run stops at 270 stays

Airbnb's search returns 18 stays per page and publishes 15 page cursors, on every destination
measured, irrespective of how much inventory the place actually holds. 18 × 15 is 270, and that is
the ceiling the surface imposes rather than a limit chosen here. A city with forty thousand
listings returns the same 270 as a village with four hundred. Requests above the ceiling are
capped rather than rejected.

### Enriching with stay details

`enrichStayDetails` adds the stay's own page to every row: `description`, `amenities` with a flag
on each entry recording whether the stay has it, `overview`, `highlights`, `host` and the flattened
`host_name` and `host_is_superhost`, `rating_categories`, `property_type`, `space_type`,
`person_capacity`, and the complete `stay_detail` record.

This costs one additional request per stay and is billed per stay as an enrichment, charged only
after the lookup succeeds. A stay removed between the search and the lookup leaves its search row
intact, logs a warning, and bills nothing.

Where the stay page and the search card disagree, the stay page wins, with two exceptions that the
search knows and the stay page does not: `price`, which is priced for the dates searched, and
`destination_matched`.

Leaving the toggle off returns search cards only and is substantially faster on a large run.

### Related Actors

The **Airbnb Listing Scraper** takes stay IDs directly and returns the same detail without a search.
It is the cheaper route when the IDs are already known, or when only a handful of stays are needed
from a destination that would otherwise be walked in full.

# Actor input Schema

## `destination` (type: `string`):

Where to search, written the way you would type it into Airbnb — a city, region, neighbourhood or address. If Airbnb cannot place it, the run still returns stays but marks them as unmatched rather than pretending they are for your destination.

## `checkin` (type: `string`):

First night of the stay. Set this together with check-out to price results for those exact dates; leave both empty for Airbnb's default window.

## `checkout` (type: `string`):

Last night of the stay. Must be after check-in, and set together with it.

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

Number of adult guests. Changes which stays are shown and what they cost.

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

Number of children (ages 2-12).

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

Number of infants (under 2).

## `maxItems` (type: `integer`):

Maximum number of stays to save, up to 270 — the most Airbnb's own search will return for one destination. Set 0 for as many as it will give.

## `enrichStayDetails` (type: `boolean`):

Add the full stay page to every row — description, the amenity list with what each stay does and does not have, the host's name and superhost status, and review scores broken out by cleanliness, location, value, check-in, accuracy and communication. This makes one extra request per stay.

## Actor input object example

```json
{
  "destination": "Paris, France",
  "checkin": "2026-10-05",
  "checkout": "2026-10-09",
  "adults": 2,
  "maxItems": 20,
  "enrichStayDetails": false
}
```

# Actor output Schema

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

All records scraped by this run

# 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 = {
    "destination": "San Francisco, CA",
    "adults": 2,
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("thenetaji/airbnb-search-scraper").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 = {
    "destination": "San Francisco, CA",
    "adults": 2,
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("thenetaji/airbnb-search-scraper").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 '{
  "destination": "San Francisco, CA",
  "adults": 2,
  "maxItems": 20
}' |
apify call thenetaji/airbnb-search-scraper --silent --output-dataset

```

## MCP server setup

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

```

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/pdYvR4VfLAnkSt6IA/builds/XhjfqNwqgRWc0NT0a/openapi.json
