# Homes.com Scraper: For Sale, Rent, Sold & Open Houses (`abotapi/homes-com-scraper`) Actor

Scrape US property records from Homes.com. Pick a city, state or ZIP and a channel (for sale, for rent, recently sold, open houses, foreclosures), or paste links. One flat row per property with price, beds, baths, size, address, geo, agent and brokerage.

- **URL**: https://apify.com/abotapi/homes-com-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Features:** Limited permissions
- **Stats:** 3 total users, 0 monthly users, 79.3% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.80 / 1,000 property 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/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

## Homes.com Scraper: For Sale, Rent, Sold & Open Houses

Homes.com Scraper turns Homes.com, one of the most visited US real estate portals, into a clean property data feed. Get price, beds, baths, square footage, full address, coordinates, listing agent and brokerage for homes for sale, for rent, recently sold, open houses and foreclosures across the United States. Search by city, state or ZIP, or paste Homes.com links, then export to JSON, CSV or Excel, or pull the results straight into your app through the API.

### Why This Scraper?

- **Forty listings per page, no extra requests.** Every search results page already carries a full set of detailed listings, so a typical search returns complete rows without opening a single property page.
- **Detail enrichment on demand.** Switch on price and tax history, HOA fees, parking, utilities, schools, highlights and the full description, and pay the surcharge only for listings actually returned.
- **Five ways to shape a search.** Location, ZIP, bedroom count, a fixed maximum price and sort order, all built from segments Homes.com actually publishes.
- **Every channel Homes.com offers.** For sale, for rent, recently sold, open houses and foreclosures, plus per-property-type variants like houses, condos, townhouses and land.
- **Paste links instead of building a search.** A property link returns one fully detailed row; a search link is walked page by page from wherever it starts, including a link that already points at page 2 or later.
- **Built for monitoring.** Incremental mode returns only new and updated listings on recurring runs by default (plus reappeared and expired ones once Emit expired is also on), and a run that could not read anything fails loudly instead of reporting a false empty result.
- **Resume without re-paying.** Continue one interrupted crawl by pasting its run or dataset ID; listings it already returned are skipped, not re-collected or re-billed.

### Use Cases

- **Real estate investors:** track price drops and days-on-market across a metro area to time offers.
- **PropTech and analytics teams:** build historical price and tax datasets for valuation models.
- **Lead generation and CRM tools:** pull the agent and brokerage contact details published on each listing.
- **Market researchers:** compare rent and for-sale channels across ZIP codes and property types.
- **Portfolio managers:** monitor a saved search or a set of properties on a schedule for what actually changed.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live record.

| Field | Example |
| --- | --- |
| `listingKey` | `"q7w8e9r0t1y2u"` |
| `url` | `"https://www.homes.com/property/1200-example-st-austin-tx-78701/q7w8e9r0t1y2u/"` |
| `listingChannel` / `isSold` | `"for_sale"` / `false` |
| `name` | `"1200 Example St, Austin, TX 78701"` |
| `price` / `priceCurrency` | `545000` / `"USD"` |
| `priceLabel` | `"Last List Price"` (recently-sold channel only; usually absent for sale/rental listings) |
| `statusLabel` | `"$10K PRICE DROP"` (also `"Sold Feb 3, 2026"` on a recently-sold record) |
| `bedrooms` / `bathrooms` | `4` / `2.5` |
| `livingAreaSqft` / `yearBuilt` | `2410` / `2006` |
| `streetAddress` / `city` / `state` / `postalCode` | `"1200 Example St"` / `"Austin"` / `"TX"` / `"78701"` |
| `propertyType` | `"SingleFamilyResidence"` |
| `soldDate` / `daysOnMarket` / `pricePerSqft` | `"Feb 3, 2026"` / `18` / `226` (recently-sold channel only) |
| `agentName` / `agentTitle` | `"A. Example"` / `"Listing Agent"` |
| `agentPhone` / `agentEmail` | `"(512) 555-0142"` / `"agent@example.com"` |
| `brokerageName` | `"Example Realty Group"` |
| `primaryImageUrl` / `description` | lead photo URL / one-line summary |
| `latitude` / `longitude` | `30.2672` / `-97.7431` |
| `priceHistory` | one entry per event: `date`, `event`, `price`, `listToSalePercent`, `pricePerSqft` (with detail enrichment) |
| `taxHistory` | one entry per year: `taxPaid`, `totalAssessment`, `landAssessment`, `improvementAssessment` (with detail enrichment) |
| `changeType` | `"NEW"` (also `"UPDATED"`, `"UNCHANGED"` with incremental mode; `"REAPPEARED"`/`"EXPIRED"` only with Emit expired too) |

