# US Rentals Scraper - Apartments.com & Realtor.com (`thirdwatch/us-rentals-scraper`) Actor

Scrape US rental listings from Apartments.com and Realtor.com in one run. Returns address, coordinates, rent range, beds, baths, square feet, property type, year built, pet policy, amenities, floor plans, photos, leasing phone and days on market. Search by city, ZIP or listing URL.

- **URL**: https://apify.com/thirdwatch/us-rentals-scraper.md
- **Developed by:** [Thirdwatch](https://apify.com/thirdwatch) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 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?

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

## US Rentals Scraper — Apartments.com & Realtor.com

Export US rental listings from the two largest public rental marketplaces in a single run.
Give the Actor a city, a ZIP code, or a listing URL and it returns one clean, normalised
row per property — address, coordinates, rent range, beds, baths, square feet, property
type, year built, pet policy, amenities, per-unit floor plans, photos, the leasing or
broker contact, days on market, status, and the canonical listing URL.

Both sources come back in one dataset with a `source` field on every row, so you can query
Apartments.com and Realtor.com together or keep them apart. Rows are de-duplicated by
listing id within a run.

Every row is normalised the same way regardless of which site it came from: prices and
areas are numbers (never strings), coordinates are floats, dates are ISO 8601 UTC, URLs are
absolute, currency is explicit, and the query that produced the row is stamped on it as
`source_query`.

***

### What you get

| Use case | How to run it |
|---|---|
| **Rent comps for a submarket** — what a 1-bed actually rents for on a given block | Feed a list of ZIP codes, set `minBeds: 1` and `maxBeds: 1`, then group `price_min`/`price_max` by `zip_code`. The `floorplans` array gives per-unit rent, bed, bath and square-foot detail for community properties, so you can build a real distribution instead of averaging headline rents. |
| **Lead lists for property-management and proptech sales** | Run `source: apartments` across your target metros, keep `phone`, `listing_name`, `address` and `broker_name`, and filter to communities with many `floorplans` (larger portfolios). Apartments.com exposes the leasing-office phone directly on the search page, so this needs no detail-page spend. |
| **Renter-facing search and alerting** | Run the same query on a schedule with `source: both`, filter with `minPrice`/`maxPrice`/`minBeds`, and diff against your last dataset on `listing_id`. `days_on_market` and `specials` (concessions like "2 Months Free") tell you which listings are going stale and where the landlord is discounting. |

***

### Input

Only `queries` is required. Everything else has a sensible default.

```json
{
  "queries": ["Austin, TX", "78704", "https://www.apartments.com/the-watson-austin-tx/s286zb6/"],
  "source": "both",
  "maxResults": 50,
  "includeDetails": false,
  "minPrice": 1200,
  "maxPrice": 3000,
  "minBeds": 1,
  "maxBeds": 2,
  "minBaths": 1,
  "propertyTypes": ["apartments", "houses"],
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyCountry": "US" }
}
```

| Field | Type | Default | Meaning |
|---|---|---|---|
| `queries` | array of strings | *(required)* | One entry per search. Accepts a city and state (`Austin, TX`), a bare ZIP code (`78704`), an Apartments.com or Realtor.com **search** URL, or a **single listing** URL (scraped as one detailed row). |
| `source` | `apartments` | `realtor` | `both` | `both` | Which site(s) to scrape. A site-specific URL in `queries` is always sent to that site only, whatever this is set to. |
| `maxResults` | integer 1–1000 | `50` | Listings per query **per source**. With `source: both`, `15` returns up to 30 rows per query. |
| `includeDetails` | boolean | `false` | Visit each listing page to add year built, the full description, the complete photo set and amenity list, pet policy, per-unit floor plans, and the contact phone. Slower and more expensive. |
| `maxDetailPages` | integer | all | Caps how many listings get the detail visit when `includeDetails` is on. |
| `minPrice` / `maxPrice` | integer | — | Monthly rent window in USD. Applied as a site filter where supported and re-checked on every row before it is saved. A listing whose rent *range* overlaps the window is kept. |
| `minBeds` / `maxBeds` | integer 0–8 | — | Bedroom range. Use `0` to include studios. |
| `minBaths` | integer 0–8 | — | Minimum bathrooms. |
| `propertyTypes` | array | all | Any of `apartments`, `houses`, `condos`, `townhomes`. |
| `proxyConfiguration` | object | US datacenter | See **Proxies and blocking** below. |

#### Searching by ZIP code

Apartments.com has no bare-ZIP search URL, so a five-digit query is resolved to its city and
state first and searched as `city-st-zip`. Realtor.com takes the ZIP directly. Results for a
ZIP query can include listings in immediately adjacent ZIPs — that is the sites' own
behaviour, and every row carries its true `zip_code` so you can tighten the set yourself.

#### Getting past the pagination ceiling

Both sites cap how deep a single search paginates (about 1,080 listings on Apartments.com,
about 2,100 on Realtor.com). To pull a whole metro, pass several ZIP codes or neighbourhood
names as separate `queries` rather than one large city.

***

### Output

One row per listing. Search-only mode fills everything the search page carries; the fields
marked **detail** are populated when `includeDetails` is on (or when you pass a listing URL
directly).

| Field | Type | Notes |
|---|---|---|
| `listing_id` | string | Stable per site. De-duplication key, namespaced by source. |
| `source` | string | `apartments` or `realtor`. |
| `source_query` | string | The `queries` entry that produced this row. |
| `listing_url` | string | Canonical, absolute listing URL. |
| `listing_name` | string | Property or community name. |
| `address` | string | Full one-line address. |
| `street` | string | Street line only. |
| `city` | string | |
| `state` | string | Two-letter code. |
| `zip_code` | string | |
| `latitude` | float | |
| `longitude` | float | |
| `price` | integer | null | Set only when the listing has one rent, not a range. |
| `price_min` | integer | null | Low end of the advertised rent. |
| `price_max` | integer | null | High end of the advertised rent. |
| `price_formatted` | string | Display string, e.g. `$1,615 - $4,884`. |
| `currency` | string | Always `USD`. |
| `beds` / `beds_min` / `beds_max` | number | null | `0` means studio. |
| `baths` / `baths_min` / `baths_max` | number | null | Apartments.com: **detail**. |
| `square_feet` / `square_feet_min` / `square_feet_max` | integer | null | Apartments.com: **detail**. |
| `property_type` | string | `apartment`, `house`, `condo`, `townhome`, … |
| `year_built` | integer | null | **detail** |
| `pet_policy` | object | null | Apartments.com: `{"pets_allowed": true}`. Realtor.com: `{"cats": …, "dogs": …}`. |
| `amenities` | array of strings | Short list on search, full list on **detail**. |
| `floorplans` | array of objects | `{name, beds, baths, square_feet, price, price_min, price_max, availability}` per unit or plan. |
| `photos` | array of strings | Search pages carry only the thumbnails: 1 photo on Apartments.com, ~3 on Realtor.com. Turn on `includeDetails` for the full gallery (typically 18–40). |
| `photo_count` | integer | Length of `photos`. |
| `agent_name` | string | Realtor.com, where a named agent is listed. |
| `broker_name` | string | Realtor.com management or brokerage office. |
| `phone` | string | Apartments.com: leasing office, on search. Realtor.com: **detail**. |
| `list_date` | string | ISO 8601. Realtor.com only. |
| `days_on_market` | integer | null | Derived from `list_date`. Realtor.com only. |
| `listing_status` | string | e.g. `for_rent`. |
| `specials` | string | Concessions, e.g. `2 Months Free`. |
| `description` | string | **detail**, up to 4,000 characters. |
| `scraped_at` | string | ISO 8601 UTC. |

#### Sample row (abridged)

```json
{
  "listing_id": "s286zb6",
  "source": "apartments",
  "listing_url": "https://www.apartments.com/the-watson-austin-tx/s286zb6/",
  "listing_name": "The Watson",
  "address": "11901 Burnet Rd, Austin, TX 78758",
  "city": "Austin", "state": "TX", "zip_code": "78758",
  "latitude": 30.40504, "longitude": -97.71511,
  "price_min": 1615, "price_max": 4884, "price_formatted": "$1,615 - $4,884",
  "currency": "USD",
  "beds_min": 0.0, "beds_max": 2.0, "baths_min": 1.0, "baths_max": 2.0,
  "square_feet_min": 425, "square_feet_max": 1403,
  "property_type": "apartment", "year_built": 2026,
  "pet_policy": {"pets_allowed": true},
  "amenities": ["Pool", "Fitness Center", "Rooftop Deck"],
  "floorplans": [{"name": "S1WF", "beds": 0, "baths": 1, "square_feet": 434, "price": 1615}],
  "photo_count": 18,
  "phone": "+1-832-730-2535",
  "specials": "2 Months Free",
  "listing_status": "for_rent",
  "scraped_at": "2026-09-07T22:04:48+00:00"
}
```

#### What each source does and does not carry

These are limits of the sites, not of the Actor:

- **Apartments.com** has no listing agent, broker or list date, so `agent_name`,
  `broker_name`, `list_date` and `days_on_market` stay empty. The leasing-office `phone` is
  the contact, and it is available without a detail visit.
- **Apartments.com** search results carry rent and bedrooms but not bathrooms, square feet,
  year built or the description — turn on `includeDetails` for those.
- **Realtor.com** search cards carry no phone number; turn on `includeDetails` to get it.
- For a multi-unit community, the singular `price`, `beds`, `baths` and `square_feet` are
  `null` by design — a community has a *range*, not one value. Use the `_min`/`_max` pair, or
  `floorplans` for per-unit numbers.

***

### Proxies and blocking

Both sites are protected, at different strengths. Measured 2026-09-07, three requests per
cell against an Austin, TX search:

| Source | Datacenter US | Residential US | Apify Unblocker |
|---|---|---|---|
| Apartments.com (iOS app User-Agent) | **3/3 OK** | 3/3 OK | 3/3 OK |
| Apartments.com (desktop Chrome User-Agent) | — | 0/3 (`403`, 388-byte "Access Denied") | — |
| Realtor.com (desktop Chrome User-Agent) | 0/3 (`429`) | 0/3 (`429`) | **3/3 OK** |

So the Actor runs **Apartments.com on the cheap US datacenter pool** and automatically
escalates to US residential only if datacenter starts getting denied, and **Realtor.com
through the Apify Unblocker**, which is the only route that is not rate-limited. Pin a group
in `proxyConfiguration` and the Actor uses exactly that pool with no escalation; supply your
own `proxyUrls` and they are used verbatim. Apartments.com serves US IPs only.

No browser is used for either source — this is a pure HTTP Actor running in 256 MB.

### Zero results are honest

A run that returns nothing tells you *why*. If a source answered with a real listings page
that simply had no matches, the run **succeeds** with a status message asking you to widen
the filters. If the source never answered with a parseable page — blocked, rate-limited, or
serving a challenge — the run **fails** with a message naming the blocked source. A block is
never reported as "no listings found".

### Legal

Scrapes only publicly available listing pages. No login, no paywall, and no personal data
beyond the business contact details the sites publish on the listing itself. You are
responsible for how you use the output, including compliance with fair-housing rules when
listing data is used in advertising or tenant screening.

# Actor input Schema

## `queries` (type: `array`):

One entry per search. Accepts a city and state (Austin, TX), a ZIP code (78704), an Apartments.com or Realtor.com search URL, or a single listing URL (which is scraped as one detailed row). Bare ZIP codes are resolved to a city and state before Apartments.com is searched.

## `source` (type: `string`):

Which site to scrape. `both` runs each query against Apartments.com and Realtor.com and returns a merged, de-duplicated dataset with a `source` field on every row.

## `maxResults` (type: `integer`):

How many listings to return for each entry in `queries`, per source. With `source: both` a value of 15 returns up to 30 rows per query. Use several ZIP codes or neighbourhoods instead of one huge city to get past each site's pagination ceiling.

## `includeDetails` (type: `boolean`):

Visit each listing page to add year built, long description, the full photo set, pet policy, complete amenity list, per-unit floor plans and the leasing office phone number. Slower and more expensive than listing-only mode.

## `maxDetailPages` (type: `integer`):

Caps how many listings get the extra detail-page visit when `includeDetails` is on. Leave empty to enrich every listing returned.

## `minPrice` (type: `integer`):

Minimum monthly rent. Applied as a site filter where the site supports it, and re-checked on every row before it is saved.

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

Maximum monthly rent. Applied as a site filter where the site supports it, and re-checked on every row before it is saved.

## `minBeds` (type: `integer`):

Minimum number of bedrooms. Use 0 to include studios.

## `maxBeds` (type: `integer`):

Maximum number of bedrooms.

## `minBaths` (type: `integer`):

Minimum number of bathrooms.

## `propertyTypes` (type: `array`):

Leave empty for every rental property type. Each option maps to the matching filter on both sites.

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

Apartments.com only serves listings to US IPs. The default is the cheap US datacenter pool, which the Actor automatically escalates to US residential if datacenter starts getting denied. Pin a group here only if you want to force one pool and skip that fallback. Realtor.com always goes through the Apify Unblocker group, which is the only route that is not rate-limited; supplying custom `proxyUrls` overrides both.

## Actor input object example

```json
{
  "queries": [
    "Austin, TX"
  ],
  "source": "both",
  "maxResults": 50,
  "includeDetails": false,
  "propertyTypes": [],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

## `results` (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 = {
    "queries": [
        "Austin, TX"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("thirdwatch/us-rentals-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 = { "queries": ["Austin, TX"] }

# Run the Actor and wait for it to finish
run = client.actor("thirdwatch/us-rentals-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 '{
  "queries": [
    "Austin, TX"
  ]
}' |
apify call thirdwatch/us-rentals-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,thirdwatch/us-rentals-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/qnhKjA9nXJxe6cLbE/builds/szMeOlC176AIHntkh/openapi.json
