# Zillow Agent Scraper - Agent Leads & Their Listings (`studioxl/zillow-agent-scraper`) Actor

Every estate agent in a city, with everything they have for sale and for rent: price, beds, baths, address, coordinates and brokerage. Plus review score, career sales, average sale price and price range. Optionally add Zestimate, floor area, year built and description to every listing.

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

## Pricing

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

## Zillow Agent Scraper

One row per estate agent, with everything they currently have on the market.

### What it does

Type a city. Get every agent Zillow lists there, ranked busiest first, each with
their review score, career sales, sales in the last twelve months, average sale
price and price range. Switch on the portfolio options and each row also carries
every property they currently have for sale and every one they have for rent,
with price, beds, baths, address, coordinates and brokerage.

It works from the agent's own record rather than from properties, which is why
the portfolio is the agent's whole portfolio rather than whatever happened to
fall inside a search.

### What comes back

| Field | Where it comes from |
|---|---|
| `agentName` · `encodedZuid` · `profileUrl` · `photoUrl` | Agent directory |
| `isTopAgent` · `isTeam` | Agent directory |
| `reviewAverage` · `reviewCountLabel` | Agent directory |
| `salesAllTime` · `salesLastYear` | Zillow's public sales-stats endpoint |
| `priceRangeMin` · `priceRangeMax` · `averageSalePrice` | Same, over three years |
| `listings` | Their live for-sale portfolio, 21 fields each |
| `rentals` | Their live rental portfolio, same shape |
| `listingCount` · `rentalCount` | The agent's true totals |
| `listingsFetched` | How many this run actually pulled |
| `listingsTotalValue` | Sum of their live asking prices |

The count fields and the fetched fields are deliberately separate. A row reading
`listingCount: 55` beside ten listings is telling you the agent has 55 and your
cap stopped at ten. A portfolio you did not ask for is left off the row
altogether rather than reported as zero, because a zero that means "we did not
look" is indistinguishable from one that means "they have none".

#### What is not here

No agent email address, and no licence number. Both appear only on the
`/profile/<slug>` page, which returns HTTP 403 to any request without a real
browser behind it. That was re-tested on 30 August 2026 across three agents, from
a clean residential IP, in the same minute the endpoints this Actor does use
returned 200. Actors that publish agent email are either driving a headless
browser per profile or serving it from a database they built earlier. Both are
legitimate; neither is free, and this Actor does not pretend to do it.

### What about what they have already sold?

Not available, and this is worth explaining because it changed while this Actor
was being built.

Zillow does publish an agent's full sold history, thousands of closed sales per
agent, from an endpoint on their profile. It was working on 30 August 2026. On 31
August it began returning HTTP 403 to every request that does not carry a browser
session cookie. Verified both ways on the same day: the endpoint returns 200
inside a real browser and 403 to the same URL, same headers, from 30 different
residential addresses.

Earning that cookie means running a full browser per profile and solving a
JavaScript challenge. This Actor is deliberately HTTP-only, which is why it costs
what it costs, so sold history is not offered rather than offered unreliably. The
`salesAllTime`, `salesLastYear` and `averageSalePrice` columns still answer "how
much has this agent sold", from a different endpoint that is not gated. What is
not available is the list of individual houses.

### Full property records

The agent's own record carries price, beds, baths, address, coordinates and
brokerage for each listing. It carries no valuation, no floor area, no year
built, no tax rate and no description, because Zillow does not put those there.

Switch on **Add the full property record to each listing** and every for-sale row
picks up Zestimate, rent Zestimate, living area, lot size, year built, days on
Zillow, price change, MLS id, property tax rate, monthly HOA fee, photo count and
the listing description, plus the listing agent's direct phone, which the
directory never publishes. That means opening one property page per listing, so
it is charged separately and is off by default.

