# Apartments.com Scraper — Rentals, Property Managers & Phones (`juramoshkov/apartments-com-scraper`) Actor

Apartments.com scraper for US rentals — rent by bed count, concessions, availability, leasing phone and the property management company. Enrich for 66 fields: amenities, floor plans, units, schools, transit, walk scores, fees, photos. Managers mode returns a company directory.

- **URL**: https://apify.com/juramoshkov/apartments-com-scraper.md
- **Developed by:** [Jura Moshkov](https://apify.com/juramoshkov) (community)
- **Categories:** Real estate, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.50 / 1,000 listing scrapeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Apartments.com Scraper — Rentals, Property Managers & Phones

Apartments.com scraper for US rental data. Pull rental listings by ZIP code, city, or map radius with rent by bed count, fees, current concessions, availability, the leasing phone, and the property management company behind each building. Turn on enrichment and every building comes back as a **66-field record** — amenities, floor plans, individually available units, schools, transit, walk scores, pet and parking policies, office hours, and the full photo gallery. Switch to managers mode and the same run returns a deduplicated company directory instead of a listing feed.

### What This Apartments.com Scraper Does

- Scrape Apartments.com rental listings by ZIP code, `City, ST`, or a latitude/longitude radius.
- Return rent bands per bed count, unit sizes, availability counts, and first-available dates.
- Surface **concessions**: the percentage a building is currently discounting, so you can see who is soft on price.
- Capture the leasing phone number for every building.
- Capture the property management company, with a stable company id you can group on.
- **Enrich** each building with the complete record: amenities, floor plans with rents, the actual available units and their apartment numbers, assigned schools with ratings, transit distances, five walk/transit/bike/car/quiet scores, itemised one-time costs and recurring fees, pet and parking policies, office hours, up to 77 photos, year built, unit count, storeys, and lease-term range.
- Roll everything up into a **property manager directory**: portfolio size, cities, phones, rent range, and how many of its buildings are discounting.
- Filter by beds, baths, rent, size, and pet policy before you pay for a single row.

### Full Building Records — What Enrichment Adds

Set `enrich` to `true` and each row grows from 30 fields to 66. This is one extra upstream request per building, billed as `LISTING_ENRICHED` rather than `LISTING_SCRAPED`, so you pay for depth only where you asked for it.

| Block | What comes back |
|---|---|
| `amenities` | Flat list of every amenity across community and interior categories — typically 60+ per building |
| `floorPlans` | Every plan with beds, baths, area, rent range, and a floor-plan image |
| `availableUnits` | The actual vacant units: apartment number, plan, beds, baths, area, rent |
| `schools` | Assigned and nearby schools with 1–10 rating, grades, enrolment, district |
| `transit` | Airports, rail and bus stops with distance, drive time, walk time |
| `walkScore` … `quietScore` | Five separate scores: walk, transit, bike, car, and quiet |
| `oneTimeCosts` / `recurringFees` | Itemised and deduplicated — application fees, deposits, admin fees, monthly charges |
| `petPolicies` | Per pet type: deposit, monthly rent, fee, limit, restrictions |
| `parkingPolicies`, `officeHours` | Parking rules; leasing-office hours per day |
| `photos` | Full gallery with type and caption |
| `yearBuilt`, `unitCount`, `storyCount`, `leaseTerms`, `submarket`, `description` | Building fundamentals |
| `listingUrl`, `propertyWebsiteUrl` | Canonical Apartments.com page and the property's own site |

### Property Manager Directory — The Mode Worth Running First

Every Apartments.com scraper returns listings. This one also answers the question a listing feed cannot: *who owns the leasing decisions in this market?*

Set `mode` to `managers` and the Actor reads the buildings for your locations, groups them by management company, and returns one row per company:

| Field | What it tells you |
|---|---|
| `propertyManager` | Company name, e.g. `Greystar` |
| `propertyCount` | How many buildings it runs in your search area |
| `cities` / `states` | Its geographic footprint within the search |
| `phones` | Every leasing number tied to it |
| `minRent` / `maxRent` | The rent range it operates across |
| `totalUnitsAvailable` | Live vacancy across the portfolio |
| `propertiesDiscounting` | How many of its buildings are running concessions |
| `avgRating` | Average resident rating across its buildings |

One downtown Austin ZIP returns 17 companies, the largest running 5 buildings and 236 available units. Rows are sorted biggest portfolio first, which is the order a prospecting list should be worked in.

This is the difference between "here are 500 apartments" and "here are the 17 companies that control them, with phone numbers."

### Use Cases

- **B2B prospecting** — property management companies are buyers of software, insurance, maintenance, cleaning, and renovation services. This is a sourced, deduplicated list of them with phones.
- **Rent comparables** — rent bands by bed count per building, timestamped, for a ZIP or a whole city.
- **Concession tracking** — `topDealPct` flags who is discounting. Run it weekly and you have a market-softness time series nobody publishes.
- **Vacancy monitoring** — `totalUnitsAvailable` and `availabilityText` per building.
- **Rental site and app content** — listings with photos, tours, ratings, and geocoordinates.
- **Market entry research** — which operators dominate a metro, and at what price points.

### Input

Pick a `mode`:

- `locations` — one row per rental building for each ZIP or `City, ST` you pass.
- `radius` — the same, around a map point.
- `managers` — one row per property management company, rolled up from the buildings it operates.

Search by ZIP code:

```json
{
  "mode": "locations",
  "locations": ["78701", "78704"],
  "maxResults": 100,
  "minBeds": 1,
  "maxRent": 2500
}
```

Property manager directory for a city:

```json
{
  "mode": "managers",
  "locations": ["Austin, TX"],
  "maxResults": 300
}
```

Only buildings currently discounting rent:

```json
{
  "mode": "locations",
  "locations": ["78701"],
  "onlyDiscounted": true,
  "maxResults": 50
}
```

Full records for every building:

```json
{
  "mode": "locations",
  "locations": ["78701"],
  "enrich": true,
  "maxResults": 50
}
```

Map radius:

```json
{
  "mode": "radius",
  "geoPoint": "30.2672, -97.7431",
  "radiusMiles": 5,
  "maxResults": 100
}
```

Filters available on every mode: `minBeds`, `maxBeds`, `minBaths`, `minRent`, `maxRent`, `minSqft`, `maxSqft`, `pets` (`none`, `dog`, `cat`, `dog_and_cat`), and `sort` (`default`, `lowest_rent`, `highest_rent`, `newest`, `recently_updated`, `has_video`). Filters are applied upstream, so a narrow search costs fewer rows.

### Output

Listing rows (`locations` and `radius` modes):

- `listingKey`, `name`
- `streetAddress`, `city`, `state`, `zipcode`, `country`
- `latitude`, `longitude`
- `phone` — leasing office number
- `propertyManager`, `propertyManagerId`
- `minRent`, `maxRent`, `minBeds`, `maxBeds`
- `minFees`, `maxFees`
- `topDealPct` — best current concession, `null` when nothing is discounted
- `totalUnitsAvailable`, `hasAvailabilities`, `availabilityText`
- `unitSummaries[]` — per bed count: `beds`, `minBaths`, `maxBaths`, `minRent`, `maxRent`, `minSize`, `maxSize`, `availabilityCount`, `firstAvailable`, `topDealPct`
- `rating`, `imageUrl`, `photoCount`, `tourUrl`
- `searchLocation`, `resolvedLocation`, `scrapedAt`

Example listing row:

```json
{
  "listingKey": "wkkr1zj",
  "name": "Placemakr Downtown Austin",
  "streetAddress": "710 E 3rd St, Austin, TX 78701",
  "city": "Austin",
  "state": "TX",
  "zipcode": "78701",
  "phone": "(737) 204-4980",
  "propertyManager": "Placemakr",
  "minRent": 1517,
  "maxRent": 1924,
  "minBeds": 0,
  "maxBeds": 2,
  "topDealPct": null,
  "totalUnitsAvailable": 10,
  "rating": 5,
  "resolvedLocation": "78701, Austin, TX"
}
```

Manager rows (`managers` mode) carry `propertyManager`, `propertyManagerId`, `propertyCount`, `cities`, `states`, `phones`, `minRent`, `maxRent`, `avgRating`, `totalUnitsAvailable`, `propertiesDiscounting`, `sampleProperties`, `searchLocation`, `scrapedAt`.

### Pricing

Pay per result. A tiny `apify-actor-start` charge ($0.00005) covers the first five seconds of run
time, which is most of a typical run — then three result events, because the three outputs are not
the same product:

- `LISTING_SCRAPED` — one rental building, 30 fields.
- `LISTING_ENRICHED` — one rental building, 66 fields, when `enrich` is on.
- `MANAGER_PROFILED` — one management company, aggregated from every building it runs in your search.

Every event is discounted by Apify plan, down to 40% off on the top plans. A managers run bills only per company, never per building it read on the way. If an enrichment call fails, the building is still delivered and billed at the base rate, never the enriched one. Empty results and error rows are never charged.

### Scheduling and Integrations

Rent and concession data is only interesting as a time series — a single snapshot cannot tell you whether a market is softening.

- Use Apify **Schedules** to run weekly. Every row carries `scrapedAt`, so history is queryable with no extra bookkeeping.
- Export to CSV, JSON, or Excel, or push to Google Sheets, S3, or a webhook through Apify integrations.
- Call the Actor from the Apify API to feed an existing CRM or rent-comp dashboard.

### FAQ

#### Does this Apartments.com scraper return contact details?

Yes — a leasing phone number for every building, plus the property management company name. Rental listings do not expose agent email addresses the way for-sale listings do, so no email field is promised here.

#### How do I get a list of property management companies?

Set `mode` to `managers` and pass one or more ZIP codes or cities. Each row is one company with its portfolio size, footprint, phones, and rent range, sorted biggest first.

#### Can I find apartments that are offering a discount?

Yes. `topDealPct` carries the current concession percentage, and `onlyDiscounted: true` filters the run down to buildings that have one.

#### How many listings does one run return?

As many as `maxResults`. The upstream provider pages 50 buildings at a time and the Actor pages through automatically; a single downtown ZIP typically has several hundred buildings.

#### Do I need an API key?

No. Run it as-is. If you have your own provider subscription you can pass `rapidApiKey` to use it instead.

#### Can I search by map area instead of ZIP?

Yes. Use `radius` mode with `geoPoint` — paste coordinates in the `30.2672, -97.7431` form Google Maps copies — and set `radiusMiles`.

#### Are duplicate buildings billed twice?

No. Listings are deduplicated by `listingKey` across pages and overlapping radius searches before anything is charged.

#### Does it cover rentals outside the US?

No — Apartments.com is US inventory. For Canadian real estate see the Realtor.ca scraper; for US for-sale listings see the Realtor.com and Redfin scrapers.

#### Is there a per-unit floor plan feed?

Yes, with `enrich` on: `floorPlans` lists every plan with rent and a floor-plan image, and `availableUnits` lists the actual vacant apartments with their unit numbers. Without enrichment you get `unitSummaries`, one entry per bed count.

#### What does enrichment cost me?

One extra upstream request per building, billed as `LISTING_ENRICHED`. Leave it off for a cheap sweep of a market, turn it on for the buildings you actually care about — a two-pass workflow (sweep wide, then enrich a filtered shortlist) is the cheapest way to work a large area.

#### Do I get walk scores?

Yes, with enrichment — five of them: walk, transit, bike, car, and quiet. Quiet legitimately comes back as 0 for the noisiest locations; that is a real score, not a missing value.

### Notes

- `locations` accepts ZIP codes and `City, ST`. The provider resolves the value server-side and echoes the match back in `resolvedLocation`, so you can confirm what was actually searched.
- `amenities` is validated upstream: an unknown slug fails the whole request. Leave it empty unless you know the exact value.
- `topDealPct` is `null` rather than `0` when a building has no concession — absence and a zero-percent deal are different facts.
- Base rows carry `listingKey`; the canonical `listingUrl` arrives with enrichment, straight from the source rather than constructed.
- This Actor returns public listing data. It does not estimate rents or provide tenant information.

# Actor input Schema

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

locations — one row per rental building for each ZIP or city. radius — the same, around a map point. managers — one row per property-management company, rolled up from the buildings it operates.

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

ZIP code (78701) or "City, ST" (Austin, TX). Used by locations and managers modes. Defaults to 78701 (downtown Austin) so a run with no input still returns data.

## `geoPoint` (type: `string`):

Radius mode only. Paste coordinates as "30.2672, -97.7431" — the format Google Maps copies.

## `radiusMiles` (type: `integer`):

Radius mode only. Defaults to 5 miles.

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

Buildings to return per run (in managers mode, buildings to read before rolling up).

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

Keep only buildings offering at least this many bedrooms. 0 includes studios.

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

Keep only buildings offering at most this many bedrooms.

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

Keep only buildings offering at least this many bathrooms.

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

Lowest monthly rent to include, in USD.

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

Highest monthly rent to include, in USD.

## `minSqft` (type: `integer`):

Smallest unit size to include, in square feet.

## `maxSqft` (type: `integer`):

Largest unit size to include, in square feet.

## `pets` (type: `string`):

Keep only buildings allowing the selected pets.

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

Order the provider returns buildings in. Matters when combined with Max results, since it decides which buildings you get.

## `amenities` (type: `array`):

Provider-validated amenity slugs. An unknown value fails the request, so leave empty unless you know the exact slug.

## `onlyDiscounted` (type: `boolean`):

Keep only buildings currently discounting rent (a non-empty deal percentage). Useful for rent-negotiation and market-softness research.

## `enrich` (type: `boolean`):

Fetch the complete record per building — 66 fields instead of 27: amenities, floor plans, available units, schools, transit, five walk/transit/bike/car/quiet scores, itemised fees, pet and parking policies, office hours, photo gallery, year built, unit count and lease terms. Costs one extra upstream request per building and is billed as LISTING\_ENRICHED instead of LISTING\_SCRAPED.

## `requestDelayMs` (type: `integer`):

Pause between upstream requests. 0 runs as fast as your provider plan allows.

## `rapidApiKey` (type: `string`):

Bring your own provider key. Leave empty to use the Actor's key.

## Actor input object example

```json
{
  "mode": "locations",
  "locations": [
    "78701"
  ],
  "geoPoint": "30.2672, -97.7431",
  "radiusMiles": 5,
  "maxResults": 50,
  "sort": "default",
  "onlyDiscounted": false,
  "enrich": false,
  "requestDelayMs": 300
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `resultsCsv` (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 = {
    "locations": [
        "78701"
    ],
    "geoPoint": "30.2672, -97.7431"
};

// Run the Actor and wait for it to finish
const run = await client.actor("juramoshkov/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 = {
    "locations": ["78701"],
    "geoPoint": "30.2672, -97.7431",
}

# Run the Actor and wait for it to finish
run = client.actor("juramoshkov/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 '{
  "locations": [
    "78701"
  ],
  "geoPoint": "30.2672, -97.7431"
}' |
apify call juramoshkov/apartments-com-scraper --silent --output-dataset

```

## MCP server setup

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