# Airbnb Scraper — Search & Public Listing Details (`meka.im/airbnb-scraper`) Actor

Search Airbnb by destination or direct room URL. Get prices from search results plus ratings, hosts, amenities, rules and images. $1/1K basic or $1.50/1K detailed listings. No login or cookies; pay only for delivered unique listings.

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

## Pricing

Pay per event

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

Search Airbnb by destination or scrape direct room URLs, without a login.

- **Search destinations or scrape direct Airbnb room URLs** — use either, or both in one run
- **Public listing details, ratings, hosts, amenities, rules and images**
- **Search-result pricing when available**
- **No login or cookies**
- **Pay only for successfully delivered unique listings**

### Quick start

```json
{
  "locationQueries": ["Lisbon, Portugal"],
  "checkIn": "2026-11-10",
  "checkOut": "2026-11-14",
  "adults": 2,
  "includeDetails": true
}
```

Or point it at specific rooms:

```json
{
  "startUrls": [
    "https://www.airbnb.com/rooms/24173768",
    "1747015303004071355"
  ],
  "enrichUserProfiles": true
}
```

Both inputs can be used in the same run. A listing that appears in both is delivered and charged once.

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `locationQueries` | array | | One destination per line. A comma is normalised for you, see Notes |
| `startUrls` | array | | Room links or plain numeric room IDs. Order is preserved |
| `includeDetails` | boolean | `false` | Fetch the public detail page for search results |
| `maxResults` | integer | 100 | Per destination. See Limits |
| `checkIn` / `checkOut` | string | | `YYYY-MM-DD`. Give both or neither |
| `adults` / `children` / `infants` / `pets` | integer | 1 / 0 / 0 / 0 | |
| `currency` | string | `USD` | |
| `locale` | string | `en-US` | |
| `priceMin` / `priceMax` | integer | | Airbnb filters on the **nightly** price |
| `enrichUserProfiles` | boolean | `false` | One extra public request per host |

At least one of `locationQueries` or `startUrls` is required. Invalid room URLs are skipped, reported in the run summary, and never charged.

### Output

One row per unique listing. URL inputs come first in the order you gave them, then search results by destination, page and position on the page.

Every row carries `sourceMode` and `priceSource` so you can tell where its data came from:

| Field | Meaning |
|---|---|
| `sourceMode` | `search` or `direct_url` |
| `priceSource` | `search_result` when a price is present, `null` when there is none |
| `hasDetails` | Whether the detail page was fetched |
| `hostEnriched` | Whether host enrichment ran and succeeded |
| `detailError` / `hostError` | Set when that step failed; the row is still delivered |

Core fields: `listingId`, `url`, `name`, `title`, `price`, `stayWindow`, `checkIn`, `checkOut`, `rating`, `reviewsCount`, `ratingText`, `coordinates`, `badges`, `paymentMessages`, `details`, `images`.

With `includeDetails` or a direct URL you also get: `propertyType`, `roomType`, `personCapacity`, `homeTier`, `subDescription`, `description`, `htmlDescription`, `descriptionOriginalLanguage`, `seoTitle`, `metaDescription`, `locationDescriptions`, `breadcrumbs`, `amenities`, `houseRules`, `highlights`, `ratingDetails` (six sub-ratings), `host`, `coHosts`, `androidLink`, `iosLink`.

With `enrichUserProfiles` the `host` object also carries `about`, `timeAsHost`, `ratingAverage`, `reviewCount`, `listingsCount`, `verifiedSince`, `hasVerificationBadge` and `highlights` such as languages and where the host lives.

### Pricing

| Event | Price | When |
|---|---|---|
| `listing-delivered` | $0.001 | A search result delivered without details |
| `detailed-listing-delivered` | $0.0015 | A search result with details, or a direct room URL |

Host enrichment does not add a third rate.

Not charged: invalid URLs, destinations that return nothing, failed fetches, and any listing already delivered in this run or a previous one. A listing whose detail fetch failed falls back to search data and is charged at the lower rate.

### Limits

Stated here rather than discovered later.

**URL-only rows have no price and no availability.** Airbnb does not expose booking prices or availability on anonymous direct-URL listing pages, so these fields may be null for URL-only inputs. Prices are available for destination search results. We verified this across 60 anonymous calls covering 10 listings, 3 date ranges, 2 guest configurations and 4 regions: the price field came back empty every time.

**Airbnb changes its own search results.** The same destination searched twice returns a somewhat different set of listings. Measured across three destinations, two runs of the same query overlap about 76% with dates and about 63% without. This Actor cannot promise that a repeated run returns an identical result set.

**About 220 unique listings per destination.** Airbnb caps a search at 15 pages, and pages overlap by roughly 20%. To cover a city, use several destinations or tighter areas.

**Without dates, prices are not comparable between rows.** Airbnb picks its own stay window per listing, and each row is priced for a different window. `stayWindow`, `checkIn` and `checkOut` tell you which window a row used.

**Neighbourhood text is titles only.** `locationDescriptions` carries section titles; Airbnb does not expose the body text to anonymous pages, so `content` is usually null.

**Some listings simply have less.** New listings show no rating, many have no description or highlights, and sub-ratings need enough reviews to exist. Those rows report null rather than a made-up value.

Not included: full availability calendars, review lists, revenue or occupancy estimates.

### Notes

