# 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)
- **Stats:** 1 total users, 0 monthly users, 69.2% runs succeeded, 0 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?

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

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

Scrape US property records from **Homes.com**, the #4 most-visited US real-estate
portal. Pick a city, state or ZIP and a listing channel, or paste links, and get
one flat row per property.

Every result page carries a full set of forty listings, so a typical search
returns complete rows without opening a single property page. Turn on detail
enrichment when you want the deep, public-records-backed sections that only
exist on a property's own page.

### What you get

**On every row, from the search results alone**

| Field | Notes |
|---|---|
| `listingKey`, `url` | Stable per-property id and canonical link |
| `price`, `priceCurrency`, `priceLabel` | `priceLabel` names what the figure is, for example `Last List Price` on a sold record |
| `statusLabel` | The badge on the card, for example `$6K PRICE DROP` or `Sold Mar 11, 2026` |
| `bedrooms`, `bathrooms`, `livingAreaSqft`, `yearBuilt` | Half baths included |
| `streetAddress`, `city`, `state`, `postalCode`, `country` | Split, not one blob |
| `propertyType` | For example `SingleFamilyResidence` |
| `listingChannel`, `isSold` | Which channel the row came from |
| `soldDate`, `daysOnMarket`, `pricePerSqft` | Populated on the recently-sold channel |
| `agentName`, `agentTitle`, `agentPhone`, `agentEmail`, `agentUrl` | Listing agent, as published on the listing |
| `brokerageName` | Listing brokerage |
| `primaryImageUrl`, `description` | Lead photo and summary |
| `latitude`, `longitude` | Coordinates (populated with detail enrichment) |

**With detail enrichment on, additionally**

| Field | Notes |
|---|---|
| `priceHistory` | One entry per recorded event: `date`, `event`, `price`, `listToSalePercent`, `pricePerSqft` |
| `priceHistorySource` | The originating MLS, named |
| `taxHistory` | Per year: `taxPaid`, `totalAssessment`, `landAssessment`, `improvementAssessment` |
| `taxHistorySource` | Usually public records |
| `amenityGroups` | The full home-details block, grouped as `{group, values}` |
| `hoaFees`, `parking`, `lotDetails`, `utilities` | Pulled out of the groups for direct use |
| `schools`, `highlights` | Assigned schools and the listing's highlight tags |
| `fullDescription` | The complete listing narrative |

**With incremental mode on, additionally**

`changeType` (`NEW` / `UPDATED` / `UNCHANGED` / `REAPPEARED` / `EXPIRED`),
`changedFields`, `firstSeenAt`, `lastSeenAt`.

### Two ways to run it

**Search by location.** Give a location (`Austin, TX` or `78701`) and pick a
channel: for sale, for rent, recently sold, open houses, foreclosures, or a
specific property type within those. Optionally narrow by ZIP, bedrooms or a
maximum price, and sort by newest.

**Paste links.** Give any mix of Homes.com search links and property links. A
search link is walked page by page (a link that already points at page 2 or
later starts the walk there); a property link returns one fully detailed row.

#### 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. For anything finer,
filter the dataset after the run.

Filters build the search URL, so they apply to **search mode only**. A link you
paste in URL mode is fetched exactly as you gave it.