With detail enrichment on, a row also carries `priceHistorySource`, `taxHistorySource`, `amenityGroups` (the full home-details block, grouped as `{group, values}`), `hoaFees`, `parking`, `lotDetails`, `utilities`, `schools`, `highlights` and `fullDescription`. With incremental mode on, a row also carries `changedFields`, `firstSeenAt` and `lastSeenAt` (compact UPDATED rows also carry `priceChanges` and `variantChanges`). Every row also carries `country` (always `US`), `agentUrl`, `availability`, `datePosted`, `dateModified` and `scrapedAt`.

#### Output views

The dataset has four views: **Properties** (the main table), **Listing details** (price and tax history, HOA, schools and other enrichment fields), **Changes** (`changeType`, `changedFields`, `priceChanges` for incremental runs) and **Agents** (listing agent and brokerage). A run summary is also stored under the `OUTPUT` key of the key-value store.

#### Known limitation: card rows

When Homes.com serves a results page without its embedded data block (this happens on the unblocking fallback), rows are built from the visible listing cards. Those rows carry price, address, beds, baths, area and the property link, but `latitude`, `longitude`, agent fields, `datePosted`/`dateModified` and `propertyType` are `null`. Turn on **Fetch full property details** or paste the property link to get a fuller row.

### How to Use

1. Pick a **mode**: `search` (a US location and a listing channel) or `url` (paste Homes.com links).
2. In search mode, fill in the location and optional filters; in URL mode, paste search and/or property links.
3. Turn on **Fetch full property details** if you want price/tax history, HOA, schools and the full description.
4. Set **Max items** (and optionally **Max pages**) to control run size and cost, then click **Start**.

**Search for houses under a price cap, newest first:**

```json
{
  "mode": "search",
  "location": "Austin, TX",
  "listingType": "houses_for_sale",
  "maxPrice": 800000,
  "sort": "newest",
  "maxItems": 40
}
```

**Search rentals with a bedroom filter:**

```json
{
  "mode": "search",
  "location": "Denver, CO",
  "listingType": "for_rent",
  "minBedrooms": 2,
  "maxItems": 60
}
```

**Paste links (a sold-channel search page and a property page), with full details:**

```json
{
  "mode": "url",
  "urls": [
    "https://www.homes.com/austin-tx/sold/",
    "https://www.homes.com/property/1200-example-st-austin-tx-78701/q7w8e9r0t1y2u/"
  ],
  "listingType": "sold",
  "fetchDetails": true
}
```

**Recurring monitoring of a ZIP code:**

```json
{
  "mode": "search",
  "location": "78701",
  "listingType": "for_sale",
  "incrementalMode": true,
  "stateKey": "austin-78701-for-sale",
  "maxItems": 0
}
```

A property link always reads its own page (it is the only source for that row), so it bills the detail-enrichment surcharge for every property row it returns, regardless of the **Fetch full property details** toggle. A search link is walked exactly like search mode, and only bills the surcharge for the listings it returns when that toggle is on.

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/homes-com-scraper").call(run_input={
    "mode": "search", "location": "Austin, TX", "listingType": "for_sale", "maxItems": 40
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["name"], item["price"])
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/homes-com-scraper').call({
  mode: 'search', location: 'Austin, TX', listingType: 'for_sale', maxItems: 40,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