**Commas in destinations are handled.** Airbnb geocodes a raw comma badly — `Paris, France` resolves to Paris, Tennessee. This Actor converts the separator to Airbnb's own form, so `Paris, France` reaches Paris in France. Country names are never stripped, because `Paris, TX` must stay in Texas.

**Price filters work on the nightly rate.** `priceMin` and `priceMax` filter Airbnb's nightly price, while the `price` on each row is the total for the stay. A row's own number will not fall inside the range you set; this is how Airbnb's filter works, not a bug.

### API and automation

```bash
curl "https://api.apify.com/v2/datasets/DATASET_ID/items?format=csv&clean=true" \
  -H "Authorization: Bearer APIFY_TOKEN"
```

Each run writes an `OUTPUT` record with how many destinations and URLs were requested and deduplicated, and how many listings were delivered, charged at each rate, skipped as duplicates, or failed.

A run that is interrupted and restarted picks up where it stopped. The Dataset is the source of truth for what was already delivered, so nothing is written or charged twice.

### FAQ

**Why is `price` null on my rows?** Those rows came from direct room URLs. Airbnb does not expose booking prices on anonymous listing pages. Add the destination to `locationQueries` instead, and the search result carries a price.

**Why did two runs of the same destination return different listings?** Airbnb changes its own search results between requests. Measured overlap between two runs of the same query is about 76% with dates and 63% without. De-duplicate by `listingId` and run more than once if you need a wider set.

**Why did I get fewer listings than `maxResults`?** Airbnb caps a single search at 15 pages and its pages overlap, so a destination yields roughly 220 unique listings at most. Use more destinations or tighter areas.

**A row has `detailError` set. Was I charged the higher rate?** No. A listing whose detail fetch failed falls back to search data and is charged as `listing-delivered`.

**`hostEnriched` is false on some rows.** The host profile page failed for that host. The listing itself is delivered normally and `hostError` says what happened.

**My run stopped early.** If `stoppedOnBudget` is true in the run summary, the run hit the maximum charge you allowed. Raise the limit and start it again; listings already delivered are not refetched or recharged.

**Why are prices for different rows not comparable?** You ran without dates. Airbnb then picks its own stay window per listing, so each row is priced for different nights. `stayWindow` tells you which window a row used. Set `checkIn` and `checkOut` to compare like with like.

**Can I get the availability calendar or reviews?** Not from this Actor. See Limits.

### Legal

This Actor is independently developed and is not affiliated with, endorsed by, or sponsored by Airbnb.

It reads public pages. It does not sign in, does not use cookies or credentials, and does not reach anything behind a login. You are responsible for how you use the data, including any applicable terms and data-protection rules.

# Actor input Schema

## `locationQueries` (type: `array`):

One destination per line, for example "Lisbon, Portugal". A comma is handled for you: Airbnb geocodes "Paris, France" to a different city unless the separator is normalised, which this Actor does.

## `startUrls` (type: `array`):

Airbnb room links (airbnb.com/rooms/…) or plain numeric room IDs. Order is preserved. Invalid entries are skipped and never charged.

## `includeDetails` (type: `boolean`):

Off: search results only, charged as listing-delivered. On: one extra public request per listing for amenities, house rules, sub-ratings, host and images, charged as detailed-listing-delivered. Direct room URLs always fetch details.

## `maxResults` (type: `integer`):

Airbnb caps a single search at 15 pages. Pages overlap, so a destination yields roughly 220 unique listings at most.

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

YYYY-MM-DD. Give both dates or neither. Without dates Airbnb picks a stay window per listing, so prices are not comparable across rows.

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

YYYY-MM-DD.

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

Number of adults for the stay. Affects which listings Airbnb returns and how they are priced.

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

Number of children aged 2 to 17.

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

Number of infants under 2. Airbnb does not count them toward a listing capacity.

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

Number of pets. Listings that do not allow pets are filtered out by Airbnb.

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

Prices come back in this currency, as Airbnb formats them.

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

Language of names, descriptions and amenity labels.

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

Airbnb filters on the nightly price. The price field on each row is the stay total, so a row's own number will not fall inside this range.

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

Airbnb filters on the nightly price. The price field on each row is the stay total, so a row own number will not fall inside this range.

## `enrichUserProfiles` (type: `boolean`):

One extra public request per host for about text, hosting years, host rating and review count, listing count, verification and profile highlights such as languages. Charged at the same rate; a failed enrichment never fails the listing.

## Actor input object example

```json
{
  "locationQueries": [
    "Lisbon, Portugal"
  ],
  "includeDetails": false,
  "maxResults": 100,
  "adults": 1,
  "children": 0,
  "infants": 0,
  "pets": 0,
  "currency": "USD",
  "locale": "en-US",
  "enrichUserProfiles": false
}
```

# Actor output Schema

## `listings` (type: `string`):

No description

## `runSummary` (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 = {
    "locationQueries": [
        "Lisbon, Portugal"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("meka.im/airbnb-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 = { "locationQueries": ["Lisbon, Portugal"] }

# Run the Actor and wait for it to finish
run = client.actor("meka.im/airbnb-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 '{
  "locationQueries": [
    "Lisbon, Portugal"
  ]
}' |
apify call meka.im/airbnb-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,meka.im/airbnb-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/dzXrf89WYpj78fwvj/builds/J0a6AO28o3MVkr2hR/openapi.json
