Skip to content

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

ValueWhen
PAGINATION (default)Standard search; returns up to ~820 results
MAP_MARKERSFastest; up to ~500 results, less data per row
PAGINATION_WITH_ZOOM_INExhaustive; 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.