#### About the filters

Homes.com publishes filters as fixed URL pages rather than free-form parameters, and it silently ignores a page it does not recognise: asking for a filter it does not publish returns the **unfiltered** result set with a normal `200 OK`. That is a quiet way to be charged for rows you did not ask for, so this actor only offers the filters Homes.com actually publishes, and refuses anything else with a clear message instead of running an unfiltered search. In practice that means bedrooms are `1` to `5`, and the maximum price is one of $800,000 / $900,000 / $1,000,000 / $1,500,000 / $2,000,000. Filters build the search URL, so they apply to **search mode only**; a search link you paste in URL mode is walked from its path as given, but any query-string parameters on it are not kept.

#### Resume and recurring updates

**Resume** (`resumeFromRunId`) continues one interrupted run: paste its run or dataset ID and every listing it already returned is skipped this run, so you do not pay twice. A resumed search walk is not cut short by that overlap: the guard that ends a walk on a repeated page compares each page against this run's own earlier pages, never against the resumed seed, so the walk keeps going past pages the previous run already fully covered, and only stops on a genuinely short, empty or repeated page (within this walk), or at Max pages. If Max pages is set low enough that the walk cannot get past the pages the previous run already collected, a resumed run can still return zero new rows this time; raise Max pages, or leave it at `0`, to give the walk enough room to reach the new listings.

**Incremental mode** (`incrementalMode`) is for scheduled runs over the same search scope. Each listing is classified `NEW`, `UPDATED` (with `changedFields`), or `UNCHANGED` (suppressed and not billed unless `emitUnchanged` is on) by default. `REAPPEARED` and `EXPIRED` additionally need `emitExpired` on: `EXPIRED` is only produced once a run with `emitExpired` on has proved it scanned the whole tracked search end to end (not capped by Max items, not run with Resume, and it actually scanned something), and `REAPPEARED` can only be produced for a listing that a previous such run already marked `EXPIRED`. `stateKey` names or shares an incremental campaign; leave it empty and the actor derives one automatically from the location, channel, filters and detail settings. A listing is `UPDATED` when any returned field changed (price, status, description, photos, agent, details), listed in `changedFields`; the scrape time and `daysOnMarket` are returned but never compared, because they change on every run. `changeScope` set to `priceAndStock` narrows this to price, price label, currency, availability, status, sold flag, sold date and price-history events, so description, photo or agent edits never produce a row. `ignoreFieldsForChanges` excludes more fields (a dotted path such as `priceHistory.pricePerSqft` excludes that key inside every list element). `compactUpdates` returns `UPDATED` and `EXPIRED` rows in short form (identifiers, current price and availability, `priceChanges` with old and new value, `variantChanges` for price-history events); a compact row carries no detail-page data, so the detail-enrichment surcharge is not charged for it. `expireAfterMissedScans` (default 1) marks a listing `EXPIRED` only after it was missing from that many complete scans in a row; `EXPIRED` means "no longer found in the results", not proof of a sale. A run is never treated as a complete scan when Max items or Max pages stopped it, when a pasted link starts at page 2 or later, or when a shared State key was last used for a different search scope. All of these options are off by default and leave existing output unchanged.

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

