# Airbnb Room URL Discovery Scraper (`automation-lab/airbnb-room-url-discovery-scraper`) Actor

Resolve Airbnb destinations and search URLs into deduplicated listing IDs, canonical room URLs, search positions, and compact discovery context.

- **URL**: https://apify.com/automation-lab/airbnb-room-url-discovery-scraper.md
- **Developed by:** [Stas Persiianenko](https://apify.com/automation-lab) (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.
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 Room URL Discovery Scraper

Turn public Airbnb destinations and search URLs into a clean inventory of canonical room URLs.
The Actor returns stable listing IDs, search positions, page numbers, titles, room types, ratings,
prices, images, and compact discovery context without opening every listing detail page.

Use it when the first step in your workflow is **finding which Airbnb listings exist**.
It is intentionally a discovery Actor, not a full listing-detail, availability, or booking scraper.

### What does this Airbnb scraper do?

The Actor accepts either:

- destination phrases such as `Barcelona, Spain`;
- public Airbnb search URLs ending in `/homes`; or
- both input types in one run.

It reads Airbnb's public search results, follows bounded pagination, creates canonical
`https://www.airbnb.com/rooms/<listingId>` URLs, and removes duplicate listing IDs across all inputs.
Filters already present in a supplied search URL—such as guest counts, bedroom counts, prices,
amenities, dates, or map bounds—are preserved.

### Who is it for?

- **Market researchers** building destination-level accommodation inventories.
- **Travel data teams** preparing URL queues for later detail or availability enrichment.
- **Analysts** comparing listing presence across cities or recurring snapshots.
- **Automation builders** who need stable Airbnb listing IDs as join keys.
- **Data engineers** feeding room URLs into spreadsheets, databases, or downstream Actors.

### Why use a URL discovery Actor?

Discovery and enrichment have different cost profiles.
This Actor avoids requesting every room detail page when you only need an inventory first.
That makes it useful for two-stage pipelines:

1. discover candidate listing URLs cheaply;
2. select the relevant IDs;
3. enrich only those listings that matter.

Each record also keeps its source search, one-based position, and page number so you can trace
where it came from.

### What Airbnb data is extracted?

| Field | Meaning |
|---|---|
| `listingId` | Stable numeric Airbnb listing ID |
| `listingUrl` | Canonical public room URL |
| `searchInput` | Original destination phrase or search URL |
| `sourceUrl` | Normalized first-page URL |
| `searchUrl` | Exact paginated URL where the result appeared |
| `searchPosition` | One-based position within that source search |
| `pageNumber` | One-based result page |
| `title` | Listing subtitle/name from the search card |
| `roomType` | Accommodation type shown in search |
| `rating` | Average rating when available |
| `reviewCount` | Parsed review count when available |
| `isSuperhost` | Whether the card shows a Superhost badge |
| `thumbnailUrl` | Primary search-card image |
| `latitude`, `longitude` | Public search coordinates when available |
| `price` | Localized price text for current filters |
| `priceQualifier` | Price qualifier such as `total` |
| `scrapedAt` | UTC extraction timestamp |

Some fields can be absent because new listings or particular card layouts do not expose them.
`listingId` and `listingUrl` are the core discovery output.

### How to get started

1. Open the Actor input page.
2. Add one or more destinations, Airbnb search URLs, or both.
3. Set `maxItems` to the maximum unique room URLs you need.
4. Set `maxPagesPerSearch` to bound requests per input.
5. Choose a locale and currency if the displayed context matters.
6. Run the Actor.
7. Open the **Results** dataset view or export it as JSON, CSV, Excel, XML, or RSS.

A useful first run is:

```json
{
  "destinations": ["Barcelona, Spain"],
  "maxItems": 20,
  "maxPagesPerSearch": 2,
  "locale": "en-US",
  "currency": "USD"
}
```

### Input parameters

| Input | Type | Default | Notes |
|---|---|---:|---|
| `destinations` | string\[] | — | Cities, regions, or destination phrases |
| `startUrls` | URL\[] | — | Public `airbnb.com` search URLs ending in `/homes` |
| `maxItems` | integer | 100 | Global unique-record limit, from 1 to 1,000 |
| `maxPagesPerSearch` | integer | 3 | Per-input page limit, from 1 to 15 |
| `locale` | string | `en-US` | Language or language-country code |
| `currency` | string | `USD` | Uppercase three-letter currency code |
| `proxyConfiguration` | object | direct | Optional Apify or custom proxy settings |

At least one destination or search URL is required.
The Actor rejects non-Airbnb URLs, room detail URLs, malformed locale values, and malformed currencies.

### Using filtered Airbnb search URLs

Build filters on Airbnb first, then copy the resulting `/homes` URL into `startUrls`.
For example:

```json
{
  "startUrls": [
    {
      "url": "https://www.airbnb.com/s/Amsterdam--Netherlands/homes?adults=2&min_bedrooms=1"
    }
  ],
  "maxItems": 50,
  "maxPagesPerSearch": 3,
  "currency": "EUR"
}
```

The Actor keeps the supplied query parameters when it requests later result pages.
It does not silently replace the user's search scope.

### Output example

A current result has this shape:

```json
{
  "listingId": "5561974",
  "listingUrl": "https://www.airbnb.com/rooms/5561974",
  "searchInput": "Barcelona, Spain",
  "sourceUrl": "https://www.airbnb.com/s/Barcelona--Spain/homes?locale=en-US&currency=USD",
  "searchUrl": "https://www.airbnb.com/s/Barcelona--Spain/homes?locale=en-US&currency=USD",
  "searchPosition": 1,
  "pageNumber": 1,
  "title": "1 Bed in shared 16 beds dorm",
  "roomType": "Shared room in Barcelona",
  "rating": 4.72,
  "reviewCount": 1655,
  "isSuperhost": true,
  "thumbnailUrl": "https://a0.muscache.com/im/pictures/example.jpeg",
  "latitude": 41.38,
  "longitude": 2.17,
  "price": "$219 total",
  "priceQualifier": "total",
  "scrapedAt": "2026-08-05T12:00:00.000Z"
}
```

Values change with dates, guest settings, locale, currency, and Airbnb's live inventory.

### How much does it cost to discover Airbnb room URLs?

Pricing is pay per event: one start event per run plus one `item` event for each unique room URL saved.
The configuration charges a **$0.0024 start fee**.
Per-item prices decrease by Apify plan tier:

| Plan tier | Price per discovered room URL |
|---|---:|
| Free | $0.003680 |
| Bronze | $0.003200 |
| Silver | $0.002496 |
| Gold | $0.001920 |
| Platinum | $0.001280 |
| Diamond | $0.000896 |

Examples before any separate platform subscription fees:

- 10 results on Free: `$0.0024 + 10 × $0.003680 = $0.0392`.
- 100 results on Bronze: `$0.0024 + 100 × $0.003200 = $0.3224`.
- 1,000 results on Gold: `$0.0024 + 1,000 × $0.001920 = $1.9224`.

Only unique dataset records are charged as items.
Duplicates and rejected records are not charged as output items.
Optional proxy usage may add platform infrastructure usage depending on your Apify setup.
Always check the live pricing panel for the applicable tier.

### Pagination, limits, and deduplication

`maxItems` applies globally across every input in the run.
`maxPagesPerSearch` applies separately to each destination or URL.
The Actor stops scheduling pages once the global result limit is reached.

Deduplication uses the numeric Airbnb listing ID.
If one listing appears in Barcelona and another supplied map/search URL, the first occurrence is kept.
Its position remains the position in the source search before cross-search deduplication.

Search inventories are snapshots, not exhaustive declarations of every property that exists.
Airbnb can personalize, reorder, insert, remove, or cap public results.

### Scheduling and monitoring workflows

Use an Apify Schedule to run the same input daily or weekly.
Store each run's dataset in your destination system, then compare records by `listingId`.
Typical derived signals include:

- newly discovered listing IDs;
- IDs no longer visible in the selected search;
- position changes;
- changed rating or price text; and
- cross-city duplicates.

The Actor emits the snapshot; it does not itself send alerts or calculate historical diffs.
Use an integration, webhook, or downstream workflow for those actions.

### Export and integration options

The default dataset supports JSON, JSONL, CSV, Excel, XML, and RSS exports.
Common patterns include:

- send new rows to Google Sheets;
- upsert by `listingId` into a database;
- enqueue `listingUrl` values for a detail Actor;
- trigger a webhook when a scheduled run finishes;
- join listing IDs with availability or host datasets; and
- compare snapshots in Python, SQL, or a data warehouse.

Because `listingId` is stable and `listingUrl` is canonical, both are convenient pipeline keys.

### Run through the Apify API with cURL

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~airbnb-room-url-discovery-scraper/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "destinations": ["Barcelona, Spain"],
    "maxItems": 20,
    "maxPagesPerSearch": 2
  }'