This is the one thing here that comes from the same parser behind the
[Zillow Property Detail Scraper](https://apify.com/studioxl/zillow-detail-scraper),
and it is the part of this Actor that has no equivalent elsewhere.

### How to use it

1. **Type your cities**, for example `Austin, TX` or `Chicago, IL`.
2. **Choose what to pull per agent.** For sale is on by default. Rentals are off,
   because most agents have none.
3. **Set the caps.** Zillow returns five rows per request on both portfolios, so
   the caps are what decide the cost of a run.
4. **Click Start.**

Agents are returned busiest first, and every filter is applied before any
portfolio is fetched, so a run capped at 50 agents reads 50 agents' portfolios
rather than every agent the directory happened to return.

### What you are charged for

Two events, both of which you control:

- **Agent scraped**, once per agent row delivered. Agents removed by your filters
  are never charged.
- **Listing enriched**, once per property page opened, and only when you switch
  full property records on. Leave it off and it never fires.

The caps are your spend control. Because Zillow serves five rows per request and
offers no larger page, a cap of 25 listings costs five requests per agent and a
cap of 100 costs twenty. A measured 100-agent Chicago run with a 25-listing cap
took 1 minute 41 seconds and 167 page fetches, with nothing lost to blocking.

### Input example

Agents in a city, with everything they have listed:

```json
{
  "locations": ["Chicago, IL"],
  "maxItems": 50,
  "minSalesLastYear": 10,
  "includeActiveListings": true,
  "maxListingsPerAgent": 25,
  "includeRentals": true,
  "maxRentalsPerAgent": 25
}
```

A cheap agent list with no portfolios at all:

```json
{
  "locations": ["Austin, TX"],
  "topAgentsOnly": true,
  "minSalesLastYear": 20,
  "includeActiveListings": false,
  "maxItems": 200
}
```

### Output example

One real row from a Chicago run, trimmed:

```json
{
  "agentName": "Matt Laricy",
  "encodedZuid": "X1-ZUyy58njdzkphl_aqadk",
  "isTopAgent": true,
  "reviewAverage": 5,
  "salesAllTime": 6924,
  "salesLastYear": 550,
  "averageSalePrice": 556672,
  "listingCount": 55,
  "listingsFetched": 10,
  "listingsTotalValue": 4524894,
  "rentalCount": 0,
  "listings": [
    {
      "zpid": 80851288,
      "url": "https://www.zillow.com/homedetails/440-N-Wabash-Ave-APT-2907-Chicago-IL-60611/80851288_zpid/",
      "price": 225000,
      "homeType": "condo",
      "homeStatus": "active",
      "bedrooms": 1,
      "bathrooms": 1,
      "streetAddress": "440 N Wabash Ave APT 2907",
      "city": "Chicago",
      "state": "IL",
      "zipcode": "60611",
      "latitude": 41.89042,
      "longitude": -87.627464,
      "brokerName": "Americorp, Ltd"
    }
  ]
}
```

One row per agent, with the portfolios nested. Apify's CSV and Excel export
flattens them into numbered columns, `listings/0/price`, `listings/1/price` and
so on.

### Integrations

Connect this Actor to n8n, Make, Zapier, Pipedream, HubSpot, Airtable, Slack,
Telegram, Gmail, Google Drive, Snowflake, Keboola, Airbyte, GitHub, LangChain,
the OpenAI Agents SDK, the Vercel AI SDK and Google ADK, or to anything else
through webhooks and the Apify API.

Two combinations worth knowing:

- **Recruiting shortlist.** Filter on minimum sales in the last twelve months,
  schedule it monthly against your target cities, and send the finished dataset
  to Airtable or HubSpot. Agents below your threshold are dropped before they are
  charged.
- **Full records for an agent's stock.** Switch on full property records to get
  Zestimate, floor area and description on everything an agent is selling, which
  is the same data the
  [Zillow Property Detail Scraper](https://apify.com/studioxl/zillow-detail-scraper)
  returns for properties you already have URLs for.

### FAQ

#### Is it legal to scrape Zillow?

This Actor reads public pages. There is no login, no paywall and no personal data
beyond what a working agent publishes as their business contact details. What you
do with the results is your responsibility, particularly if you contact anyone,
where marketing and data-protection rules apply. This is not legal advice.

#### Does this Actor respect robots.txt?

Yes, and it needs no exception. The agent directory, the sales-stats endpoint and
all three portfolio endpoints are permitted. The switch is there so that if
Zillow changes its rules, the run stops rather than carrying on.

Earlier versions had to qualify this, because building an agent's portfolio meant
scanning a city's listings through a route Zillow's robots.txt does not permit.
That route is gone.

#### Why does an agent have fewer listings than `listingCount` says?

Because your cap stopped the fetch. Zillow returns five rows per request, so
`maxListingsPerAgent` decides how many pages are worth asking for. `listingCount`
is always the agent's real total, so the two together tell you exactly what you
have and what you are missing. Raise the cap to close the gap.

#### Can I get agent email addresses?

No. See "What is not here" above. Phone numbers are available on for-sale
listings when you switch on full property records, since a property page names
the listing agent and their direct number.

#### Why are some agents teams rather than people?

Because Zillow's directory ranks both, and it does not separate them. "Vesta
Preferred Realty" and "Matt Laricy" sit side by side in the same results. Every
row carries `isTeam` so you can split them yourself, and `statsIncludeTeam` tells
you whether a sales figure covers the individual or the whole team.

#### Can I use this with the Apify API or an MCP server?

Yes, both. The Actor runs under limited permissions and needs no special access.

### Other real-estate scrapers

| Actor | What it does |
|---|---|
| [Zillow Property Detail Scraper](https://apify.com/studioxl/zillow-detail-scraper) | Full 60-field record for any property |
| [Zillow Search Scraper](https://apify.com/studioxl/zillow-search-scraper) | Find listings by area and filters |
| **Zillow Agent Scraper** | This one |

### Support

Missing a field, or want a different shape? Open an issue on the **Issues** tab.
Field requests are welcome, and if Zillow publishes it, it can usually be added.

# Actor input Schema

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

Cities or areas, for example "Austin, TX" or "Chicago, IL". Each is searched separately and the results are combined.

## `agentNames` (type: `array`):

Look up named agents directly, for example "Matt Laricy". Zillow's name search is fuzzy and also returns similar names, so only agents whose name actually matches what you typed are kept.

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

Your spend cap. Filters are applied before any portfolio is fetched, so a run capped at 50 reads 50 agents' portfolios rather than every agent the directory returned. Set 0 for no limit.

## `maxPagesPerLocation` (type: `integer`):

Zillow lists 15 agents per directory page. This caps how deep to go in each area.

## `topAgentsOnly` (type: `boolean`):

Only agents carrying Zillow's Top Agent badge.

## `minSalesLastYear` (type: `integer`):

Skip agents below this many recent sales, the quickest way to strip out inactive licence-holders. Applied before portfolios are fetched, so agents you filter out cost nothing.

## `includeActiveListings` (type: `boolean`):

Every property the agent currently has listed for sale, with price, beds, baths, address, coordinates and brokerage. This is the agent's complete for-sale portfolio as Zillow holds it, not a sample.

## `maxListingsPerAgent` (type: `integer`):

Zillow returns five listings per request and offers no larger page, so 25 costs five requests per agent and 100 costs twenty. Whatever you set, the row still reports the agent's true total in listingCount, so you can tell a capped portfolio from a small one.

## `includeRentals` (type: `boolean`):

The agent's rental listings, same shape as the for-sale ones. Off by default because most agents have none: a Chicago agent with 55 properties for sale had 4 for rent.

## `maxRentalsPerAgent` (type: `integer`):

Same five-per-request cost as listings.

## `onlyAgentsWithListings` (type: `boolean`):

Drop agents with nothing currently for sale. Applied before charging, so filtered agents are not billed to you.

## `enrichListings` (type: `boolean`):

The agent's own record carries price, beds, baths, address and coordinates, but Zillow does not put a valuation, floor area, year built, tax rate or description there. Those exist only on the property page, so getting them means opening one page per listing.

Switching this on adds Zestimate, rent Zestimate, living area, lot size, year built, days on Zillow, price change, MLS id, tax rate, HOA fee, photo count and the listing description to every for-sale row, and picks up the listing agent's direct phone, which the directory never publishes.

This is charged per property opened, separately from the agent row, because one property fetch costs about what a whole agent costs. Leave it off and you pay nothing for it.

## `respectRobotsTxt` (type: `boolean`):

Check each URL against Zillow's robots.txt before fetching. The agent directory, the sales-stats endpoint and the portfolio endpoints are all permitted, so leaving this on costs you nothing. It is here so that if Zillow changes its rules, the run stops rather than carrying on.

## `maxConcurrency` (type: `integer`):

Measured across the Zillow Actors: 10 parallel is clean, 15 doubles the block rate for no gain. The portfolio endpoints are rate limited in their own right, so raising this mostly buys retries.

## `maxRequestsPerMinute` (type: `integer`):

Usually the setting that decides how long a run takes. 300 measured as the sweet spot.

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

Zillow rejects datacenter IPs. Residential US is the default and is what this Actor is priced around; changing it will most likely return nothing.

## Actor input object example

```json
{
  "locations": [
    "Austin, TX"
  ],
  "agentNames": [],
  "maxItems": 100,
  "maxPagesPerLocation": 10,
  "topAgentsOnly": false,
  "minSalesLastYear": 0,
  "includeActiveListings": true,
  "maxListingsPerAgent": 25,
  "includeRentals": false,
  "maxRentalsPerAgent": 25,
  "onlyAgentsWithListings": false,
  "enrichListings": false,
  "respectRobotsTxt": true,
  "maxConcurrency": 10,
  "maxRequestsPerMinute": 300,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

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

One row per estate agent, with their live portfolio nested inside each row.

# 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": [
        "Austin, TX"
    ],
    "agentNames": [],
    "maxItems": 100,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("studioxl/zillow-agent-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": ["Austin, TX"],
    "agentNames": [],
    "maxItems": 100,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("studioxl/zillow-agent-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": [
    "Austin, TX"
  ],
  "agentNames": [],
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call studioxl/zillow-agent-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,studioxl/zillow-agent-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/0EJF4QSYOm1H6bmaK/builds/KMSz5oaB1F46QFVef/openapi.json