Optionally pipe results straight into the tools you already use over Model Context Protocol: Notion, Linear, Airtable, or the Apify MCP server. 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.
3. For Notion, also set `notionParentPageUrl` to the page where item pages 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.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | select | `search` | `search` picks a location and a listing channel; `url` scrapes exactly the links you paste. |
| `location` | string | `Austin, TX` | Search mode. A US city and state (`Austin, TX`) or a 5-digit ZIP (`78701`). |
| `zipCode` | string | (none) | Search mode. Optionally narrow the search to one 5-digit ZIP inside the location. |
| `minBedrooms` | integer | (none) | Search mode. Homes.com publishes fixed per-count bedroom pages, so only 1 to 5 can be requested, and it matches listings with exactly that many bedrooms rather than a true minimum. |
| `maxPrice` | integer | (none) | Search mode. Fixed maximum-price page: 800000, 900000, 1000000, 1500000 or 2000000. Any other value is refused, not ignored. |
| `sort` | select | `relevance` | Search mode. `newest` puts the most recently listed properties first. |
| `urls` | array | 11 sample links, one per supported shape (see the URL shapes table) | URL mode. Homes.com search links (walked page by page) and/or property links (one detailed row each, always billing the detail surcharge). |
| `listingType` | select | `for_sale` | The listing channel: for sale (and per-property-type variants), for rent (and per-property-type variants), recently sold, open houses, foreclosures. Also labels the rows a pasted link returns. |
| `fetchDetails` | boolean | `false` | Read each property's own page: price and tax history, home details, HOA, parking, utilities, schools, highlights, full description. Adds a per-listing surcharge, charged only for listings actually returned. |
| `maxItems` | integer | `20` | The single cap on the run. `0` returns everything the search has, bounded by the natural end of results. |
| `maxPages` | integer | `0` | Optional extra ceiling on result pages walked per search. `0` means no page limit. |
| `resumeFromRunId` | string | (none) | Continue one interrupted crawl: paste a previous run ID or dataset ID, and listings it already returned are skipped. |
| `incrementalMode` | boolean | `false` | For scheduled monitoring: the first run returns everything as NEW, later runs return only what changed. |
| `changeScope` | string | `all` | `priceAndStock` makes only price/availability changes count as UPDATED. |
| `compactUpdates` | boolean | `false` | Short UPDATED and EXPIRED rows with old/new price values. |
| `expireAfterMissedScans` | integer | `1` | Complete scans a listing must be missing from before it is EXPIRED. |
| `ignoreFieldsForChanges` | array | (none) | Fields (or dotted paths) whose changes never make a listing UPDATED. |
| `stateKey` | string | (none) | Optional name for an incremental campaign. Different keys keep separate baselines; the same key shares one. |
| `emitUnchanged` | boolean | `false` | Also return listings that have not changed, marked UNCHANGED. Returns, and bills, extra rows. |
| `emitExpired` | boolean | `false` | Also return listings no longer found after a complete scan, marked EXPIRED. Returns, and bills, extra synthetic rows. |
| `proxy` | object | Apify Proxy enabled | Homes.com serves listing pages to US connections only and refuses low-quality exits, so the run climbs to a residential US connection when a cheaper one is refused, unless you pin a specific proxy group or use your own proxy URLs, which always take priority. |
| `mcpConnectors` | array | (none) | Pipe a condensed summary per item into Notion, Linear, Airtable or the Apify MCP server. Authorize once under Apify > Settings > Integrations. |
| `notionParentPageUrl` | string | (none) | Notion connector only: the parent page under which item pages are created. |
| `maxNotifyListings` | integer | `50` | Cap on items written per connector. Does not affect the dataset. |

#### URL mode: supported link shapes

Behaviour change: leaving `urls` untouched in URL mode now runs the 11 sample links below (one per shape) instead of stopping with "No URLs provided". Max items (default 20) caps the whole run, but each property link still bills the detail surcharge, so clear the list or replace it with your own links before running.

| Shape | Example |
|---|---|
| City search | `https://www.homes.com/austin-tx/` |
| Sold channel | `https://www.homes.com/austin-tx/sold/` |
| Property detail | `https://www.homes.com/property/12821-ranft-cove-del-valle-tx/m6kdhgkklwzy3/` |
| Page N (starts at that page) | `https://www.homes.com/austin-tx/p2/` |
| For rent | `https://www.homes.com/austin-tx/homes-for-rent/` |
| Open houses | `https://www.homes.com/austin-tx/open-houses/` |
| Maximum price | `https://www.homes.com/austin-tx/under-800k/` |
| Bedrooms | `https://www.homes.com/austin-tx/3-bedroom/` |
| Newest first | `https://www.homes.com/austin-tx/newest/` |
| Foreclosures | `https://www.homes.com/austin-tx/foreclosure/` |
| Property type | `https://www.homes.com/austin-tx/houses-for-sale/` |

