How every call works
Jobs and polling, credits, paging with cursors, field notes, rate limits and the response envelope, the same on every RealtyCrawl route.
Every route works the same way, whatever the source.
#Jobs
- A call answers 200 when the result is ready, including cache hits.
- Otherwise it answers 202 with
poll_url. Poll it with?wait=20untilstatusiscompleteorfailed. - Send an
Idempotency-Keyheader when you retry, so a retry is never charged twice.
#Credits
- Each route has a price in credits, shown on its page. You pay when a call completes.
- Failed calls are free. So is a page past the last one.
#Paging
- Area searches return one page at a time. Send
result.next_cursorascursorfor the next page. - A cursor only works on the route that made it, with the same inputs.
- Some sites cap how many results one search can show. We split large areas into smaller map boxes behind the cursor, so you can keep paging.
#Field notes
A field we can't fill is null, never missing. result.field_notes says why, once per field.
#Response envelope
| Field | Type | Description |
|---|---|---|
request_id | string | ID of this request. Poll it at /v1/jobs/{request_id}. |
status | string: pending, complete, failed | pending, complete or failed. Always check it, even on HTTP 200. |
credits | integer | Credits charged. The route's price when complete; 0 while pending, when failed, or for a page past the last one. |
cached | boolean | True when the result came from our cache. |
poll_url | string or null | Where to poll while pending. Null when complete. |
error | object or null | {code, message, param} when failed. Null otherwise. |
result | object or null | The result when complete. Null otherwise. |
result.source | string | Source of the rows, like zillow. |
result.query | object | The inputs you sent, as we read them. |
result.count | integer | Rows in data. |
result.reported_total | integer or null | Total matches the source reported for the whole search, when it shows one. |
result.next_cursor | string or null | Send as cursor for the next page. Null on the last page and on lookups. |
result.collected_at | string | When we read the source (ISO 8601, UTC). |
result.schema_version | integer | Version of the row schema. Changes only with a new, opt-in version. |
result.completeness | string: complete, partial | complete, or partial when the source returned less than a full page for a reason other than reaching the end. |
result.summary | object or null | Maths over this page for area searches. Null on lookups. |
result.data | array | The rows. |
result.field_notes | array | Why a field is null: {field, reason, detail}. One note covers every row where it applies. |
result.summary | object | Maths over the rows on this page: priced_count, disclosed_count, median_sale_price, p25, p75, median_price_per_sqft, earliest_sold_date, latest_sold_date. Rows without a sale price are left out of the price maths but stay in data. |
result.data[].source | string | Which source the row came from. New sources may be added, so handle values you don't know. |
result.data[].source_id | string | The source's own ID for the home or building. On Zillow, a home's ID is its zpid. |
result.data[].source_url | string | Page for this home or building on the source site. |
result.data[].sold_date | string or null | Sale date (YYYY-MM-DD). |
result.data[].sale_price | Money or null | Sale price. Null when the price is not disclosed; field_notes says why. Never filled with a list price or estimate. |
result.data[].sale_price_disclosure | string: public_record, mls_reported, not_disclosed, unknown | How the sale price is known. public_record: from county records. mls_reported: the source shows an MLS price. not_disclosed: the price is hidden, usually in a non-disclosure state. unknown: the source shows a price but not where it came from. |
result.data[].list_price | Money or null | Last asking price before the sale. Null when the source does not show it. |
result.data[].list_date | string or null | Date the sold listing started (YYYY-MM-DD). |
result.data[].sale_to_list_ratio | number or null | sale_price divided by list_price. |
result.data[].days_on_market | integer or null | Days from list_date to the first pending date, or to sold_date when there was no pending date. Null when the source does not say. |
result.data[].price_per_sqft | number or null | Sale price divided by living area. |
result.data[].beds | number or null | Bedrooms. |
result.data[].baths | number or null | Bathrooms as the source counts them. |
result.data[].living_area_sqft | integer or null | Living area in square feet. |
result.data[].property_type | string or null: single_family, condo, townhouse, multi_family, land, manufactured, apartment, other | Home type. |
result.data[].address | Address | Address. |
result.data[].location | Location or null | Map point. |
result.data[].broker_name | string or null | Listing brokerage at the time of sale. |
result.data[].extras | object | Fields only this source has. Each source lists its own on its docs page. {} when there are none. |
result.data[].extras.home_type_raw | string or null | Zillow's own home type code. |
result.data[].extras.days_on_zillow | integer or null | On sold rows, Zillow counts days since the sale here, not days on market. |
result.data[].extras.zestimate | Money or null | Zillow's Zestimate on the search card. |
result.data[].extras.rent_zestimate | Money or null | Zillow's Rent Zestimate on the search card. |
result.data[].extras.tax_assessed_value | Money or null | Zillow's "tax assessed value". |
#Headers
| Header | What it means |
|---|---|
RateLimit-Limit | Requests allowed per minute on this key. |
RateLimit-Remaining | Requests left this minute. |
RateLimit-Reset | Seconds until the minute resets. |
X-Credits-Charged | Credits this response charged. |