# Housing.com $0.7💰 (`abotapi/housing-com-scraper`) Actor

From $0.7/1K. Scrape residential property listings from housing.com. Supports buy and rent across 750+ cities.

- **URL**: https://apify.com/abotapi/housing-com-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** Real estate, Developer tools, Automation
- **Stats:** 33 total users, 10 monthly users, 98.9% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $0.70 / 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

## Housing.com India Property Scraper

Scrape residential property listings from housing.com, India's leading property platform. Supports buy and rent across 750+ cities.

### Features

- **750+ Indian cities** - Mumbai, Delhi, Bangalore, Pune, Chennai, Kolkata, and more
- **Buy & Rent** - new projects, resale, and rental listings
- **Rich data** - BHK configurations with individual pricing, coordinates, amenities, images, seller info, RERA tags, EMI
- **Post-filters** - filter by BHK, price range, and property type (client-side)
- **City aliases** - "Delhi", "Bangalore", "Bombay", "Calcutta", "Madras" all resolve correctly
- **Fast & cheap** - ~700-1000 listings/min

### Quick Start

#### Buy properties in Mumbai

```json
{
  "mode": "search",
  "service": "buy",
  "city": "Mumbai",
  "max_properties": 30
}
```

#### 3 BHK apartments in Andheri West under 5 Cr

```json
{
  "mode": "search",
  "service": "buy",
  "city": "Mumbai",
  "locality": "andheri_west",
  "min_bhk": 3,
  "max_bhk": 3,
  "max_price": 50000000,
  "max_properties": 50
}
```

#### Rentals in Bangalore Koramangala

```json
{
  "mode": "search",
  "service": "rent",
  "city": "Bangalore",
  "locality": "koramangala",
  "max_properties": 30
}
```

#### 2-3 BHK in Delhi Dwarka

```json
{
  "mode": "search",
  "service": "buy",
  "city": "Delhi",
  "locality": "dwarka",
  "min_bhk": 2,
  "max_bhk": 3,
  "max_properties": 100,
  "max_pages": 5
}
```

#### Daily monitoring of a search (incremental mode)

```json
{
  "mode": "search",
  "service": "buy",
  "city": "Mumbai",
  "locality": "andheri_west",
  "max_properties": 200,
  "incrementalMode": true,
  "emitExpired": true
}
```

Schedule this to run daily. The first run returns everything as `NEW`; later runs return only `NEW`/`UPDATED`/`REAPPEARED`/`EXPIRED` properties.

### Input Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `mode` | string | `search` | `search` (build query from city/locality) |
| `service` | string | `buy` | `buy` or `rent` |
| `city` | string | `Mumbai` | City name. Aliases supported: Delhi, Bangalore, Bombay, Calcutta, Madras, Gurugram |
| `locality` | string | - | Locality within city (e.g. `andheri_west`, `bandra_west`, `koramangala`). Leave empty for entire city |
| `min_price` | integer | - | Minimum price in INR (post-filter). Buy: e.g. 5000000 = 50L. Rent: e.g. 30000 |
| `max_price` | integer | - | Maximum price in INR (post-filter) |
| `min_bhk` | number | - | Minimum BHK, e.g. 2 (post-filter) |
| `max_bhk` | number | - | Maximum BHK, e.g. 3 (post-filter) |
| `property_type` | string | - | `project`, `resale`, or `rent` (post-filter) |
| `max_properties` | integer | `30` | Max properties to scrape (0=unlimited). Counts properties that passed the filters above (same properties `push_data` would emit with Incremental mode off) — in Incremental mode a subset of that count may still be suppressed as UNCHANGED rather than actually pushed/billed. |
| `max_pages` | integer | `5` | Max pages (30 listings/page) |
| `resumeFromRunId` | string | - | Paste a previous run ID or dataset ID to continue a large pull without returning or charging for properties already collected there. See **Resume & recurring updates** below. |
| `incrementalMode` | boolean | `false` | Turn on for daily/recurring monitoring of the same search — see **Resume & recurring updates** below. |
| `stateKey` | string | - | Optional. Name a monitoring campaign, or share state across differently-configured runs. Incremental mode only. |
| `emitUnchanged` | boolean | `false` | Also return (and bill) properties unchanged since the last run. Incremental mode only. |
| `emitExpired` | boolean | `false` | Also return (and bill) properties no longer found, once a run fully scanned the tracked search. Incremental mode only. |
| `proxy` | object | - | Optional proxy configuration |