A bare ZIP link such as `https://www.homes.com/78701/` is not served by Homes.com; it is skipped with a warning. Use `https://www.homes.com/austin-tx/78701/` or search mode with the ZIP.

### Output Example

> Sample shape: values are illustrative placeholders, not from a live record.

```json
{
  "listingKey": "q7w8e9r0t1y2u",
  "url": "https://www.homes.com/property/1200-example-st-austin-tx-78701/q7w8e9r0t1y2u/",
  "listingChannel": "for_sale",
  "isSold": false,
  "name": "1200 Example St, Austin, TX 78701",
  "price": 545000,
  "priceCurrency": "USD",
  "priceLabel": "List Price",
  "statusLabel": "$10K PRICE DROP",
  "propertyType": "SingleFamilyResidence",
  "bedrooms": 4,
  "bathrooms": 2.5,
  "livingAreaSqft": 2410,
  "yearBuilt": 2006,
  "streetAddress": "1200 Example St",
  "city": "Austin",
  "state": "TX",
  "postalCode": "78701",
  "country": "US",
  "latitude": 30.2672,
  "longitude": -97.7431,
  "availability": "InStock",
  "datePosted": "2026-06-15T00:00:00.000",
  "dateModified": "2026-09-01T00:00:00.000",
  "brokerageName": "Example Realty Group",
  "agentName": "A. Example",
  "agentTitle": "Listing Agent",
  "agentPhone": "(512) 555-0142",
  "agentEmail": "agent@example.com",
  "agentUrl": "https://www.homes.com/real-estate-agents/a-example/exampleid/",
  "primaryImageUrl": "https://images.homes.com/example/listing-photo.jpg",
  "description": "Updated single-family home near downtown Austin.",
  "fullDescription": "Updated single-family home near downtown Austin with an open floor plan, a fenced backyard and a two-car garage.",
  "priceHistory": [
    { "date": "07/02/2026", "event": "Price Changed", "price": 545000, "listToSalePercent": -1.8, "pricePerSqft": 226 }
  ],
  "priceHistorySource": "Example MLS",
  "taxHistory": [
    { "year": 2025, "taxPaid": 9120, "totalAssessment": 512000, "landAssessment": 140000, "improvementAssessment": 372000 }
  ],
  "taxHistorySource": "Public Records",
  "amenityGroups": [
    { "group": "Interior Features", "values": ["Hardwood floors", "Walk-in closet"] }
  ],
  "hoaFees": ["$45 Monthly HOA Fee"],
  "parking": ["2 Car Garage"],
  "lotDetails": ["0.18 Acre Lot"],
  "utilities": ["Public Sewer", "Public Water"],
  "schools": ["Example Elementary School", "Example High School"],
  "highlights": ["Recently updated kitchen"],
  "changeType": "NEW",
  "changedFields": [],
  "firstSeenAt": "2026-09-04T09:41:00Z",
  "lastSeenAt": "2026-09-04T09:41:00Z",
  "scrapedAt": "2026-09-04T09:41:00Z"
}
```

### Plan Requirement

Homes.com refuses direct connections, low-cost proxy pools and plain residential HTTP requests with a plain `Access Denied` page (HTTP 403). By default the run reads pages through a US connection with a real browser and, when the edge still refuses a page, falls back to Apify's premium unblocking proxy group, so the proxy access that group needs must be available on your Apify plan. Leave the Proxy configuration enabled. If your account cannot use it, the run stops with a message saying the proxy refused the connection ("a plan or proxy-access limit, not an empty result set"). If you pin a specific proxy group or supply your own proxy URLs, those take priority and the fallback is not used.

### FAQ

#### How much does it cost?

