Search
POST /v1/search
Cost · 1 credit per result returned (minimum 1) · failed calls are free
Sync if maxItems ≤ 50, async otherwise.
Body, option A: structured filters (recommended)
{ "filters": { "status": "for_sale", "location": "San Francisco, CA", "bbox": { "west": -122.55, "south": 37.70, "east": -122.35, "north": 37.85 }, "price": { "min": 500000, "max": 1500000 }, "beds": { "min": 2 }, "baths": { "min": 1 }, "sqft": { "min": 800 }, "yearBuilt": { "min": 1990 }, "homeTypes": ["house", "townhouse"], "daysOnZillow": "30", "hasPool": false, "hasGarage": true, "isWaterfront": false }, "extractionMethod": "PAGINATION", "maxItems": 50, "async": false}bbox is required. A free-text location/city/ZIP/neighborhood alone
(without coordinates) isn’t enough, the search engine needs a real bounding box
to render results. Requests missing bbox are rejected with 400 invalid_filters
and never hit upstream. location is still accepted as a hint that flows into the
URL’s usersSearchTerm field, but it’s optional and decorative.
How to get a bbox: open the area on zillow.com’s map and read
west,south,east,north off the map viewport in the URL, or skip the math and
paste the whole map URL into searchUrls (option B). Either way the search is
anchored to real coordinates, not a place name.
homeTypes accepts an array here (POST): any of house, condo,
townhouse, multi_family, manufactured, lot, apartment. The GET wrapper
(/v1/listings) takes the same values as a comma-separated string in
home_types, same set, different shape.
Body, option B: pre-built Zillow URL (advanced)
{ "searchUrls": [{ "url": "https://www.zillow.com/homes/for_sale/?searchQueryState=..." }]}The URL must contain a ?searchQueryState=... query parameter ,
pretty URLs like https://www.zillow.com/austin-tx/houses/ are rejected
with 400 invalid_search_url. Open the area on zillow.com, apply your
filters in the UI, then copy the resulting URL, it’ll have
searchQueryState baked into the query string.
extractionMethod
| Value | When |
|---|---|
PAGINATION (default) | Standard search; returns up to ~820 results |
MAP_MARKERS | Fastest; up to ~500 results, less data per row |
PAGINATION_WITH_ZOOM_IN | Exhaustive; recursive map-tile zoom; can return huge result sets |
PAGINATION_WITH_ZOOM_IN always runs async.
Sync 200 response
{ "data": [ { "zpid": "...", "address": "1 Main St", "addressCity": "San Francisco", "price": "$1,250,000", "unformattedPrice": 1250000, "beds": 2, "baths": 2, "area": 1100, "latLong": { "latitude": 37.78, "longitude": -122.41 }, "statusType": "FOR_SALE", "hdpData": { "homeInfo": { "homeStatus": "FOR_SALE", "daysOnZillow": 8, "...": "..." } } } ], "meta": { "count": 50 }, "request_id": "..."}Async 202 response
{ "data": { "job_id": "...", "status": "running" }, "request_id": "..." }POST /v1/search/with-details
Cost · 1 credit per search result + 1 credit per detail record (two stages billed separately) · failed calls are free
Runs a search, then enriches each zpid with the full property-detail blob, in one chained job. Always async.
Body
{ "filters": { "status": "for_sale", "bbox": { "west": -97.94, "south": 30.10, "east": -97.56, "north": 30.52 }, "beds": { "min": 3 } }, "extractionMethod": "PAGINATION", "maxItems": 200, "propertyStatus": "FOR_SALE", "extractBuildingUnits": "disabled"}202 response
{ "data": { "job_id": "...", "status": "running", "stage": "search" }, "request_id": "..." }When the job completes, GET /v1/jobs/{id}/results returns the detail rows
(not the search rows). The intermediate search dataset is discarded.
Output formats
Sync responses support ?format=csv and ?format=ndjson. See Output formats.