### Resume & recurring updates

Two distinct features:

- **Resume** (`resumeFromRunId`) continues **one** interrupted pull. Paste a previous run ID or dataset ID and this run skips every `listingId` already collected there — no re-scraping, no re-billing.
- **Incremental mode** (`incrementalMode`) is for scheduling this actor daily/weekly against the **same** city/locality/service (or URL set) and filters, and getting only what changed. The actor remembers its own state in a key-value store — no run ID to paste.
  - The first run returns every matching property as `NEW`.
  - Later runs return `NEW`, `UPDATED`, and `REAPPEARED` properties by default. `UNCHANGED` properties are suppressed (not returned, not billed) unless `emitUnchanged` is on.
  - `EXPIRED` properties (tracked previously, no longer found) are only ever produced when `emitExpired` is on **and** the run fully scanned the tracked search to its natural end — not when `max_properties`/`max_pages` capped it, and not when `resumeFromRunId` was used, since a partial scan cannot tell "gone" from "not reached yet".
  - State is tracked per **city/locality/service (or URL set) + every post-filter** (`min_price`, `max_price`, `min_bhk`, `max_bhk`, `property_type`) — `max_properties`/`max_pages` are caps, not part of the tracked scope, so raising them never starts a new monitoring campaign. Set `stateKey` to name a campaign explicitly or to deliberately share state across differently-configured runs.
  - Each returned property gets `changeType`, `changedFields`, `firstSeenAt`, `lastSeenAt`. These four fields are absent entirely when `incrementalMode` is off — the default output shape is unchanged.
  - `UPDATED` means the property's own listed details changed (price, title, area, amenities, images, ...). It ignores `scraped_at`, which is just a timestamp of when this actor happened to fetch the page — that field always differs run to run and is excluded from comparison for that reason alone, not because housing.com's own data is unreliable. `scraped_at` still appears with its live, current value on every returned row.

### Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape — the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human-readable **summary** of each record — not the full JSON. Each item becomes one entry with a **title** and its key fields flattened to plain text. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify → Settings → Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. (If the picker is empty, you haven't authorized a connector yet.)
3. For **Notion**, also set `notionParentPageUrl` to the page where items should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

### Output Fields

| Field | Type | Description |
|-------|------|-------------|
| `listingId` | string | Unique listing ID |
| `title` | string | Property or project name |
| `subtitle` | string | BHK summary (e.g. "2, 3 BHK Flats") |
| `address` | string | Locality and city |
| `propertyType` | string | `project`, `resale`, or `rent` |
| `price_display` | string | Formatted price (e.g. "₹9.8 Cr") |
| `price_values` | array | Raw price in INR \[min, max] |
| `currency` | string | Always "INR" |
| `area_value` | number | Built-up area |
| `area_unit` | string | Area unit (sq.ft) |
| `latitude` | number | Property latitude |
| `longitude` | number | Property longitude |
| `features` | array | Configurations, possession date, avg price |
| `bhk_configs` | array | Individual BHK options with pricing |
| `amenities` | array | Society amenities |
| `sellers` | array | Seller/agent names |
| `seller_firms` | array | Agency/firm names |
| `highlights` | array | Property highlights (parking, gym, etc.) |
| `emi` | string | Estimated monthly EMI |
| `tags` | array | Tags (e.g. `rera_verified`) |
| `images` | array | Image URLs (up to 15) |
| `image_count` | integer | Total images available |
| `url` | string | Full URL on housing.com |
| `changeType` | string | Incremental mode only: `NEW` / `UPDATED` / `UNCHANGED` / `REAPPEARED` / `EXPIRED` vs. the previous run for this state key. Absent when Incremental mode is off. |
| `changedFields` | array | Incremental mode only: field names that changed since the previous run. Never includes `scraped_at` (see Resume & recurring updates above). |
| `firstSeenAt` | string | Incremental mode only: ISO timestamp this property was first seen under this state key. |
| `lastSeenAt` | string | Incremental mode only: ISO timestamp this property was last seen present under this state key. |

