Predefined Scrapers API
The Predefined Scrapers API is currently in beta. Parameters and response shapes may change in future releases. 🚧
The Predefined Scrapers API is only available for Cloud plans. Contact us for more information here.
Predefined scrapers return validated JSON from supported sites without browser code, at a fixed price per call. Discover the available scrapers, search Zillow listings, or look up an individual property.
When to use: Use this API when a scraper exists for your site. Use Smart Scrape for other single pages and BrowserQL for custom flows.
Endpoint
- List:
GET /scrapers - Run:
POST /scrapers/{scraper}/{api} - Auth:
tokenquery parameter (?token=YOUR_API_TOKEN_HERE) orAuthorization: Bearer YOUR_API_TOKEN_HERE - Content-Type:
application/jsonfor POST requests - Response:
application/json
- A Browserless API token from your account dashboard
Quickstart
Search for homes for sale in Austin, TX. Run JavaScript server-side with Node.js (not in a browser, where your token would be exposed). The Python examples use requests; install it with python -m pip install requests.
- cURL
- JavaScript
- Python
curl --request POST \
--url 'https://production-sfo.browserless.io/scrapers/zillow/search?token=YOUR_API_TOKEN_HERE' \
--header 'Content-Type: application/json' \
--data '{"params":{"query":"Austin, TX","tenure":"sale"}}'
const TOKEN = "YOUR_API_TOKEN_HERE";
const response = await fetch(
`https://production-sfo.browserless.io/scrapers/zillow/search?token=${TOKEN}`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ params: { query: "Austin, TX", tenure: "sale" } }),
},
);
const result = await response.json();
console.log(result);
TOKEN = "YOUR_API_TOKEN_HERE"
import requests
response = requests.post(
f"https://production-sfo.browserless.io/scrapers/zillow/search?token={TOKEN}",
json={"params": {"query": "Austin, TX", "tenure": "sale"}},
)
print(response.json())
Response (abridged example; listings and counts change):
{
"ok": true,
"data": {
"query": { "text": "Austin, TX", "regionId": 10221, "regionType": 6, "subType": "CITY", "label": "Austin, TX", "tenure": "sale", "currency": "USD" },
"page": 1,
"pageCount": 20,
"pageSize": 41,
"totalUnits": 5575,
"exhausted": false,
"via": "api",
"results": [
{
"kind": "home",
"zpid": "29365471",
"url": "https://www.zillow.com/homedetails/15205-Back-Of-The-Moon-St-Austin-TX-78734/29365471_zpid/",
"address": { "line": "15205 Back Of The Moon St, Austin, TX 78734", "street": "15205 Back Of The Moon St", "city": "Austin", "state": "TX", "zip": "78734", "latitude": 30.410772, "longitude": -97.917114 },
"price": { "amount": 475000, "currency": "USD", "period": "total", "text": "$475,000" },
"beds": 3,
"baths": 2,
"livingAreaSqft": 1800,
"lotArea": { "value": 9400.248, "units": "sqft" },
"homeType": "SINGLE_FAMILY",
"status": "FOR_SALE",
"statusText": "Active",
"daysOnZillow": 4,
"imgSrc": "https://photos.zillowstatic.com/fp/f2fb36cf6cea9d2eeac4b4b3f6509f16-p_e.jpg",
"brokerName": "LPT Realty LLC - Ascend Group",
"building": null
}
// …40 more results omitted.
]
},
"error": null
}
totalUnits is Zillow's count of matching listings, not Browserless billing units. This call costs 10 Browserless units, regardless of how many listings it returns.
Check both the HTTP status and ok: a scraper can return HTTP 200 with ok: false. See Error responses.
List the available scrapers
GET /scrapers is free and isn't metered. Use it to discover scraper names, supported hostnames, API names, prices, and output schemas. Examples use San Francisco; see the regional endpoints for other regions.
- cURL
- JavaScript
- Python
curl --request GET \
--url 'https://production-sfo.browserless.io/scrapers?token=YOUR_API_TOKEN_HERE'
const TOKEN = "YOUR_API_TOKEN_HERE";
const response = await fetch(
`https://production-sfo.browserless.io/scrapers?token=${TOKEN}`,
);
console.log(await response.json());
TOKEN = "YOUR_API_TOKEN_HERE"
import requests
response = requests.get(
f"https://production-sfo.browserless.io/scrapers?token={TOKEN}"
)
print(response.json())
Response (catalog v1.2.0 excerpt; schemas and deployment-specific metadata omitted):
{
"version": "1.2.0",
"contractVersion": 1,
// previousVersion and swap metadata omitted.
"scrapers": {
"zillow": {
"hostnames": ["zillow.com", "www.zillow.com"],
"apis": {
"search": { "ttlSeconds": 1800, "units": 10, "outputSchema": { /* … */ } },
"property": { "ttlSeconds": 21600, "units": 10, "outputSchema": { /* … */ } }
}
},
"example": {
"hostnames": ["example.com", "www.example.com"],
"apis": {
"page-info": { "ttlSeconds": 86400, "units": 1, "outputSchema": { /* … */ } }
}
}
}
}
units is the current price per call, and outputSchema is the JSON Schema every successful result is validated against. ttlSeconds is how long a result is considered fresh. The catalog also includes previousVersion (a string or null) and swap metadata: inProgress, targetVersion, startedAt, lastOutcome, and lastAt.
Zillow search
Send either params.query or a Zillow place url to POST /scrapers/zillow/search. A listing URL belongs to Zillow property, not search.
Request body
| Parameter | Type | Default | Description |
|---|---|---|---|
url | string | — | Alternative to params.query: a Zillow place URL such as /homes/<slug>_rb/, /<city>-<st>/, a rentals URL, or a Zillow URL containing ?searchQueryState=. Only zillow.com and www.zillow.com are supported. |
params.query | string | — | Place text, for example Austin, TX. Required when you don't supply a place URL. |
params.tenure | string | sale | sale or rent. A value that contradicts the URL, such as sale with a rentals URL, returns invalid-input. |
params.page | integer | 1 | Page from 1 through 24. |
params.sort | string | Site default | Sale: pricea, priced, days, beds, baths, size, lot, priorityscore, zest, zesta. Rent: paymenta, paymentd, days, beds, baths, size, lot, priorityscore. |
params.priceMin, params.priceMax | number | Unset | Minimum and maximum price. For rent, these bound monthly rent. |
params.bedsMin, params.bedsMax | number | Unset | Minimum and maximum bedrooms. |
params.bathsMin, params.bathsMax | number | Unset | Minimum and maximum bathrooms. |
params.sqftMin, params.sqftMax | number | Unset | Minimum and maximum living area in square feet. |
params.yearBuiltMin, params.yearBuiltMax | number | Unset | Earliest and latest construction year. |
params.homeTypes | string | Unset | Comma-separated house, condo, townhouse, apartment, multifamily, manufactured, land. Listing one switches the others off. |
params.daysOnZillow | string | Unset | One of 1, 7, 14, 30, 90, 6m, 12m, 24m, 36m. |
params.keywords | string | Unset | 1–200 printable characters. |
For all scraper requests, url is limited to 8,192 characters. params accepts at most 64 keys, with keys up to 128 characters and values up to 4,096 characters. Values must be strings, numbers, or booleans, not nested objects or arrays.
Response fields
Successful responses have ok: true, the following fields under data, and error: null.
| Field | Description |
|---|---|
query | Resolved place: text, regionId, regionType, subType, label, tenure, and currency (USD or CAD). |
page, pageCount, pageSize | Current page, Zillow's displayed page count (capped at 20), and page size (41). Pages 21–24 can still return results. |
totalUnits | Number of matching listings, not a charge. |
exhausted | true when the requested page is past the real end and has no results. |
via | api or browser, indicating which extraction path succeeded. |
results[] | Matching homes or buildings. |
results[].kind, zpid, url | kind is home or building; zpid is a string identifier; url links to Zillow. |
results[].address | line, street, city, state, zip, latitude, and longitude. |
results[].price | amount, currency, period, and display text. |
results[].beds, baths, livingAreaSqft, lotArea | Property dimensions. lotArea contains value and units. |
results[].homeType, status, statusText, daysOnZillow | Property type, listing status, and listing age. |
results[].imgSrc, brokerName, building | Photo URL, brokerage, and building information. building is null for a home result. |
Filter rentals
Find rentals with at least two bedrooms and monthly rent no higher than $2,500, sorted by ascending payment:
curl --request POST \
--url 'https://production-sfo.browserless.io/scrapers/zillow/search?token=YOUR_API_TOKEN_HERE' \
--header 'Content-Type: application/json' \
--data '{"params":{"query":"Austin, TX","tenure":"rent","bedsMin":2,"priceMax":2500,"sort":"paymenta"}}'
Response uses the search envelope above, with data.query.tenure set to rent. Prices describe rent rather than a sale price; counts and listings depend on current availability.
Zillow property
Call POST /scrapers/zillow/property with a property ID or a Zillow listing URL. Building URLs aren't accepted.
| Parameter | Type | Default | Description |
|---|---|---|---|
params.zpid | string or integer | — | Positive property ID as a digit-only string or integer, at most 2,147,483,647. Use a zpid from a search result. |
url | string | — | Alternative to params.zpid: https://www.zillow.com/homedetails/…/<zpid>_zpid/. |
curl --request POST \
--url 'https://production-sfo.browserless.io/scrapers/zillow/property?token=YOUR_API_TOKEN_HERE' \
--header 'Content-Type: application/json' \
--data '{"params":{"zpid":29365471}}'
Response (excerpt; values change as the listing changes):
{
"ok": true,
"data": {
"identity": { "zpid": "29365471", "url": "https://www.zillow.com/homedetails/15205-Back-Of-The-Moon-St-Austin-TX-78734/29365471_zpid/", "homeStatus": "FOR_SALE", "homeType": "SINGLE_FAMILY", "tenure": "sale", "country": "USA", "currency": "USD", "buildingId": null, "providerListingId": null },
"address": { "street": "15205 Back Of The Moon St", "city": "Austin", "state": "TX", "zip": "78734", "neighborhood": null, "subdivision": "Travis Landing", "county": "Travis County", "latitude": 30.410772, "longitude": -97.917114, "parcelId": "156788" },
"price": { "list": 475000, "zestimate": null, "rentZestimate": null, "lastSold": null, "listLow": null, "baseRent": null, "pricePerSqft": 264, "hoaFeeText": "$166 annually", "taxAssessedValue": 362528 },
// facts, listing, history, schools, photos, media, and rental omitted.
"via": "api"
},
"error": null
}
| Group | Description |
|---|---|
identity | Property and provider identifiers, URL, status, home type, country, currency, and tenure (sale, rent, or other). The returned zpid is a string. |
address | Street, city, state, ZIP, neighborhood, subdivision, county, coordinates, and parcel ID. Unlike search results, it has no line field. |
price | Listing price, estimates, last sale, rent, price per square foot, HOA text, and assessed value. Unavailable values can be null. |
facts, listing, history | Property characteristics, listing information, and historical records. |
schools[], photos[] | School information and property photos. |
media, rental | Additional media and rental details. |
via | api or browser. |
Use the catalog's outputSchema for the full field definitions.
Pricing
A call is charged the API's fixed units only when it returns HTTP 200 with ok: true or error.code: "not-found"; every other outcome is free, including timeouts and 429s. No browser-time, proxy, or CAPTCHA units are added, and the charge appears under Time units on your account dashboard.
| API | Units per charged call |
|---|---|
zillow.search | 10 |
zillow.property | 10 |
example.page-info | 1 |
GET /scrapers always returns the current price for every API. See Unit consumption for your allowance and usage tracking.
Testing cheaply
Use example.page-info to check authentication and response handling for 1 unit. It accepts only example.com or www.example.com URLs and returns title and heading; heading uses the first paragraph if the page has no heading.
curl --request POST --url 'https://production-sfo.browserless.io/scrapers/example/page-info?token=YOUR_API_TOKEN_HERE' --header 'Content-Type: application/json' --data '{"url":"https://example.com/"}'
Response contains ok: true, data.title: "Example Domain", data.heading with the page's heading or first paragraph, and error: null.
Timeouts and concurrency
Set the timeout query parameter in milliseconds, for example &timeout=90000. Without it, your plan's maximum session time applies, capped at the server ceiling of 30 minutes. Values above the server ceiling are clamped, but a value above your plan's maximum session time returns HTTP 400 rather than extending the plan limit. Zero, negative, and non-numeric values also return 400.
Allow at least 90,000 ms for Zillow, and keep your HTTP client's timeout longer than the API timeout. The API path usually finishes in a few seconds, but a blocked request can fall back to a stealth browser and take about 90 seconds.
Scraper calls count toward your plan's concurrency limit like any other request, for as long as the call is in flight. Above the limit you get 429 Too Many Requests; it costs nothing, so retry with backoff.
Error responses
Scraper errors return HTTP 200 with ok: false; host and platform failures use HTTP error statuses. Check the HTTP status first, then inspect error.retryable only if the body contains a scraper error envelope. Use backoff for transient failures.
| Status | Code | Charged | What it means / what to do |
|---|---|---|---|
| 200 | not-found | Yes | The lookup ran but no property or region matched. Check the query or property ID; retryable: false. |
| 200 | invalid-input | No | The scraper rejected an input. Correct the field named in message; values aren't echoed. Retryable: false. |
| 200 | blocked | No | Bot protection defeated the API path and browser fallback. details.stage is api, rotated, or browser. Retryable: true. |
| 200 | upstream-changed | No | The site's page or API changed. A scraper update is needed; retryable: false. |
| 200 | upstream-error | No | The site or network failed. Retryable: true. |
| 400 | unknown-scraper / unknown-api | No | Check the path against GET /scrapers. Retryable: false. |
| 400 | input-not-cloneable | No | The body couldn't be handed to the scraper. Correct its structure; retryable: false. |
| 408 | job-timeout | No | The job exceeded timeout. Increase it within your plan limit; retryable: true. |
| 429 | Too Many Requests (platform response) | No | Your plan's concurrency limit is reached, as for every endpoint. Wait for an in-flight request to finish, then retry. This response doesn't use the scraper envelope. |
| 429 | overloaded | No | Rare: the region is at scraper capacity. Retry with backoff; retryable: true. |
| 503 | bundle-unavailable / worker-crashed / job-drained | No | Rare: the scraper runtime is restarting or being updated. Retry with backoff; retryable: true. |
| 500 | invalid-result | No | Output failed schema validation. Report the error; retryable: false. |
| 401 | Unauthorized (platform response) | No | Supply a valid API token. This response doesn't use the scraper envelope. |
For example, a missing property returns HTTP 200 with this envelope (message is abbreviated):
{
"ok": false,
"data": null,
"error": {
"code": "not-found",
"message": "…",
"retryable": false,
"status": null,
"details": null
}
}
error.status and error.details carry additional context when available; they aren't the HTTP response status. Invalid timeout parameters can also produce a plain HTTP 400 platform response, so don't assume every error body is JSON.
FAQ & Troubleshooting
Why am I getting 401 Unauthorized?
Pass a valid API token in ?token= or Authorization: Bearer. Check your token in the account dashboard; don't put it in the JSON body.
I got not-found and was still charged. Why?
The scraper completed the lookup and found no match. That definitive answer costs the same fixed units as a successful lookup. Other error outcomes are free.
I sent a Zillow listing URL to search and got invalid-input
Send /homedetails/…_zpid/ URLs to zillow/property. zillow/search takes a place query or place URL, not an individual listing.
Why did a request take 40+ seconds?
Allow at least 90 seconds for Zillow. When the site's API path is blocked, the scraper can fall back to a stealth browser. A successful result's via field tells you which path returned it; the price doesn't increase for the browser fallback.
What should I do about 429 Too Many Requests?
Your account is at its plan concurrency limit; scraper calls count toward it while they run. The response costs nothing; retry with backoff.
Next steps
See the OpenAPI reference for complete parameter details.