# Apartments.com Scraper - Rentals, Prices & Property Details (`abotapi/apartments-com-scraper`) Actor

From $1.50/1K. Scrape Apartments.com rentals by city, ZIP, neighborhood, address or URL. Extract rent, floorplans, available units, amenities, fees, schools, transit, ratings, reviews, photos, phone numbers and GPS coordinates.

- **URL**: https://apify.com/abotapi/apartments-com-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** Real estate, Automation, Developer tools
- **Features:** Limited permissions
- **Stats:** 5 total users, 1 monthly users, 77.8% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

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

## Apartments.com Scraper: Rentals, Floorplans & Contacts

Apartments.com Scraper turns Apartments.com into structured rental data. Search any US city, ZIP, neighborhood or street address, paste result and property links directly, or resolve a search term to a location with autocomplete. Every property comes back as one rich row with rent range, floorplans, available units, amenities, pet and parking policy, fees, nearby schools, walk and transit scores, office hours, ratings, reviews, photos, phone and GPS coordinates. Export to JSON, CSV or Excel, or read results straight through the Apify API.

### Why This Scraper?

- **One row per property, not one row per sub-block.** Amenities, fees, schools, units and reviews all live inside the same record instead of being split into separate rows you'd otherwise pay for one by one.
- **50+ fields per property**, including floorplan models, every available unit with rent and square footage, the full amenity list, pet and parking policy, fee schedule, nearby schools, walk and transit scores, office hours, ratings and recent reviews.
- **Three modes**: location search with filters, direct URL scraping (result pages and property pages, mixed in one run), and location autocomplete for resolving a term to coordinates.
- **Graceful pagination** that walks a whole result set on its own, bounded only by a single soft cap (`maxListings`), with duplicate properties removed automatically.
- **Daily/recurring change monitoring.** Turn on Incremental mode to track a search over time and receive only the properties that are new, updated or reappeared on each scheduled run.
- **Resume support** for one specific interrupted or previous large crawl via `resumeFromRunId`, plus an internal checkpoint that survives platform migrations without re-billing already-saved properties.
- **A refused run fails loudly** with a clear status message instead of quietly returning an empty dataset that looks like a genuine "no results" search.

### Use Cases

- **Rental market research and investors:** compare rent ranges, unit mixes and amenities across a metro to spot pricing trends over time.
- **Listing aggregator and search apps:** pull structured floorplans, units and availability to power your own rental search tool.
- **Property management competitive intelligence:** track competitors' rent specials, fees and policies with Incremental mode so you only see what changed.
- **Relocation and leasing services:** collect management company names, phone numbers and office hours tied to specific neighborhoods.
- **Neighborhood and livability research:** pair school, walk score and transit data with rent to study a market beyond price alone.

### Data You Get

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

| Field | Example |
| --- | --- |
| `id` | `abc1234` |
| `name` | `Sample Apartments` |
| `url` | `https://www.apartments.com/sample-apartments-austin-tx/abc1234/` |
| `description` | `A modern community near downtown with resort-style amenities.` |
| `address` / `city` / `state` / `zip` | `100 Example St, Austin, TX 78701` / `Austin` / `TX` / `78701` |
| `latitude` / `longitude` | `30.0000` / `-97.0000` |
| `rentMin` / `rentMax` / `currency` | `1200` / `2600` / `USD` |
| `bedsRange` / `bathsRange` / `sqftRange` | `1 - 2 bd` / `1 - 2 ba` / `500 - 1,000` |
| `propertyType` | `rental` (or the plural filter value, e.g. `apartments`, when one is set) |
| `managementCompany` / `rentSpecial` / `verified` | `Sample Management Co` / `Move-in special: one month free` / `true` |
| `phone` / `petsAllowed` | `(800) 000-0000` / `true` |
| `rating` / `reviewCount` | `4.1` / `12` |
| `amenities` | `["Pool", "Fitness Center", "In Unit Laundry"]` |
| `floorplans` | `[{ "name": "A1", "rent": "$1,200 - $1,400", "beds": 1, "baths": 1, "sqft": 650 }]` |
| `units` | `[{ "model": "A1", "unit": "000", "beds": 1, "baths": 1, "rent": 1300, "sqft": 650, "availability": "Now" }]` |
| `walkScore` | `{ "walk": 80, "transit": 50, "drive": 90, "bike": 70 }` |
| `photos` | `["https://images1.apartments.com/i2/0000/sample-photo.jpg"]` |
| `changeType` | `NEW` (incremental mode only, see How to Use) |