# Actor input Schema

## `mode` (type: `string`):

Scraping mode: 'search' builds query from city/locality, 'url' extracts locality from provided housing.com URLs

## `urls` (type: `array`):

Housing.com search URLs (url mode only). e.g. https://housing.com/in/buy/search?f=eyJiYX...

## `service` (type: `string`):

Buy or Rent

## `city` (type: `string`):

City name (e.g. 'Mumbai', 'Delhi', 'Bangalore', 'Pune'). Use the city listing to find exact names.

## `locality` (type: `string`):

Locality/area within the city (e.g. 'andheri\_west', 'bandra\_west'). Leave empty for entire city.

## `min_price` (type: `integer`):

Minimum price in INR. Post-filter — applied client-side after fetching results. For buy: e.g. 5000000 = 50 Lakhs. For rent: e.g. 30000 = ₹30K/month.

## `max_price` (type: `integer`):

Maximum price in INR. Post-filter — applied client-side.

## `min_bhk` (type: `number`):

Minimum BHK (e.g. 2 for 2+ BHK). Post-filter — applied client-side.

## `max_bhk` (type: `number`):

Maximum BHK (e.g. 3 for up to 3 BHK). Post-filter — applied client-side.

## `property_type` (type: `string`):

Filter by property type. Post-filter — applied client-side.

## `max_properties` (type: `integer`):

Maximum number of properties to scrape (0=unlimited)

## `max_pages` (type: `integer`):

Maximum number of result pages (30 listings per page)

## `resumeFromRunId` (type: `string`):

Paste a previous run ID or dataset ID to continue a large pull of properties without returning or charging for properties already collected there. Use this after an interrupted run, or when continuing a pull in another run. For recurring daily monitoring of the same search, use Incremental mode below instead.

## `incrementalMode` (type: `boolean`):

Turn this on for daily or recurring monitoring of the same city/locality/URL search. The first run returns all matching properties as NEW. Later runs normally return only NEW, UPDATED, and REAPPEARED properties -- a property is UPDATED only when its own listed details change (price, title, area, amenities, images, etc.); the scraped\_at timestamp is ignored for this comparison since it always differs run to run by construction (every returned property still carries its current scraped\_at value). Turn on "Emit unchanged" or "Emit expired" only when you also want those properties returned (and billed). State is kept separately for each city/locality/service or URL set and filter combination; use State key when you want to name or deliberately share a monitoring campaign. To continue one specific interrupted run instead, use Resume from a previous run above.

## `stateKey` (type: `string`):

Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently-configured runs. Leave empty to let the actor derive a key automatically from the city/locality/service (or URLs) and filters used -- different search setups then never mix state with each other.

## `emitUnchanged` (type: `boolean`):

Off by default. Turn on to also return properties that have not changed since the last run, marked UNCHANGED. This returns -- and bills -- extra rows you already have, so leave it off unless you specifically want the full snapshot every run.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to also return properties that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked search to its natural end -- not when Max Properties or Max Pages capped it, or when Resume was used. This returns -- and bills -- extra synthetic rows, so leave it off unless you need expiry tracking.

## `proxy` (type: `object`):

Proxy configuration. Optional — the API works without proxy.

## `dataset_name` (type: `string`):

Custom dataset name (leave empty for default)

## `clear_dataset` (type: `boolean`):

Clear the dataset before scraping

## `mcpConnectors` (type: `array`):

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify → Settings → Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON — the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "search",
  "service": "buy",
  "city": "Mumbai",
  "max_properties": 30,
  "max_pages": 1000,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "clear_dataset": false,
  "maxNotifyListings": 50
}
```

# Actor output Schema

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

Individual property listing records with price, BHK configurations, coordinates, and detail data.

## `metadata` (type: `string`):

Scraping run metadata including timing, counts, and configuration.

# 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 = {
    "mode": "search",
    "city": "Mumbai",
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/housing-com-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 = {
    "mode": "search",
    "city": "Mumbai",
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/housing-com-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 '{
  "mode": "search",
  "city": "Mumbai",
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false
}' |
apify call abotapi/housing-com-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=abotapi/housing-com-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

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