### Input parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `mode` | select | `search` | `search` picks a location and a 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 | empty | Search mode. Optionally narrow the search to one 5-digit ZIP inside the location. |
| `minBedrooms` | integer | empty | Search mode. Homes.com publishes fixed per-count bedroom pages, so only 1 to 5 can be requested and a value matches that count. |
| `maxPrice` | select | empty | 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, which pairs well with Max items. |
| `urls` | list | empty | URL mode. Homes.com search links (walked page by page) and/or property links (one detailed row each). |
| `listingType` | select | `for_sale` | The listing channel: for sale / for rent (including per-property-type channels), recently sold, open houses, foreclosures. Labels the rows a pasted link returns too. |
| `fetchDetails` | checkbox | `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 | empty | Continue one interrupted crawl: paste a previous run ID or dataset ID, and listings it already returned are skipped. |
| `incrementalMode` | checkbox | `false` | For scheduled monitoring: the first run returns everything as NEW, later runs only what changed. |
| `stateKey` | string | empty | Optional name for an incremental campaign. Different keys keep separate baselines; the same key shares one. |
| `emitUnchanged` | checkbox | `false` | Also return listings that have not changed, marked UNCHANGED. Returns, and bills, extra rows. |
| `emitExpired` | checkbox | `false` | Also return listings no longer found after a complete scan, marked EXPIRED. Returns, and bills, extra synthetic rows. |
| `proxy` | 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. |
| `mcpConnectors` | list | empty | Pipe a condensed summary per item into Notion, Linear, Airtable or the Apify MCP server. Authorize once under Apify > Settings > Integrations. |
| `notionParentPageUrl` | string | empty | 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. |

### Recurring monitoring

Turn on **Incremental changes for scheduled runs** and schedule the actor. The
first run returns everything as `NEW`; later runs return only what actually
changed, so a daily watch on a market costs a handful of rows instead of the
whole market. Unchanged and expired rows are off by default and are billed when
you turn them on.

**Resume from a previous run** is a different tool: paste a run or dataset id to
continue one interrupted crawl without re-collecting, or re-paying for, what it
already returned.

### Limits and cost control

`Max items` is the single cap and defaults to 20, so an exploratory run stays
cheap. Set it to 0 for everything the search has, bounded by the natural end of
results. `Max pages` is an optional extra ceiling per search.

Detail enrichment opens one page per property and adds a per-property
surcharge, charged only for properties actually returned. A property whose
detail page could not be read is never charged for enrichment, and a run where
nothing could be read fails loudly rather than reporting an empty success.

### 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. Authorize
a connector once under **Apify > Settings > Integrations**, then pick it in the
input. Each connector receives a condensed summary per item; the complete
record always stays in the dataset. For Notion, also give the parent page the
item pages should be created under.

### Output sample

```json
{
  "listingKey": "a1b2c3d4e5f6g",
  "url": "https://www.homes.com/property/1200-example-st-austin-tx/a1b2c3d4e5f6g/",
  "listingChannel": "for_sale",
  "isSold": false,
  "name": "1200 Example St, Austin, TX 78701",
  "price": 545000,
  "priceCurrency": "USD",
  "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,
  "brokerageName": "Example Realty Group",
  "agentName": "A. Example",
  "agentTitle": "Listing Agent",
  "agentPhone": "555-0100 ext. 0",
  "agentEmail": "agent@example.com",
  "priceHistory": [
    { "date": "08/21/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",
  "hoaFees": ["$45 Monthly HOA Fees"],
  "schools": ["Example Elementary School", "Example High School"],
  "changeType": "NEW",
  "scrapedAt": "2026-09-04T09:41:00Z"
}
```

The sample above uses placeholder contact details. Real runs return the agent
and brokerage details as published on the listing.

### Plan requirement

Runs on any Apify plan. Homes.com serves its listing pages to US connections
only; leave the Proxy configuration enabled for the most reliable results.

### Notes

- Results cover the United States.
- `price` on a recently-sold record is labelled by `priceLabel`. Read the label
  before treating the figure as a closing price.
- Property and tax history come from public records and the originating MLS,
  each attributed in its own `*Source` field.

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

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

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

## `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/sold/",
    "https://www.homes.com/property/12821-ranft-cove-del-valle-tx/m6kdhgkklwzy3/"
  ],
  "listingType": "for_sale",
  "fetchDetails": false,
  "maxItems": 20,
  "maxPages": 0,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "proxy": {
    "useApifyProxy": true
  },
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (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/sold/",
        "https://www.homes.com/property/12821-ranft-cove-del-valle-tx/m6kdhgkklwzy3/"
    ],
    "listingType": "for_sale",
    "fetchDetails": false,
    "maxItems": 20,
    "maxPages": 0,
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "proxy": {
        "useApifyProxy": true
    },
    "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/sold/",
        "https://www.homes.com/property/12821-ranft-cove-del-valle-tx/m6kdhgkklwzy3/",
    ],
    "listingType": "for_sale",
    "fetchDetails": False,
    "maxItems": 20,
    "maxPages": 0,
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "proxy": { "useApifyProxy": True },
    "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/sold/",
    "https://www.homes.com/property/12821-ranft-cove-del-valle-tx/m6kdhgkklwzy3/"
  ],
  "listingType": "for_sale",
  "fetchDetails": false,
  "maxItems": 20,
  "maxPages": 0,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "proxy": {
    "useApifyProxy": true
  },
  "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/aHPyixhgiGz3bhfBo/openapi.json