Further fields on detail-scraped records: `yearBuilt`, `unitsInBuilding`, `stories`, `neighborhood`, `neighborhoodDescription`, `fees`, `transit`, `officeHours`, `breadcrumbs`, `managementLogo` (management-company logo image URL), `virtualTours` (only with `includeVirtualTours`; many properties have none, so it is often an empty list) and `detailScraped`. Additive alias fields sit next to the originals: `propertyName`, `isVerified`, `location`, `coordinates`, `rent`, `beds`, `baths`, `sqft`, `contact`, `models`, `transitAndPOI` and `scores`.

Search-result cards (search mode, and any `url`-mode entry that resolves to a result page rather than a single property) also carry `street`, `bedsLabel` (e.g. `"Studio - 2 Beds"`), `amenitiesSummary` (a short card-text summary) and a single `photo` thumbnail URL; these sit alongside the detail fields above once `fetchDetails` enriches the record, but a directly pasted single-property URL is scraped straight from its detail page and never carries them. Every detail-scraped record also keeps `rawJsonLd`, the underlying source object the detail fields were parsed from. Autocomplete-mode rows have a completely different shape: `query`, `id`, `display`, `geographyType`, `city`, `state`, `county`, `latitude`, `longitude`, `boundingBox` and `scrapedAt`, with no property fields at all. In Incremental mode, every classified row adds `changeType`, `changedFields` (populated only for `UPDATED`), `firstSeenAt` and `lastSeenAt`.

### How to Use

1. Pick a **mode**: `search` (location plus filters), `url` (paste result or property links), or `autocomplete` (location suggestions only, no listings).
2. For search mode, fill in locations and any filters; for URL mode, paste your links. Switch **Fetch full property details** on or off depending on whether you need the enriched fields.
3. Set **Max properties** (and optionally **Max result pages**) to control run size and cost, then click **Start**.
4. Download the dataset as JSON, CSV or Excel, or read it through the API.

**Search a city, fast cards only:**

```json
{
  "mode": "search",
  "locations": ["Austin, TX"],
  "fetchDetails": false,
  "maxPages": 3,
  "maxListings": 100
}
```

**Search with filters and full details:**

```json
{
  "mode": "search",
  "locations": ["Denver, CO", "80202"],
  "propertyType": "apartments",
  "bedrooms": "2",
  "minRent": 1500,
  "maxRent": 2800,
  "fetchDetails": true,
  "maxPages": 5
}
```

**Scrape URLs (a result page and a property page, mixed):**

```json
{
  "mode": "url",
  "urls": [
    "https://www.apartments.com/houston-tx/2-bedrooms/",
    "https://www.apartments.com/sample-apartments-austin-tx/abc1234/"
  ],
  "fetchDetails": true,
  "maxPages": 2
}
```

**Location autocomplete:**