You pay for three things: each property row returned to the dataset (the platform's dataset-item event), the `detail-enrichment` event for each listing where detail enrichment actually ran (charged one listing at a time, right after that row is delivered, so an interrupted run bills only what it returned), and a one-time actor-start event. The **Pricing** tab shows the current rates. Use **Max items** to cap the cost of any run, and remember that a pasted property link in URL mode reads its detail page and bills the surcharge for every such row it returns, whether or not the toggle is on.

#### Is it legal to scrape Homes.com?

This actor collects only publicly available property data. You are responsible for how you use it: follow Homes.com's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Listing facts such as price and specs are generally treated as data, but photos, descriptions and agent details may carry their own rights.

#### Can I get only new or changed listings on a schedule?

Yes. Schedule the actor from the **Schedules** tab and turn on **Incremental mode**. Each run then returns only new and updated listings by default (plus reappeared ones once you also turn on **Emit expired**), and unchanged ones are not billed unless you turn on **Emit unchanged**.

#### Why was my Bedrooms or Maximum price value rejected instead of just ignored?

Homes.com publishes these filters as fixed URL pages, and silently answers an unrecognised page with the unfiltered result set. Rather than risk billing you for rows you did not ask for, this actor only accepts the values Homes.com actually publishes (bedrooms 1 to 5, and the five listed price ceilings) and refuses anything else with a clear message.

#### Why did my run fail instead of returning an empty dataset?

If every page this run tried came back refused, the run stops with a clear message so "no listings here" is never confused with "nothing could be read". A genuinely empty search (pages read fine, nothing matched) still succeeds and says so. Run it again in a few minutes if the connection was the problem.

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.

### 🔗 Want more Homes data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>🏠 <a href="https://apify.com/abotapi/homes-co-nz-scraper"><b>Homes.co.nz Scraper</b></a><br>Scrape Homes.co.nz listings by location or URL with pagination. Extract property details...</td><td>🏠 <a href="https://apify.com/abotapi/trulia-scraper"><b>Trulia Scraper</b></a><br>Scrape Trulia.com property listings by location, filters, or URL. Extract prices, beds...</td></tr>
<tr><td>🧩 <a href="https://apify.com/abotapi/zillow-scraper"><b>Zillow Scraper</b></a><br>Scrape zillow.com properties for sale, for rent and recently sold across the US: price...</td><td>🏠 <a href="https://apify.com/abotapi/realtor-ca-scraper"><b>Realtor.ca</b></a><br>Scrape Realtor.ca property listings with prices, descriptions, images, agent and...</td></tr>
<tr><td>🏠 <a href="https://apify.com/abotapi/realtor-com-scraper"><b>Realtor.com</b></a><br>Extract property listings from realtor.com. Get comprehensive data, including prices...</td><td>🏠 <a href="https://apify.com/abotapi/newhomesource-scraper"><b>NewHomeSource</b></a><br>Scrape new homes, floor plans, builders and communities from NewHomeSource.com. Search by...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/homes-com-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

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

search = pick a US location and a listing channel, and get every matching property. url = paste homes.com search or property links and get only those.

## `location` (type: `string`):

A US city and state ('Austin, TX'), or a 5-digit ZIP code ('78701'). Accepted in any casing and with or without the comma.

## `zipCode` (type: `string`):

Optional. Narrow the search to a single 5-digit US ZIP code inside the location above.

## `minBedrooms` (type: `integer`):

Optional. homes.com exposes bedroom filtering as one fixed page per count, so only 1 to 5 can be requested and the value matches listings with exactly that bedroom count. Leave empty for no bedroom filter.

## `maxPrice` (type: `integer`):

Optional. homes.com only publishes fixed maximum-price pages: 800000, 900000, 1000000, 1500000 or 2000000. Any other value is rejected with a clear message rather than silently ignored. Leave empty for no price ceiling.

## `sort` (type: `string`):

Result ordering. 'Newest' puts the most recently listed properties first, which is the useful setting when you cap the run with Max items.

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

Only used when mode = url. A property link always reads its full property page, so it always bills the detail surcharge. A search link is walked like search mode.

## `listingType` (type: `string`):

Which homes.com channel to read. 'Recently sold' returns closed sales with the sale date, days on market and price per square foot. 'Open houses' returns only listings with a scheduled open house.

## `fetchDetails` (type: `boolean`):

Reads each property page and adds the sections that do not exist on a result card: price and property history (with list-to-sale percentages and price per square foot), multi-year tax history with land and improvement assessments, grouped home details, HOA fees, parking, utilities, schools, highlights and the full listing description. Adds one page read per listing and bills a per-listing detail-enrichment surcharge only for listings actually returned.

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

The single cap on the run: stop after this many property rows across all searches and URLs. Defaults to 20 so a run stays small unless you raise it. 0 = unlimited, bounded only by the natural end of results (or Max pages, if set).

## `maxPages` (type: `integer`):

Optional hard cap on the number of result pages walked per search or pasted search link. Each page carries up to 40 listings. 0 (default) means no page limit: the walk stops naturally on a short page, an empty page, or a page that repeats listings already collected.

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

homes.com serves its listing pages only to US connections and refuses low-quality exits outright, so this run climbs to a residential US connection as soon as a cheaper one is refused. Apify Proxy works on every plan; leave it enabled for the most reliable results.

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

Paste a previous run ID or dataset ID to continue a large crawl without returning or charging for listings already collected there. For recurring monitoring of the same search, use Incremental mode below instead.

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

Turn this on for daily or recurring monitoring. The first run returns all matching listings as NEW. Later runs normally return only NEW, UPDATED and REAPPEARED listings. A listing is UPDATED when any of its returned data changed (price, status, description, photos, agent, details...), except the scrape time and days on market, which are never compared; 'What counts as a change' narrows this to price and availability only, and 'Ignore fields' excludes more fields. changedFields names what moved. Turn on 'Emit unchanged' or 'Emit expired' only when you also want those rows returned (and billed). State is kept separately for each location, channel, filter and detail setup; 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 location, channel, filters and detail settings, so different searches never mix state with each other.

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

Off by default. Turn on to also return listings 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 listings 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, never when Max items capped it, when Resume was used, or when the run scanned nothing. This returns, and bills, extra synthetic rows, so leave it off unless you need expiry tracking.

## `changeScope` (type: `string`):

All fields (default): any change to a listing's data makes it UPDATED, except fields that change on their own (the scrape time and days on market). Price and availability only: only a change to price, price label, currency, availability, status, sold flag or sold date, or to a price-history entry, makes a listing UPDATED; description, photos, agent and the other details never do.

## `compactUpdates` (type: `boolean`):

Off by default. Turn on to return UPDATED and EXPIRED listings as short rows: identifiers, current price and availability, priceChanges (old and new value) and variantChanges (price-history events added, removed or changed). NEW rows stay complete. A compact row carries no detail-page data, so the detail-enrichment surcharge is not charged for it.

## `expireAfterMissedScans` (type: `integer`):

With Emit expired on: a listing is marked EXPIRED only after it was missing from this many complete scans in a row (default 1). EXPIRED means the listing was no longer found in the search results; it does not prove the home was sold or delisted.

## `ignoreFieldsForChanges` (type: `array`):

Field names whose changes never make a listing UPDATED (they stay in the output). A dotted path such as priceHistory.pricePerSqft ignores that key inside every element of a list. Fields that change on their own (the scrape time and days on market) are always ignored.

## `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.

## `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",
  "location": "Austin, TX",
  "sort": "relevance",
  "urls": [
    "https://www.homes.com/austin-tx/",
    "https://www.homes.com/austin-tx/sold/",
    "https://www.homes.com/property/12821-ranft-cove-del-valle-tx/m6kdhgkklwzy3/",
    "https://www.homes.com/austin-tx/p2/",
    "https://www.homes.com/austin-tx/homes-for-rent/",
    "https://www.homes.com/austin-tx/open-houses/",
    "https://www.homes.com/austin-tx/under-800k/",
    "https://www.homes.com/austin-tx/3-bedroom/",
    "https://www.homes.com/austin-tx/newest/",
    "https://www.homes.com/austin-tx/foreclosure/",
    "https://www.homes.com/austin-tx/houses-for-sale/"
  ],
  "listingType": "for_sale",
  "fetchDetails": false,
  "maxItems": 20,
  "maxPages": 0,
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "changeScope": "all",
  "compactUpdates": false,
  "expireAfterMissedScans": 1,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `detail` (type: `string`):

No description

## `changes` (type: `string`):

No description

## `agents` (type: `string`):

No description

## `summary` (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 = {
    "mode": "search",
    "location": "Austin, TX",
    "sort": "relevance",
    "urls": [
        "https://www.homes.com/austin-tx/",
        "https://www.homes.com/austin-tx/sold/",
        "https://www.homes.com/property/12821-ranft-cove-del-valle-tx/m6kdhgkklwzy3/",
        "https://www.homes.com/austin-tx/p2/",
        "https://www.homes.com/austin-tx/homes-for-rent/",
        "https://www.homes.com/austin-tx/open-houses/",
        "https://www.homes.com/austin-tx/under-800k/",
        "https://www.homes.com/austin-tx/3-bedroom/",
        "https://www.homes.com/austin-tx/newest/",
        "https://www.homes.com/austin-tx/foreclosure/",
        "https://www.homes.com/austin-tx/houses-for-sale/"
    ],
    "listingType": "for_sale",
    "fetchDetails": false,
    "maxItems": 20,
    "maxPages": 0,
    "proxy": {
        "useApifyProxy": true
    },
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "compactUpdates": false,
    "ignoreFieldsForChanges": [],
    "maxNotifyListings": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/homes-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",
    "location": "Austin, TX",
    "sort": "relevance",
    "urls": [
        "https://www.homes.com/austin-tx/",
        "https://www.homes.com/austin-tx/sold/",
        "https://www.homes.com/property/12821-ranft-cove-del-valle-tx/m6kdhgkklwzy3/",
        "https://www.homes.com/austin-tx/p2/",
        "https://www.homes.com/austin-tx/homes-for-rent/",
        "https://www.homes.com/austin-tx/open-houses/",
        "https://www.homes.com/austin-tx/under-800k/",
        "https://www.homes.com/austin-tx/3-bedroom/",
        "https://www.homes.com/austin-tx/newest/",
        "https://www.homes.com/austin-tx/foreclosure/",
        "https://www.homes.com/austin-tx/houses-for-sale/",
    ],
    "listingType": "for_sale",
    "fetchDetails": False,
    "maxItems": 20,
    "maxPages": 0,
    "proxy": { "useApifyProxy": True },
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "compactUpdates": False,
    "ignoreFieldsForChanges": [],
    "maxNotifyListings": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/homes-com-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 '{
  "mode": "search",
  "location": "Austin, TX",
  "sort": "relevance",
  "urls": [
    "https://www.homes.com/austin-tx/",
    "https://www.homes.com/austin-tx/sold/",
    "https://www.homes.com/property/12821-ranft-cove-del-valle-tx/m6kdhgkklwzy3/",
    "https://www.homes.com/austin-tx/p2/",
    "https://www.homes.com/austin-tx/homes-for-rent/",
    "https://www.homes.com/austin-tx/open-houses/",
    "https://www.homes.com/austin-tx/under-800k/",
    "https://www.homes.com/austin-tx/3-bedroom/",
    "https://www.homes.com/austin-tx/newest/",
    "https://www.homes.com/austin-tx/foreclosure/",
    "https://www.homes.com/austin-tx/houses-for-sale/"
  ],
  "listingType": "for_sale",
  "fetchDetails": false,
  "maxItems": 20,
  "maxPages": 0,
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "compactUpdates": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}' |
apify call abotapi/homes-com-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/homes-com-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/6MN68NG7MaIB2qUwi/builds/traaAvCgNTkCJ0GXO/openapi.json