```

For production, prefer an `Authorization: Bearer` header instead of putting tokens in URLs.
Retrieve results from the run's default dataset ID.

### Run through the Apify API with JavaScript

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/airbnb-room-url-discovery-scraper').call({
  destinations: ['Amsterdam, Netherlands'],
  maxItems: 50,
  maxPagesPerSearch: 3,
  currency: 'EUR',
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map(({ listingId, listingUrl }) => ({ listingId, listingUrl })));
```

### Run through the Apify API with Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/airbnb-room-url-discovery-scraper').call(run_input={
    'destinations': ['Berlin, Germany'],
    'maxItems': 50,
    'maxPagesPerSearch': 3,
    'currency': 'EUR',
})
items = client.dataset(run['defaultDatasetId']).list_items().items
print([(item['listingId'], item['listingUrl']) for item in items])
```

### Use with MCP and AI agents

Add the Actor to Claude Code through Apify MCP:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/airbnb-room-url-discovery-scraper"
```

#### Claude Desktop

Add this server in Claude Desktop's MCP configuration:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/airbnb-room-url-discovery-scraper"
    }
  }
}
```

#### Cursor

Use the same JSON server entry in Cursor's MCP settings, then enable the `apify` server.

#### VS Code

Add the same HTTP MCP server URL to your VS Code MCP configuration and start the server from the MCP panel.

Example prompts:

- “Discover 30 Airbnb room URLs in Barcelona and return listing IDs and positions.”
- “Resolve this filtered Amsterdam Airbnb search URL and save the result as CSV.”
- “Build one deduplicated listing inventory for Barcelona, Amsterdam, and Berlin.”

### Tips for reliable results

- Supply Airbnb search URLs when exact filters matter.
- Use destination names with country or region context to avoid ambiguous places.
- Start with one or two pages before increasing scale.
- Keep scheduled inputs stable when comparing snapshots.
- Use dates and guest counts in the source URL when prices must be comparable.
- Leave proxy configuration disabled unless direct access fails in your environment.
- Do not interpret a missing result as proof that a listing was removed.

The Actor requests HTML and embedded public search state only; it does not download listing images.

### Limitations and failure behavior

- Public Airbnb search results can be capped, reordered, personalized, or changed without notice.
- This Actor does not scrape complete room details, reviews, calendars, hosts, or booking availability.
- Search cards may omit ratings, review counts, coordinates, or prices.
- A destination phrase can be ambiguous; use a filtered Airbnb URL for exact scope.
- Login-only and private pages are unsupported.
- The Actor fails if every search is blocked or if Airbnb's structured page data is missing.
- In a mixed batch, successful searches are retained and failed searches are logged as warnings.
- The maximum supported output is 1,000 unique records per run.

### Responsible and legal use

Scrape only public information you have a lawful purpose to process.
Respect Airbnb's terms, applicable database rights, privacy laws, contractual obligations,
and rate limits.
Do not use the Actor to harass individuals, discriminate, evade access controls, or republish
personal information unlawfully.

You are responsible for your input, retention policy, downstream processing, and compliance.
This Actor is not affiliated with or endorsed by Airbnb.

### Troubleshooting and FAQ

#### Why did my destination return the wrong area?

Use a more specific phrase such as `Barcelona, Spain`, or copy a search URL directly from Airbnb.
A URL with map bounds is the most precise option.

#### Why did I receive fewer items than `maxItems`?

The source may expose fewer results, the page limit may be reached, or duplicates may have been removed.
Increase `maxPagesPerSearch` only when the source has another cursor.

#### Why are price or rating fields missing?

Airbnb does not show every field on every search card.
New listings can lack ratings, and price display depends on dates and guest filters.

#### What happens if Airbnb blocks a page?

The Actor retries bounded transient failures.
If all searches fail, the run exits with an error instead of returning a misleading empty success.
You can optionally configure an Apify or custom proxy.

#### Does it discover every Airbnb listing in a city?

No. It discovers the public results surfaced by the selected searches and page limits at run time.
Use multiple precise map/search URLs if your research method requires broader coverage.

#### Can I provide room detail URLs?

No. This Actor resolves search scopes into room URLs.
Use a listing detail Actor after discovery.

### Related automation-lab Actors

- [Airbnb Listing Scraper](https://apify.com/automation-lab/airbnb-listing) for deeper listing enrichment.
- [Airbnb Host Portfolio Scraper](https://apify.com/automation-lab/airbnb-host-portfolio-scraper) for public host-to-listing relationships.
- [Airbnb Shared Wishlist Scraper](https://apify.com/automation-lab/airbnb-shared-wishlist-scraper) for room URLs and context from shared wishlists.

A common pipeline is discovery here, selective detail enrichment, then scheduled snapshot comparison.

# Actor input Schema

## `destinations` (type: `array`):

Cities, regions, or destination phrases to search on Airbnb, such as Barcelona, Spain.

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

Public Airbnb destination or search URLs ending in /homes. Existing dates, guest counts, prices, amenities, map bounds, and other URL filters are preserved.

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

Maximum number of unique listing records saved across all searches.

## `maxPagesPerSearch` (type: `integer`):

Maximum Airbnb result pages requested for each destination or search URL.

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

Optional Airbnb language or language-country code, such as en-US, de-DE, or fr.

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

Optional three-letter uppercase currency code used in discovery context, such as USD or EUR.

## `proxyConfiguration` (type: `object`):

Optional Apify or custom proxy configuration. Direct requests are used when omitted.

## Actor input object example

```json
{
  "destinations": [
    "Barcelona, Spain"
  ],
  "startUrls": [
    {
      "url": "https://www.airbnb.com/s/Amsterdam--Netherlands/homes"
    }
  ],
  "maxItems": 20,
  "maxPagesPerSearch": 3,
  "locale": "en-US",
  "currency": "USD",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

Dataset containing discovered Airbnb room URLs.

# 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 = {
    "destinations": [
        "Barcelona, Spain"
    ],
    "startUrls": [
        {
            "url": "https://www.airbnb.com/s/Amsterdam--Netherlands/homes"
        }
    ],
    "maxItems": 20,
    "maxPagesPerSearch": 3,
    "locale": "en-US",
    "currency": "USD"
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/airbnb-room-url-discovery-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 = {
    "destinations": ["Barcelona, Spain"],
    "startUrls": [{ "url": "https://www.airbnb.com/s/Amsterdam--Netherlands/homes" }],
    "maxItems": 20,
    "maxPagesPerSearch": 3,
    "locale": "en-US",
    "currency": "USD",
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/airbnb-room-url-discovery-scraper").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 '{
  "destinations": [
    "Barcelona, Spain"
  ],
  "startUrls": [
    {
      "url": "https://www.airbnb.com/s/Amsterdam--Netherlands/homes"
    }
  ],
  "maxItems": 20,
  "maxPagesPerSearch": 3,
  "locale": "en-US",
  "currency": "USD"
}' |
apify call automation-lab/airbnb-room-url-discovery-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=automation-lab/airbnb-room-url-discovery-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/TnFqHn2IFjATiBo8m/builds/vsYgGqJUWT8i8R94S/openapi.json