```json
{
  "mode": "autocomplete",
  "locations": ["Brooklyn", "78701"]
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/apartments-com-scraper").call(run_input={"mode": "search", "locations": ["Austin, TX"], "maxListings": 50})
for prop in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(prop["name"], prop.get("rentMin"), prop.get("rentMax"))
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/apartments-com-scraper').call({ mode: 'search', locations: ['Austin, TX'], maxListings: 50 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

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

#### How maxPages and maxListings shape a run

By default `maxPages` is `0` (unlimited): a search or URL entry walks every result page (about 40 properties per page) until Apartments.com naturally runs out, an empty page or one that repeats properties already collected, or until `maxListings` (the run's only soft cap, default `20`, set `0` for unlimited) is reached, whichever comes first. Set `maxPages` to a specific number only when you want an explicit page cap below that natural stop. For a very large walk-all pull spread across multiple runs, use `resumeFromRunId` in a follow-up run instead of raising the cap on one run: paste the ID of a previous run (or its dataset) and the new run loads the properties already saved there, skips them, and appends only new properties to a fresh dataset.

#### Resume and recurring updates

**Resume** (`resumeFromRunId`) continues one specific interrupted or previous large crawl: paste a run ID or dataset ID and this run skips properties already collected there, appending only the remaining new properties. An internal same-run checkpoint also protects against platform migrations without any input needed, so an interrupted run picks back up without re-saving (and re-billing) properties it already wrote.

**Incremental mode** (`incrementalMode`) is for a recurring schedule (for example, daily) against the same search: the actor remembers state itself, keyed on your search, filter and `fetchDetails` settings (or an explicit `stateKey`), so you never paste a run ID. The first run returns every property as `NEW`. Later runs classify each property as `NEW`, `UPDATED` (with `changedFields` listing exactly what changed), or `UNCHANGED` (suppressed and not returned unless `emitUnchanged` is on; a suppressed property is not billed at all: no result charge and no detail-enrichment charge, even though its detail page is fetched to compare it). Turning on `emitExpired` also marks properties that were present in a previously tracked run but are no longer found as `EXPIRED`, but only once a run has scanned the tracked search completely: not capped by `maxListings` or `maxPages`, not started from a pasted results page beyond page 1, not started from a link whose query filter such as `?bb=` was removed, not narrowed by a client-side bedroom or rent filter, and not a `resumeFromRunId` run. A partial scan cannot tell "gone" apart from "not reached yet", so it skips expiry detection instead and keeps the previous state as-is. `REAPPEARED` can only ever be produced for a property that an earlier complete, `emitExpired` run has already marked `EXPIRED`; if `emitExpired` has never been turned on for a given state key, nothing in that state can come back as `REAPPEARED`, because nothing was ever marked absent in the first place.

Change detection compares every extracted field (rent, availability, floorplans, units, fees, amenities, photos, reviews and ratings). `scrapedAt`, `rawJsonLd`, `breadcrumbs`, `neighborhoodDescription`, `virtualTours` and `managementLogo` are never compared, and neither are the derived copy fields `propertyName`, `location`, `coordinates`, `isVerified`, `rent`, `beds`, `baths`, `sqft`, `transitAndPOI`, `scores`, `models` and `contact`. Use `ignoreFieldsForChanges` to exclude more: a plain name such as `photos` or `availability` matches that field anywhere, including inside `floorplans` and `units`, and a dotted path such as `units.availability` matches only that one. Ignored fields stay in the output and are only left out of the comparison and of `changedFields`; changing the list never causes a one-time wave of `UPDATED` rows.

Combining `resumeFromRunId` with Incremental mode on a state key that has no tracked properties yet seeds that baseline from the resume dataset too, so those properties don't look `NEW` again on the next scheduled run. Combining it with a state key that already has tracked properties instead fails the run immediately, since it isn't clear which baseline should win; remove `resumeFromRunId` or set a different `stateKey` to bootstrap a separate campaign.

#### URL shapes (url mode)

| Shape | Example |
|---|---|
| City search | `https://www.apartments.com/austin-tx/` |
| Paginated search (starts at that page) | `https://www.apartments.com/austin-tx/2/` |
| Bedroom filter | `https://www.apartments.com/austin-tx/2-bedrooms/` |
| Bedroom and price filter | `https://www.apartments.com/austin-tx/min-1-bedrooms-under-2000/` |
| Price filter | `https://www.apartments.com/austin-tx/1-bedrooms-under-1500/` |
| Property type (houses, condos, townhomes) | `https://www.apartments.com/houses/austin-tx/` |
| Amenity / pet collection | `https://www.apartments.com/austin-tx/pet-friendly-dog/` |
| City and ZIP | `https://www.apartments.com/austin-tx-78701/` |
| Property page | `https://www.apartments.com/the-grove-houston-tx/9cwjc35/` |
| Share link (the `?sk=` part is ignored) | `https://www.apartments.com/austin-tx/?sk=x` |

A bare ZIP link such as `/78701/` and the `m.apartments.com` mobile host are not supported: such a URL is skipped with a warning in the log, and the run fails if no URL is usable.

Behaviour change: the `urls` field now has a default holding one example of each shape above. A run in `url` mode with the field left untouched scrapes those examples (capped by Max properties) instead of stopping with an empty-input error.

#### Reviews and output views

