Zumper Scraper - Rentals, Prices, Agents & 60+ Fields
Pricing
from $1.20 / 1,000 listing results
Zumper Scraper - Rentals, Prices, Agents & 60+ Fields
Scrape Zumper.com rental listings: apartments, houses, condos and rooms. Pulls 60+ fields per listing, including monthly rent, beds/baths, square footage, decoded amenities, agent name and phone, ratings, availability and full-resolution photos. Search by location with filters, or paste Zumper URLs.
Pricing
from $1.20 / 1,000 listing results
Rating
0.0
(0)
Developer
Abot API
Maintained by CommunityActor stats
1
Bookmarked
3
Total users
1
Monthly active users
8 days ago
Last modified
Categories
Features
Share
Zumper Scraper: Rental Listings, Rent and Availability Data
Zumper Scraper turns Zumper (zumper.com), a US rental listing site, into a clean apartment, house, condo and room dataset. Search by location with rich filters, or paste your own Zumper search, building or listing links. Every record carries monthly rent, beds/baths, square footage, decoded amenities, agent name and phone, ratings, availability and full resolution photo URLs. It also monitors listings for changes across runs (NEW, UPDATED, UNCHANGED, REAPPEARED, EXPIRED), resumes an interrupted pull, and pipes results into your apps through MCP connectors.
Why This Scraper?
- 60+ fields per listing, core data straight from the results page. Price, beds/baths, square footage, amenities, agent name, phone, rating and photos come back without turning on detail fetching.
- Amenities decoded to plain text. Amenities and building features come back as readable labels ("In-Unit Laundry", "Fitness Center", "Garage Parking"), not opaque numeric codes.
- Full photo URLs, not just IDs. Every image is returned as a ready to open URL.
- Two ways to find data. Search by location and filters, or paste specific search, building or listing URLs (multiple URLs supported).
- Availability monitoring. Scheduled runs classify every listing as NEW, UPDATED, UNCHANGED, REAPPEARED or EXPIRED, with per campaign state you can name or share.
- Resume long pulls. Continue an interrupted or unbounded walk across separate runs without paying twice for what was already collected.
- Predictable cost. A small default result cap, per record output, and an optional per listing detail surcharge only when you turn detail fetching on.
Use Cases
- Rent trend and market research: track rent ranges, price drops and new listings across neighborhoods and cities over time.
- Apartment hunters and relocation services: pull filtered listings (price, beds, pets) for a target city into your own site or alert tool.
- PropTech and rental aggregators: build a secondary Zumper sourced feed with agent contacts and photos, without visiting the site by hand.
- Investors and property managers: monitor competing buildings' pricing and availability in a submarket.
- Lead generation for movers and brokers: watch specific buildings or listings for status changes (NEW, price change, EXPIRED) and reach out through the agent contact on the record.
Data You Get
Sample shape: values are illustrative placeholders, not from a live listing.
| Field | Example |
|---|---|
id | "64606115" |
url | https://www.zumper.com/apartment-buildings/p000000/sample-building-city-st |
title | "1A with Balcony" |
buildingName | "Sample Apartments" |
propertyCategory | "apartments" (also "houses", "condos", "rooms") |
address | "123 Sample St" |
city / state / zipcode | "San Francisco" / "CA" / "94103" |
neighborhood | "SOMA" |
latitude / longitude | 37.7700 / -122.4000 |
minPrice / maxPrice | 4035 / 6360 (monthly rent, USD; the same names work as rent filters in both modes) |
previousPrice | 4200 (set when the rent dropped) |
minBedrooms / maxBedrooms | 0 / 2 (0 = studio) |
minBathrooms / maxBathrooms | 1 / 2 |
minSquareFeet / maxSquareFeet | 509 / 980 |
dateAvailable | "2026-01-15" |
rating / externalRating | 9.3 / 4.1 |
unitAmenities | ["In-Unit Laundry", "Dishwasher", "Air Conditioning"] |
buildingAmenities | ["Fitness Center", "Garage Parking", "Roof Deck"] |
agentName / brokerageName / phone | "Sample Mgmt" / "Sample Brokerage" / "(415) 555-0100" |
primaryImage / images / imageCount | https://img.zumpercdn.com/000000000/1280x960 / [...] / 10 |
shortDescription | "Bright corner unit with balcony." |
scrapedAt | "2026-01-01T00:00:00.000Z" |
changeType | "NEW" (only present when incrementalMode is on, see below) |
changedFields, firstSeenAt, lastSeenAt | Incremental mode only |
With Fetch detail pages on, a record is additionally enriched with the full description, year built and floors, amenities grouped by category, pet policy, parking, deposits, fees, specials, income restrictions, lease terms, office hours, per unit and per floorplan availability, full agent and property manager contacts, a category by category rating breakdown, and Zumper's editorial resident feedback summary. Zumper does not publish individual resident reviews (per comment text with an author and date): the rating and feedback fields above are all of the review data that Zumper itself exposes, so userReviews is always present as an empty array rather than omitted.
How to Use
- Pick a mode:
search(build a URL from the fields below) orurl(paste your own Zumper URLs). - In search mode, add locations and any filters (price, beds, baths, pets); switch on Fetch detail pages if you need the fields above.
- Set Max listings (and optionally Max pages) to control run size and cost, then click Start.
- Download the dataset as JSON, CSV or Excel, or read it through the API.
Search apartments in a city:
{"mode": "search","locations": ["San Francisco, CA"],"propertyCategory": "apartments","maxListings": 20}
Search houses with filters, cheapest first, full detail:
{"mode": "search","locations": ["Austin, TX"],"propertyCategory": "houses","minPrice": 1500,"maxPrice": 6000,"minBedrooms": 2,"petsAllowed": ["dogs"],"sortBy": "price-asc","fetchDetails": true,"maxListings": 50}
Paste your own Zumper URLs (search results, building or listing):
{"mode": "url","urls": ["https://www.zumper.com/apartments-for-rent/new-york-ny","https://www.zumper.com/apartment-buildings/p000000/sample-building-san-francisco-ca"],"fetchDetails": true}
URL mode shapes. Each shape below was loaded in a real run and returned listings; the urls field is prefilled with one example of each search shape. The field's default equals the prefill, so a URL-mode run with no links scrapes those example searches (capped by Max listings); paste your own links to scrape something else. ZIP-slug links (/apartments-for-rent/94110-san-francisco-ca), state-level links (/apartments-for-rent/ca) and some neighborhood slugs such as /mission-district load but carry no listings: use the city and ZIP form or a neighborhood slug that lists results.
| Shape | Example path |
|---|---|
| City search | /apartments-for-rent/san-francisco-ca |
| Category search (houses, condos, rooms) | /houses-for-rent/austin-tx |
| Paginated search (starts at that page) | /apartments-for-rent/san-francisco-ca?page=2 |
| Filtered or sorted search | /apartments-for-rent/san-francisco-ca?price-max=3000&bed-min=1&sort=newest |
| Neighborhood search (use the neighborhood slug shown on Zumper) | /apartments-for-rent/san-francisco-ca/soma |
| City and ZIP search | /apartments-for-rent/san-francisco-ca/94110 |
| Building or listing page | /apartment-buildings/p13179/<slug>, /apartment-buildings/<id>/<slug>, /address/<street-city-zip-usa> |
Facets in a pasted URL (price-min, price-max, bed-min, bed-max, bath-min, pets, sort) win over the run level filter fields. The tracked scope for recurring updates includes the full pasted URL. A link that is not a recognised Zumper search or building page is logged with a warning and still attempted.
Run it from your code
Python:
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("abotapi/zumper-scraper").call(run_input={"mode": "search", "locations": ["San Francisco, CA"], "maxListings": 20})for listing in client.dataset(run["defaultDatasetId"]).iterate_items():print(listing["title"], listing["minPrice"], listing["city"])
JavaScript:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('abotapi/zumper-scraper').call({mode: 'search', locations: ['San Francisco, CA'], maxListings: 20,});const { items } = await client.dataset(run.defaultDatasetId).listItems();
Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.
Output views
The dataset has three views in the Console: Listings Overview (the main fields of every record), Listing Details (Fetch details only) (description, amenities, units, contacts and ratings breakdown) and Changes (Incremental mode) (changeType, changedFields, detectedAt, priceChangeAmount, priceChangePercent, leadScore and stateKey for monitoring runs). The incremental fields detectedAt, leadScore, priceChangeAmount, priceChangePercent and stateKey (the saved-search identity: your State key as manual:<text>, or the automatic auto:<hash> key) are added only to incremental rows and never count as a change themselves.
Field availability and the dedup key
Field availability can vary by region, listing type and Zumper interface changes; use id as the idempotency / dedup key across runs. Fetch detail pages defaults to off because the results page already carries the large majority of fields; turn it on when you need the full description, ratings breakdown, units or agent records.
Both modes, one filter set
minPrice, maxPrice, minBedrooms, maxBedrooms, minBathrooms and sortBy apply in both modes: in URL mode they narrow and reorder the results of a pasted search results URL after they are read, so a pasted URL can return fewer listings than the page itself shows. A pasted building or listing URL is saved as is and is never narrowed by them; each such URL returns exactly one record (its own fields, plus units / floorplanListings for unit level detail when Fetch detail pages is on). locations, propertyCategory and petsAllowed are search only: they shape the search URL this actor builds and are not re applied to results that come back.
How Max listings shapes a run
With one location or URL, Max listings simply stops the run once that many records have been pushed. With several locations, the budget is split into an equal share per location (rounded up), and each location stops at its own share, its own last page, or the site's own end of results, whichever comes first.
Known limitation: on each results page, the number of listings attempted is sized from the remaining budget BEFORE incremental mode classifies them, and every listing on the page is marked as handled either way. If already tracked, unchanged listings sort ahead of new or changed ones on a page, the attempts can be spent on suppressed rows and the new or changed listings further down that page are silently skipped for this run (the walk moves on to the next page, or ends, and never comes back), even though the run still finishes normally. This only bites in incrementalMode with a finite Max listings (the default is 20); a non incremental run is not affected, since nothing is suppressed. Skipped new listings show up on a later run instead. Workaround: set Max listings to 0 (or well above the size of the search) on recurring monitoring runs.
Resume and recurring updates
- Resume (
resumeFromRunId) continues one interrupted run: paste its run or dataset ID and the actor skips everything already collected there, so you don't pay twice. A migration or Resurrect of a failed run also checkpoints automatically and picks back up in the same run without input. - Incremental mode (
incrementalMode) is for scheduled runs over the same search. Each record is classifiedNEW,UPDATED(withchangedFields),UNCHANGED(suppressed and not billed unlessemitUnchangedis on),REAPPEAREDorEXPIRED(only after a run that scanned the whole tracked search, and only withemitExpired).stateKeynames or shares the stored state; leave it empty to derive one automatically from the location, category, filters andfetchDetails, so different searches never mix state with each other.UPDATEDmeans any output field changed exceptscrapedAtand the fields inignoreFieldsForChanges; the order of the amenity lists never counts (the photo order does, because the first photo is the cover image). Saved state is re-compared under the current ignore list, so changing it never reports every listing asUPDATED. - Ignore fields (
ignoreFieldsForChanges, last field of this section): field names, or dotted paths such asunits.statusthat reach inside every array element, that must not count as a change. They stay in the output. - Price and availability monitoring (all default off):
changeScope=priceAndStockcompares only the rent fields,dateAvailableand each unit's id, price and availability keys;compactUpdatesreturnsUPDATEDandEXPIREDrows as slim records withpriceChangesandvariantChanges(old and new). A compact row carries no detail page data, so it is not charged the detail enrichment event; every other row is billed as before.expireAfterMissedScans(default 1) holds a listing until it is missing from that many consecutive complete scans.EXPIREDmeans the listing dropped out of the results, not that it is unavailable. - Known limitation:
emitExpiredrows are not subject to Max listings. Once a run qualifies to detect EXPIRED listings, every tracked listing found gone is pushed and billed, however many that is; Max listings only bounds the live NEW/UPDATED/REAPPEARED side of the same run. Budget for this on a long running campaign, or keepemitExpiredoff unless you want the full expired batch every time it fires. - Known limitation: searching several overlapping or adjoining locations in one run can end a location's walk early. If two locations return some of the same listings (a shared boundary, a duplicate feed entry), a later location can reach a results page made up entirely of listings an earlier location already returned; its walk then reads that page as a repeat and stops, even though that location has more, never seen pages left. This both drops real listings from the run and, with
emitExpiredon, can mark a still live listing on the unscanned pages as EXPIRED. Workaround: avoid pairing locations whose inventories are likely to overlap in one run, or run them as separate campaigns (separatestateKey). - Known limitation: a
stateKeyshared or reused across a differently scoped run can produce false EXPIRED rows. This includes deliberately sharing onestateKeybetween two different location, URL or search mode filter setups (in URL mode the rent, bedroom and bathroom filters are applied after reading and do not trigger this), and pasting a search URL that starts mid pagination (for example a copied?page=3link) under astateKeythat previously tracked the whole search: the listings that a differently scoped or later starting run never re-visits are not actually gone, but a complete-looking scan of the narrower scope will still mark them EXPIRED. Workaround: letstateKeystay empty (automatic, per scope) unless you specifically want to share state, and always paste page 1 links for a tracked campaign. - A monitoring campaign that is bootstrapped from a
resumeFromRunIddataset (its first incremental run only; combining the two after a baseline exists fails fast with a clear message) does not suppress the listings it bootstrapped: on the next scheduled run each of them comes back asUPDATED, with every field listed inchangedFields, instead ofUNCHANGED. Those rows are pushed and billed like any other row (plus the detail surcharge when Fetch detail pages is on), even though you already paid for them in the resumed run; billing does not depend on the change type, so it costs the same as if they had come backNEW. After that one run they are tracked normally. - If the run is interrupted by a platform migration or resurrected while
incrementalModeis on, listings pushed before the interruption are protected from being re-pushed or re-billed in that same run, but the automatic checkpoint saves only which listings were already handled, not their incremental classification. Their change is therefore never saved to the baseline, and on the next scheduled run they are reported (and billed) again: a listing that wasNEWreadsNEWagain, and one that wasUPDATEDorREAPPEAREDreads the same way again, instead ofUNCHANGED. - Known limitation: a results page that loads but carries no readable listing data is treated as the end of that search. If Zumper changes its page layout or serves an unrecognized placeholder page, the walk stops as if the search had run out, and with
emitExpiredon, every tracked listing from that page onward is marked (and billed)EXPIREDeven though it is still live. If a monitoring run suddenly returns a large EXPIRED batch, check whether the same search still shows those listings on the site before acting on it; they come back asREAPPEAREDon the next healthy run.
Send results into your apps (MCP connectors)
Optionally pipe each scraped listing into the tools you already use (Notion, Linear, Airtable, or any Model Context Protocol server) by selecting an authorized connector in the input. Each connector receives a condensed, human readable summary per item; the complete record always stays in the dataset.
- Authorize a connector once under Apify -> Settings -> Integrations (Notion, Linear, Airtable, or Apify).
- Select it in the Pipe results into your apps input field. (If the picker is empty, you have not authorized a connector yet.)
- For Notion, also set
notionParentPageUrlto 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 | search (build a URL from the fields below) or url (use your own URLs). |
locations | array | New York, NY when empty | Search mode only. City, state or neighborhood, multiple supported. A bare ZIP code is not a Zumper search page (a warning is logged); use a city and ZIP link in URL mode instead. |
propertyCategory | string | apartments | Search mode only for the category to browse. In URL mode it only labels records when the pasted URL names no category itself. |
petsAllowed | array | (none) | Search mode only. Ask Zumper for listings that allow the selected pets (dogs, cats). |
urls | array | sample | URL mode only. Zumper search, building or listing links. |
minPrice | integer | (none) | Both modes. Minimum monthly rent (USD). |
maxPrice | integer | (none) | Both modes. Maximum monthly rent (USD). |
minBedrooms | integer | (none) | Both modes. Minimum bedrooms (0 = studio). |
maxBedrooms | integer | (none) | Both modes. Maximum bedrooms. |
minBathrooms | integer | (none) | Both modes. Minimum bathrooms. |
sortBy | string | default | Both modes. default, price-asc, price-desc, newest, rating-desc. |
fetchDetails | boolean | false | Add full description, ratings breakdown, units/floorplans, agent contacts and more. |
maxPages | integer | (none, unlimited) | Result pages walked per location or URL. Leave empty to walk every page (still bounded by Max listings). |
maxListings | integer | 20 | Stop after this many listings across all searches (0 = unlimited). |
resumeFromRunId | string | (none) | Continue one interrupted run, or bootstrap the first run of a new incremental campaign. |
incrementalMode | boolean | false | Turn on for recurring monitoring of the SAME search. |
stateKey | string | (none) | Names or shares an incremental mode state. |
emitUnchanged | boolean | false | Also return (and bill) unchanged listings. |
emitExpired | boolean | false | Also return (and bill) listings tracked before but no longer found. |
expireAfterMissedScans | integer | 1 | Consecutive complete scans a listing must be missing before it is EXPIRED. |
changeScope | string | all | all or priceAndStock (only rent, availability date and per-unit price/availability count as a change). |
compactUpdates | boolean | false | Slim UPDATED and EXPIRED rows with priceChanges and variantChanges. |
ignoreFieldsForChanges | array | (none) | Field names or dotted paths left out of change detection (incremental mode only). |
proxy | object | Residential | Proxy and connection settings; managed automatically, or supply your own proxy URLs. |
mcpConnectors | array | (none) | Optional: send a summary of each record to apps you authorized under Integrations. |
notionParentPageUrl | string | (none) | Notion connector only: page under which records are created. |
maxNotifyListings | integer | 50 | Cap on items written to each connector per run. |
Output Example
Sample shape: values are illustrative placeholders, not from a live listing.
{"id": "64606115","url": "https://www.zumper.com/apartment-buildings/p000000/sample-building-city-st","padmapperUrl": null,"title": "1A with Balcony","buildingName": "Sample Apartments","propertyCategory": "apartments","isBuilding": true,"address": "123 Sample St","city": "San Francisco","state": "CA","zipcode": "94103","neighborhood": "SOMA","latitude": 37.77,"longitude": -122.4,"minPrice": 4035,"maxPrice": 6360,"previousPrice": 4200,"currency": "USD","minBedrooms": 0,"maxBedrooms": 2,"minBathrooms": 1,"maxBathrooms": 2,"minSquareFeet": 509,"maxSquareFeet": 980,"dateAvailable": "2026-01-15","listedOn": "2025-12-01T00:00:00.000Z","modifiedOn": "2026-01-01T00:00:00.000Z","unitAmenities": ["In-Unit Laundry", "Dishwasher", "Air Conditioning"],"buildingAmenities": ["Fitness Center", "Garage Parking", "Roof Deck"],"shortDescription": "Bright corner unit with balcony.","rating": 9.3,"externalRating": 4.1,"agentName": "Sample Mgmt","brokerageName": "Sample Brokerage","phone": "(415) 555-0100","primaryImage": "https://img.zumpercdn.com/000000000/1280x960","images": ["https://img.zumpercdn.com/000000000/1280x960"],"imageCount": 1,"scrapedAt": "2026-01-01T00:00:00.000Z"}
Plan Requirement
Zumper serves a "Client Challenge" interstitial to plain HTTP clients instead of listings. This actor loads pages in a real browser, which clears it, and in testing both an Apify residential connection and an Apify datacenter connection loaded the search pages. The Proxy & connection field defaults to Apify Residential, which is included on paid Apify plans, and the actor rotates to the next available connection when one is refused. Apify free plans do not include Apify proxies. On a free plan the log shows "The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable." and the run then either returns the rows the backup connection could read or fails. If the backup connection could not load the pages, the run fails with "No results: residential proxies are unavailable on this run (Apify free plans do not include them), and the backup connection could not load the requested Zumper pages." If every connection is refused for the account, it fails with "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required." Any other run in which no page loads fails with "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search." To run on a free plan, upgrade at https://apify.com/pricing or supply your own proxy URLs in the Proxy & connection field.
FAQ
How much does it cost?
Billing has three events: the Apify actor start event once per run, apify-default-dataset-item once per listing record pushed to the dataset, and detail-enrichment once per listing that was enriched when Fetch detail pages is on (not for slim compact update rows, and not for unchanged listings that incremental mode suppresses). Nothing is charged for pages that fail or listings that are not returned. The Pricing tab shows the current rates. Use Max listings to cap the cost of a run.
Is it legal to scrape Zumper?
This actor collects only publicly available rental listing data. You are responsible for how you use it: follow Zumper's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Listing facts (rent, beds, baths, address) are generally not subject to copyright, but photos and agent supplied descriptions may carry third party rights.
Can I get only new or changed listings on a schedule?
Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then returns only new, updated and reappeared listings (plus expired ones when emitExpired is on), and unchanged ones are not billed unless you turn on emitUnchanged.
Why did my run return fewer listings than I expected?
With several locations, Max listings is split into an equal share per location, and each stops at its share or its last page, whichever comes first. Filters (price, beds, baths, pets) also narrow the result set. On a recurring, incremental run over a location with a lot of steady state (unchanged) inventory, the per page budget can also be used up on suppressed rows before reaching new ones on the same page (see "How Max listings shapes a run" above). Raise Max listings, or set it to 0, to collect more.
Why did my run finish with an empty dataset?
It depends on whether any page loaded. If pages loaded but nothing matched your search or filters (or incremental mode suppressed every unchanged listing), the run finishes SUCCEEDED with an empty dataset, the status message "No listings returned" and nothing billed. A results page that loads but carries no readable listing data is treated as the end of that search. If no page could be loaded at all (the site or the connection refused every request), the run ends FAILED with one of the connection messages quoted under Plan Requirement ("The proxy connection was refused for this account...", "No results: residential proxies are unavailable on this run...", or "Could not load any results page..."), so a refused connection is never reported as an empty success. Try a different location, widen the filters, or run it again in a few minutes; supplying your own proxy URLs in the Proxy & connection field can also help. If only some locations or listings could be read, you still get what was found.
Does emitExpired respect Max listings?
No. Max listings caps the live NEW/UPDATED/REAPPEARED side of a run; once a run qualifies to detect EXPIRED listings, every tracked listing found gone is pushed regardless of Max listings. See the known limitation under "Resume and recurring updates" above.
Can EXPIRED ever be wrong?
In specific cases, yes: overlapping or adjoining locations searched together in one run, or a stateKey that is shared or reused across a differently scoped or differently starting run (including a pasted URL that starts mid pagination), or a results page that loads without readable listing data, can make a still live listing look unscanned this run and get marked EXPIRED. See the known limitations under "Resume and recurring updates" above for the exact triggers and the workaround.
Can I use it with AI agents or MCP?
Yes. Call it from any Apify integration or MCP client, and use the Pipe results into your apps 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:
| π§© Padmapper Scraper Scrape Padmapper apartment rentals across US/CA cities: rent, beds, baths, sqft... | π Rent.com Scraper Extract rental listings from rent.com. Get comprehensive data including monthly rent... |
| π Trulia Scraper Scrape Trulia.com property listings by location, filters, or URL. Extract prices, beds... | π QuintoAndar Scraper Scrape QuintoAndar property listings across Brazil with 80+ fields, including rent, sale... |
| π StreetEasy Scraper Scrape StreetEasy sales, rentals and in-contract listings across NYC and Jersey City... | π Realtor.ca Scrape Realtor.ca property listings with prices, descriptions, images, agent and... |
π Browse all abotapi scrapers
π¬ Support
- π Found a bug or a missing field? Open a ticket on the Issues tab. 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.