Reviews come from the property detail page (`reviews`: author, rating, title, body, date) and are included whenever the property is detail-scraped, up to **Max reviews per property** (default: every review on the page, up to 1000; `0` skips them). Reviews are not a separate event: every full or partial block of 10 reviews delivered on a returned property adds one `detail-enrichment` unit to that property's detail charge, and a property with no reviews, a suppressed property, an unchanged property returned with Emit unchanged, or a run with details off adds none. The detail unit and the review blocks are charged right after each property is saved, not at the end of the run. The dataset has these views in the Console: Properties Overview, Contacts & Office, Floorplans/Units/Fees/Schools, Reviews (one row per review) and Changes (incremental mode).

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

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `search` | How to start: `search` builds result pages from location terms, `url` scrapes pasted URLs, `autocomplete` returns location suggestions only, no listings. |
| `locations` | array | no default (prefill: `["Austin, TX"]`) | City, ZIP, neighborhood, or street address terms, one per line. Used by `search` and `autocomplete`; ignored in `url` mode. |
| `propertyType` | string | `any` | Limit to `apartments`, `houses`, `condos`, `townhomes`, or `any` for all types. |
| `bedrooms` | string | `any` | Minimum bedroom count: `any`, `studio`, `1`, `2`, `3`, `4`. |
| `minRent` | integer | (none) | Lowest monthly rent in USD. Leave empty for no minimum. |
| `maxRent` | integer | (none) | Highest monthly rent in USD. Leave empty for no maximum. |
| `lifestyle` | string | `none` | Optional single lifestyle filter (for example `pet-friendly`, `luxury`, `furnished`); applied only when `propertyType` is `any`. |
| `urls` | array | one example of each supported URL shape (see URL shapes) | Apartments.com result or property URLs, one per line. Used by `url` mode; search-mode filters are ignored. |
| `fetchDetails` | boolean | `true` | Enrich each search-result card with its detail page: floorplan units, full amenities, pet and parking policy, fees, schools, transit, walk score, office hours, ratings and reviews. A property URL you paste directly is always fetched and returned in full regardless of this toggle; it only affects search-result cards and the enrichment billing event, see the FAQ. |
| `includeVirtualTours` | boolean | `false` | Also list the property's virtual tour, 3D tour and video tour links in `virtualTours` (detail-scraped records only; no extra requests; only some properties publish a tour, the list is empty for the rest). |
| `sortBy` | string | `default` | Order of search results: `default` (best match), `rent_asc`, `rent_desc` or `recently_updated` (the site's own sort, also on later pages). A pasted link's own query string (for example `?so=` or `?bb=`) is removed with a warning in the run log, so this setting always decides the order; a removed filter such as `?bb=` also turns off expired-property detection for that link. With **Max properties**, the sort decides which properties are kept. |
| `maxPages` | integer | `0` | Safety bound on result pages walked per location or start URL (about 40 properties per page). `0` means unlimited, the walk stops on its own once a page comes back empty or repeats listings already collected. |
| `maxReviews` | integer | `1000` | Reviews kept on each property row (the default keeps every review shown; `0` skips them, at most 1000). Each block of 10 delivered reviews adds one `detail-enrichment` unit, see Reviews and output views. |
| `maxListings` | integer | `20` | The sole soft cap on the run, applied across all locations/URLs. `0` means unlimited. |
| `resumeFromRunId` | string | (none) | Continue one specific interrupted or previous large crawl: paste a run ID or dataset ID and this run skips properties already collected there. |
| `incrementalMode` | boolean | `false` | Daily/recurring monitoring of this same search. First run returns everything as `NEW`; later runs return only `NEW`/`UPDATED` by default. See "Resume and recurring updates" above. |
| `stateKey` | string | (none) | Optional name for a monitoring campaign, so its incremental state stays stable or is deliberately shared. Auto-derived from your search/filter settings when left empty. |
| `ignoreFieldsForChanges` | array | `[]` | Incremental mode only. Fields excluded from change detection (plain name = anywhere, dotted path = exact). They stay in the output. |
| `emitUnchanged` | boolean | `false` | Incremental mode only. Also return properties unchanged since the last run, marked `UNCHANGED`. Adds and bills extra rows you already have. |
| `emitExpired` | boolean | `false` | Incremental mode only. Also return properties from a previous run no longer found, marked `EXPIRED`, once a run has fully scanned the tracked search (not capped, not a resume). Adds and bills extra synthetic rows. |
| `proxy` | object | Apify Residential | Connection settings. |
| `mcpConnectors` | array | (none) | Optionally send a summary of each record to apps you authorized under Integrations. Leave empty to skip; never changes the dataset output. |
| `notionParentPageUrl` | string | (none) | Notion connector only: URL or ID of the Notion page under which item pages are created. Required for Notion export; ignored by other connectors. |
| `maxNotifyListings` | integer | `50` | Cap on the number of items written to each connector per run. Does not affect the dataset. |

Alternative input names are also accepted: `startUrls` for `urls`, `search` for `locations` (one term), `maxItems` for `maxListings` and `endPage` for `maxPages`. When both are given, the alternative name wins. Output field aliases (`propertyName`, `models`, `scores`, `transitAndPOI`, `isVerified`) sit next to the original fields and are not part of change detection in incremental mode.

### Output Example

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

```json
{
  "id": "abc1234",
  "url": "https://www.apartments.com/sample-apartments-austin-tx/abc1234/",
  "name": "Sample Apartments",
  "description": "A modern community near downtown with resort-style amenities and easy highway access.",
  "address": "100 Example St",
  "street": "100 Example St",
  "city": "Austin",
  "state": "TX",
  "zip": "78701",
  "latitude": 30.0,
  "longitude": -97.0,
  "rentMin": 1200,
  "rentMax": 2600,
  "currency": "USD",
  "bedsLabel": "Studio - 2 Beds",
  "bedsRange": "1 - 2 bd",
  "bathsRange": "1 - 2 ba",
  "sqftRange": "500 - 1,000",
  "propertyType": "rental",
  "yearBuilt": 2010,
  "unitsInBuilding": 200,
  "stories": 4,
  "neighborhood": "Sample Neighborhood",
  "leaseOptions": "6 - 15 Month Leases",
  "managementCompany": "Sample Management Co",
  "rentSpecial": "Move-in special: one month free",
  "verified": true,
  "phone": "(800) 000-0000",
  "petsAllowed": true,
  "rating": 4.1,
  "reviewCount": 12,
  "amenities": ["Pool", "Fitness Center", "In Unit Laundry"],
  "floorplans": [{ "name": "A1", "rent": "$1,200 - $1,400", "beds": 1, "baths": 1, "sqft": 650 }],
  "units": [{ "model": "A1", "unit": "000", "beds": 1, "baths": 1, "rent": 1300, "sqft": 650, "availability": "Now" }],
  "fees": ["Application Fee $0", "Admin Fee $0"],
  "parking": ["Garage - $0/mo"],
  "transit": [{ "name": "Sample Station", "distanceMiles": 1.0, "driveTimeMin": 5 }],
  "schools": [{ "name": "Example Elementary", "type": "Public", "grades": "PK-5", "students": 500 }],
  "walkScore": { "walk": 80, "transit": 50, "drive": 90, "bike": 70 },
  "officeHours": ["Monday, Tuesday, Wednesday, Thursday, Friday: 09:00-18:00"],
  "reviews": [{ "author": "A. Renter", "rating": 5, "title": "Great place", "body": "Sample review text.", "date": "2026-01-01" }],
  "photo": "https://images1.apartments.com/i2/0000/sample-thumb.jpg",
  "photos": ["https://images1.apartments.com/i2/0000/sample-photo.jpg"],
  "detailScraped": true,
  "scrapedAt": "2026-01-01T00:00:00.000Z"
}
```

### Plan Requirement

Apartments.com refuses plain HTTP requests from both datacenter and residential connections (an "Access Denied" page), so this actor loads every page in a real browser. Residential is the default (`proxy`: Apify Residential, United States or Canada) and the recommended choice; datacenter connections are often refused, so they are less reliable. Residential access requires an Apify plan that includes it; on a plan without it the actor logs "Apify free plans do not include residential proxies" and continues on a backup connection, where some or all pages may still be unavailable. The actor retries on a fresh connection when one is refused and recovers from a single refused page. Apartments.com shows the same United States listings regardless of the connecting region.

If nothing at all could be read, the run fails with one of three messages instead of returning an empty dataset: the proxy connection was refused for this account (a paid plan is required, or check your own proxy URLs); no results because residential proxies are unavailable and the backup connection could not load the pages; or the requests failed and nothing was collected (a connection problem, not an empty search, try again later). A run where the site answered but the layout could not be read also fails, with nothing saved or charged.

### FAQ

#### How much does it cost?

Billing has three events, listed with current rates on the **Pricing** tab of this actor's Store page: one charge per property returned, one detail-enrichment charge per detail-enriched property that is returned (only when `fetchDetails` is on, also for a pasted single property URL), plus one more `detail-enrichment` unit for every block of 10 reviews delivered on a returned property (see Max reviews per property), and a small start charge per run. Suppressed unchanged properties and failed properties are not charged at all (neither as results nor for detail enrichment); an unchanged property is billed only when you turn on `emitUnchanged` and it is returned. Use **Max properties** to cap the cost of any run.

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

This actor collects only publicly available rental listing data. You are responsible for how you use it: follow Apartments.com's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Contact details, photos and reviews on individual listings may be subject to third-party rights.

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

Yes. Schedule the actor from the **Schedules** tab and turn on **Incremental mode**. Each run then returns only new and updated properties for the same search; unchanged properties are not returned and not billed at all (no result charge and no detail-enrichment charge) unless you turn `emitUnchanged` on, in which case a returned unchanged property is billed as a returned property (one result charge and one detail-enrichment unit, no review blocks).

#### Why doesn't turning off fetchDetails change anything for a pasted property URL?

`fetchDetails` only controls whether search-result cards get a detail-page visit. A URL you paste that already points straight at one property page is always fetched and returned with the full detail fields (floorplans, units, fees, schools, and more); turning `fetchDetails` off in that case only skips the detail-enrichment billing event for that record, it does not skip the fetch or shrink the returned data.

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

If every request this run made was refused and nothing at all could be read, the run fails with a clear status message instead of quietly returning zero properties, so a genuine block is never confused with an empty search. A run where the site answered but had no matching properties still succeeds, with one row explaining that. Missing required input for the selected mode (for example, search mode with no locations) also fails immediately, before any proxy or browser session is paid for.

#### 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 real estate data?

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

<table>
<tr><td>🏠 <a href="https://apify.com/abotapi/rent-com-scraper"><b>Rent.com Scraper</b></a><br>Extract rental listings from rent.com. Get comprehensive data including monthly rent...</td><td>🏠 <a href="https://apify.com/abotapi/591-com-tw-scraper"><b>591.com.tw Scraper</b></a><br>Extract property listings from 591房屋交易網 591.com.tw, Taiwan’s largest real estate...</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/zumper-scraper"><b>Zumper Scraper</b></a><br>Scrape Zumper.com rental listings: apartments, houses, condos and rooms. Pulls 60+ fields...</td></tr>
<tr><td>🏠 <a href="https://apify.com/abotapi/onthemarket-scraper"><b>OnTheMarket Scraper</b></a><br>Scrape onthemarket.com for-sale, to-rent and new-homes listings with 85+ fields: price...</td><td>🏠 <a href="https://apify.com/abotapi/quintoandar-scraper"><b>QuintoAndar Scraper</b></a><br>Scrape QuintoAndar property listings across Brazil with 80+ fields, including rent, sale...</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/apartments-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`):

How to start. 'search' builds result pages from city / ZIP / address terms. 'url' scrapes Apartments.com result or property URLs you paste. 'autocomplete' returns location suggestions (region IDs, coordinates, bounding boxes) for each term, with no listing scrape.

## `locations` (type: `array`):

City, ZIP, neighborhood, or street address terms, one per line (e.g. 'Austin, TX', '78701', 'Brooklyn, NY'). Each is resolved through Apartments.com's location service. Used by 'search' and 'autocomplete' modes; ignored in 'url' mode.

## `propertyType` (type: `string`):

Limit to a rental type. 'Any' returns all types.

## `bedrooms` (type: `string`):

Minimum bedroom count. 'Any' applies no bedroom filter.

## `minRent` (type: `integer`):

Lowest monthly rent. Leave empty for no minimum.

## `maxRent` (type: `integer`):

Highest monthly rent. Leave empty for no maximum.

## `lifestyle` (type: `string`):

Optional single lifestyle filter. Applied only when property type is 'Any' (Apartments.com does not combine a type prefix with these). 'None' applies no lifestyle filter.

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

Apartments.com result URLs (e.g. https://www.apartments.com/austin-tx/2-bedrooms/) or property URLs (e.g. https://www.apartments.com/the-grove-houston-tx/9cwjc35/), one per line. Supported: city, paginated (/2/), bedroom/price-filtered, property-type (houses/condos/townhomes), amenity (pet-friendly-dog), city-ZIP (austin-tx-78701), share links (?sk=...) and property pages. Bare /78701/ and m.apartments.com links are skipped with a warning. Multi-URL supported. Search-mode filters are ignored. Pagination starts at the page in the URL and walks forward.

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

When on, each property is enriched with its detail page: floorplan units, full amenities, pet & parking policy, fees, schools, transit, walk score, office hours, ratings, reviews, and description. When off, only the fast result-card fields are returned (name, address, rent range, beds, phone, rating, photo).

## `includeVirtualTours` (type: `boolean`):

When on (and Fetch full property details is on), each property also lists its virtual tour, 3D tour and video tour links in virtualTours. Off by default. Only some properties publish a tour (for example a Matterport 3D tour); the list is empty for the rest. No extra requests are made: the links come from the same detail page.

## `sortBy` (type: `string`):

Order of the search results (Apartments.com's own sort, live-checked: it also holds on later result pages). 'Default' keeps the site's best-match order. Applies to search pages and pasted search links. A pasted link's own query string (for example ?so= or ?bb=) is removed with a warning, so this setting always decides the order. With a Max properties cap the sort decides which properties are kept.

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

Safety bound on how many result pages to walk per location or start URL (about 40 properties per page). 0 (default) means unlimited — the walk stops naturally once a page comes back empty or repeats listings already collected, or sooner once Max properties is reached. Set a number only for an explicit page cap below that natural stop.

## `maxListings` (type: `integer`):

The sole soft cap on the run, applied across all locations/URLs. Defaults to 20 so a run stays small unless you raise it. 0 means unlimited (bounded only by the natural stop on maxPages).

## `maxReviews` (type: `integer`):

How many reviews to keep on each property row. The default (1000, the maximum) keeps every review the property page shows; lower it to keep fewer, 0 skips reviews. Every full or partial block of 10 reviews delivered adds one detail-enrichment unit to that property's detail charge. Applies when the property detail page is fetched.

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

Residential (the default) is the most reliable: Apartments.com is served from United States and Canada residential connections, and the actor retries on a fresh connection when one is refused. Residential needs a paid Apify plan; on the free plan the actor continues on a backup connection and some pages may be unavailable. Residential is recommended; datacenter connections are often refused, so they are less reliable. Apartments.com shows the same United States listings regardless of the connecting region.

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

Optional. Paste a previous run ID or dataset ID to continue a large walk-all pull: properties already saved by that run are loaded before scraping starts and skipped, so this run only appends new properties. For recurring daily monitoring of the same search, use Incremental mode below instead. Leave empty for a normal fresh run.

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

Turn this on for daily or recurring monitoring. The first run returns all matching properties as NEW. Later runs normally return only NEW, UPDATED, and REAPPEARED properties. A property is UPDATED when any extracted field changes (rent, availability, floorplans, units, fees, amenities, photos, reviews, ratings and so on); scrapedAt, rawJsonLd, breadcrumbs, neighborhoodDescription, virtualTours and managementLogo are never compared (nor are the derived copy fields propertyName, location, coordinates, isVerified, rent, beds, baths, sqft, transitAndPOI, scores, models and contact), and you can exclude more with Ignore fields when detecting changes below. Turn on "Emit unchanged" or "Emit expired" only when you also want those properties returned (and billed). State is kept separately for each location/URL, property type, bedroom, rent range, and lifestyle/detail-mode 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 prior 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/URL and filter settings — different searches 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 — not when Max properties capped it or when Resume was used. This returns — and bills — extra synthetic rows, so leave it off unless you need expiry tracking.

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

Optional. Extra fields that must NOT make a property count as UPDATED, e.g. photos, availability or reviews. A plain name matches that field anywhere, including inside floorplans and units; a dotted path such as units.availability matches only that one. The fields stay in the output, they are just not compared. scrapedAt, rawJsonLd, breadcrumbs, neighborhoodDescription, virtualTours and managementLogo are always ignored (so are the derived copy fields propertyName, location, coordinates, isVerified, rent, beds, baths, sqft, transitAndPOI, scores, models and contact).

## `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",
  "locations": [
    "Austin, TX"
  ],
  "propertyType": "any",
  "bedrooms": "any",
  "lifestyle": "none",
  "urls": [
    "https://www.apartments.com/austin-tx/",
    "https://www.apartments.com/austin-tx/2/",
    "https://www.apartments.com/austin-tx/2-bedrooms/",
    "https://www.apartments.com/austin-tx/min-1-bedrooms-under-2000/",
    "https://www.apartments.com/austin-tx/1-bedrooms-under-1500/",
    "https://www.apartments.com/houses/austin-tx/",
    "https://www.apartments.com/condos/austin-tx/",
    "https://www.apartments.com/townhomes/austin-tx/",
    "https://www.apartments.com/austin-tx/pet-friendly-dog/",
    "https://www.apartments.com/austin-tx-78701/",
    "https://www.apartments.com/the-grove-houston-tx/9cwjc35/",
    "https://www.apartments.com/apartments/austin-tx/"
  ],
  "fetchDetails": true,
  "includeVirtualTours": false,
  "sortBy": "default",
  "maxPages": 0,
  "maxListings": 20,
  "maxReviews": 1000,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

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

No description

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

No description

## `reviews` (type: `string`):

No description

## `changes` (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",
    "locations": [
        "Austin, TX"
    ],
    "urls": [
        "https://www.apartments.com/austin-tx/",
        "https://www.apartments.com/austin-tx/2/",
        "https://www.apartments.com/austin-tx/2-bedrooms/",
        "https://www.apartments.com/austin-tx/min-1-bedrooms-under-2000/",
        "https://www.apartments.com/austin-tx/1-bedrooms-under-1500/",
        "https://www.apartments.com/houses/austin-tx/",
        "https://www.apartments.com/condos/austin-tx/",
        "https://www.apartments.com/townhomes/austin-tx/",
        "https://www.apartments.com/austin-tx/pet-friendly-dog/",
        "https://www.apartments.com/austin-tx-78701/",
        "https://www.apartments.com/the-grove-houston-tx/9cwjc35/",
        "https://www.apartments.com/apartments/austin-tx/"
    ],
    "maxReviews": 1000,
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    },
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "ignoreFieldsForChanges": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/apartments-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",
    "locations": ["Austin, TX"],
    "urls": [
        "https://www.apartments.com/austin-tx/",
        "https://www.apartments.com/austin-tx/2/",
        "https://www.apartments.com/austin-tx/2-bedrooms/",
        "https://www.apartments.com/austin-tx/min-1-bedrooms-under-2000/",
        "https://www.apartments.com/austin-tx/1-bedrooms-under-1500/",
        "https://www.apartments.com/houses/austin-tx/",
        "https://www.apartments.com/condos/austin-tx/",
        "https://www.apartments.com/townhomes/austin-tx/",
        "https://www.apartments.com/austin-tx/pet-friendly-dog/",
        "https://www.apartments.com/austin-tx-78701/",
        "https://www.apartments.com/the-grove-houston-tx/9cwjc35/",
        "https://www.apartments.com/apartments/austin-tx/",
    ],
    "maxReviews": 1000,
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "ignoreFieldsForChanges": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/apartments-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",
  "locations": [
    "Austin, TX"
  ],
  "urls": [
    "https://www.apartments.com/austin-tx/",
    "https://www.apartments.com/austin-tx/2/",
    "https://www.apartments.com/austin-tx/2-bedrooms/",
    "https://www.apartments.com/austin-tx/min-1-bedrooms-under-2000/",
    "https://www.apartments.com/austin-tx/1-bedrooms-under-1500/",
    "https://www.apartments.com/houses/austin-tx/",
    "https://www.apartments.com/condos/austin-tx/",
    "https://www.apartments.com/townhomes/austin-tx/",
    "https://www.apartments.com/austin-tx/pet-friendly-dog/",
    "https://www.apartments.com/austin-tx-78701/",
    "https://www.apartments.com/the-grove-houston-tx/9cwjc35/",
    "https://www.apartments.com/apartments/austin-tx/"
  ],
  "maxReviews": 1000,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/apartments-com-scraper --silent --output-dataset

```

## MCP server setup

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