Redfin API
The TrueScrape Redfin API exposes 4 public endpoints covering Real estate agent profile, For-rent property details and For-sale property details. Every call is a GET against public, logged-out pages and returns the same unified schema as every other platform here. Calls cost 1 credit each. A cache hit is free, and a failed or empty response is never charged.
Endpoints
| Endpoint | Returns | Credits | Cacheable | Batchable |
|---|---|---|---|---|
| /v1/redfin/agent | Real estate agent profile | 1 | yes | yes |
| /v1/redfin/for-rent | For-rent property details | 1 | yes | yes |
| /v1/redfin/for-sale | For-sale property details | 1 | yes | yes |
| /v1/redfin/search | Search Redfin listing results | 1 | yes | yes |
Reference
/v1/redfin/agent1 creditcacheablebatchableReal estate agent profile
Public profile for a Redfin agent or partner agent: name, brokerage, contact and bio.
| Parameter | Type | Required | Description |
|---|---|---|---|
| url | string | yes | Redfin agent or partner-agent profile URL, e.g. https://www.redfin.com/real-estate-agents/the-home-team |
| tld | string | no | Redfin marketplace, default com. One of: com, ca |
| country_code | string | no | — |
Also accepts cache_max_age (a hit costs 0 credits) and include_raw (returns the untouched upstream payload under raw).
Returns one RealEstateAgent.
Fields
| Field | Type | Present | Description |
|---|---|---|---|
| platform | one of 32 strings | always | |
| id | string | null | always | |
| name | string | null | always | |
| url | string | null | always | |
| photoUrl | string | null | always | |
| brokerageName | string | null | always | |
| licenseNumber | string | null | always | |
| phone | string | null | always | |
| bio | string | null | always | |
| languages | string[] | always | |
| serviceAreas | string[] | always | |
| rating | number | null | always | |
| reviewCount | number | null | always | |
| salesStats | AgentStat[] | always | Sales figures the source publishes for the agent, such as deals closed or average days on market. Which figures appear varies by source. |
| listings | PropertyListing[] | always | Property listings shown on the agent's profile. |
| fetchedAt | string | always | When this record was retrieved, as an ISO 8601 timestamp. |
curl "$API/v1/redfin/agent?url=8XkPqR2nLvE&cache_max_age=7d" \
-H "x-api-key: $KEY"const query = new URLSearchParams({
url: '8XkPqR2nLvE',
cache_max_age: '7d',
});
const response = await fetch(`${API}/v1/redfin/agent?${query}`, {
headers: { 'x-api-key': KEY },
});
const body = await response.json();
if (!body.success) throw new Error(body.error.code);
// 1 credit, 0 on a cache hit
console.log(body.data, body.meta.creditsCharged);Python
import os, httpx
r = httpx.get(
f"{os.environ['API']}/v1/redfin/agent",
params={
"url": "8XkPqR2nLvE",
"cache_max_age": "7d",
},
headers={"x-api-key": os.environ["KEY"]},
timeout=30,
)
body = r.json()
if not body["success"]:
raise RuntimeError(body["error"]["code"])
print(body["data"], body["meta"]["creditsCharged"])Response codes
| 200 | Success |
| 400 | invalid_request. Not charged |
| 401 | missing_api_key / invalid_api_key / revoked_api_key. Never a billing error |
| 402 | insufficient_credits. The key is valid, the balance is not |
| 429 | daily_cap_exceeded or upstream_rate_limited. Not charged |
| 501 | not_configured. This deployment is not set up to serve this endpoint. Not charged. |
| 502 | upstream_blocked / upstream_schema_drift. Not charged |
| 504 | upstream_timeout. Not charged |
/v1/redfin/for-rent1 creditcacheablebatchableFor-rent property details
Public details for one Redfin rental: price (or starting price for a multi-unit building), specs, availability and amenities.
| Parameter | Type | Required | Description |
|---|---|---|---|
| url | string | yes | Redfin rental URL, e.g. https://www.redfin.com/CA/San-Francisco/952-Sutter-St-94109/apartment/1594243 |
| tld | string | no | Redfin marketplace, default com. One of: com, ca |
| country_code | string | no | — |
Also accepts cache_max_age (a hit costs 0 credits) and include_raw (returns the untouched upstream payload under raw).
Returns one PropertyListing.
Fields
| Field | Type | Present | Description |
|---|---|---|---|
| platform | one of 32 strings | always | |
| id | string | always | The property id on its source, such as a Zillow zpid. Always a string. |
| url | string | null | always | |
| listingType | sale | rent | null | always | |
| status | active | pending | under_offer | sold | rented | off_market | unknown | null | always | Where the listing is in its lifecycle. `pending` and `under_offer` mean a deal is in progress. `off_market` means withdrawn without a sale and may return, unlike `sold`. `unknown` means the status could not be read as one of the other values. |
| price | number | null | always | Asking price, or the rent for a rental, in `currency`. Read it together with `priceQualifier`. |
| currency | string | null | always | ISO 4217 currency code for the amounts in this record, such as USD or EUR. |
| priceQualifier | string | null | always | Wording that changes what `price` means, such as "offers over", "guide price", "per month" or "starting at". |
| pricePerArea | number | null | always | Price per unit of area, in `currency` per `areaUnit`. |
| address | Address | always | |
| latitude | number | null | always | |
| longitude | number | null | always | |
| beds | number | null | always | |
| baths | number | null | always | Number of bathrooms. Can be fractional, such as 2.5. |
| receptions | number | null | always | Number of reception rooms, a figure UK and European listings state. US listings have no equivalent, so it is null there. |
| area | number | null | always | Interior floor area, in `areaUnit`. |
| areaUnit | sqft | sqm | null | always | Unit of `area`: `sqft` for square feet, `sqm` for square metres. Null when the source gave no unit. |
| lotArea | number | null | always | Area of the plot the property stands on, in `lotAreaUnit`. |
| lotAreaUnit | sqft | sqm | null | always | Unit of `lotArea`: `sqft` for square feet, `sqm` for square metres. Null when the source gave no unit. |
| propertyType | string | null | always | Property type as the source words it, so values differ between sources. |
| yearBuilt | number | null | always | |
| description | string | null | always | |
| imageUrls | string[] | always | |
| floorPlanUrls | string[] | always | |
| agentName | string | null | always | |
| agentPhone | string | null | always | |
| brokerageName | string | null | always | The listing brokerage, or the management company for a rental building. |
| amenities | Record<string, string> | always | Further listing facts as label and value pairs, such as HOA dues, a council tax band or an energy rating. Which labels appear varies by source. |
| schools | School[] | always | |
| priceHistory | PriceEvent[] | always | Past events for the property, such as listings, price changes and sales. |
| listedAt | string | null | always | When the property was listed. Usually an ISO 8601 timestamp; some sources pass their own date text through. |
| updatedAt | string | null | always | When the source last updated the listing. Usually an ISO 8601 timestamp; some sources pass their own date text through. |
| fetchedAt | string | always | When this record was retrieved, as an ISO 8601 timestamp. |
curl "$API/v1/redfin/for-rent?url=8XkPqR2nLvE&cache_max_age=7d" \
-H "x-api-key: $KEY"const query = new URLSearchParams({
url: '8XkPqR2nLvE',
cache_max_age: '7d',
});
const response = await fetch(`${API}/v1/redfin/for-rent?${query}`, {
headers: { 'x-api-key': KEY },
});
const body = await response.json();
if (!body.success) throw new Error(body.error.code);
// 1 credit, 0 on a cache hit
console.log(body.data, body.meta.creditsCharged);Python
import os, httpx
r = httpx.get(
f"{os.environ['API']}/v1/redfin/for-rent",
params={
"url": "8XkPqR2nLvE",
"cache_max_age": "7d",
},
headers={"x-api-key": os.environ["KEY"]},
timeout=30,
)
body = r.json()
if not body["success"]:
raise RuntimeError(body["error"]["code"])
print(body["data"], body["meta"]["creditsCharged"])Response codes
| 200 | Success |
| 400 | invalid_request. Not charged |
| 401 | missing_api_key / invalid_api_key / revoked_api_key. Never a billing error |
| 402 | insufficient_credits. The key is valid, the balance is not |
| 429 | daily_cap_exceeded or upstream_rate_limited. Not charged |
| 501 | not_configured. This deployment is not set up to serve this endpoint. Not charged. |
| 502 | upstream_blocked / upstream_schema_drift. Not charged |
| 504 | upstream_timeout. Not charged |
/v1/redfin/for-sale1 creditcacheablebatchableFor-sale property details
Public details for one Redfin for-sale listing: price, specs, agent, price history and nearby schools.
| Parameter | Type | Required | Description |
|---|---|---|---|
| url | string | yes | Redfin for-sale listing URL, e.g. https://www.redfin.com/CA/San-Francisco/2040-Laguna-St-94115/unit-201/home/1642558 |
| tld | string | no | Redfin marketplace, default com. One of: com, ca |
| country_code | string | no | — |
Also accepts cache_max_age (a hit costs 0 credits) and include_raw (returns the untouched upstream payload under raw).
Returns one PropertyListing.
Fields
| Field | Type | Present | Description |
|---|---|---|---|
| platform | one of 32 strings | always | |
| id | string | always | The property id on its source, such as a Zillow zpid. Always a string. |
| url | string | null | always | |
| listingType | sale | rent | null | always | |
| status | active | pending | under_offer | sold | rented | off_market | unknown | null | always | Where the listing is in its lifecycle. `pending` and `under_offer` mean a deal is in progress. `off_market` means withdrawn without a sale and may return, unlike `sold`. `unknown` means the status could not be read as one of the other values. |
| price | number | null | always | Asking price, or the rent for a rental, in `currency`. Read it together with `priceQualifier`. |
| currency | string | null | always | ISO 4217 currency code for the amounts in this record, such as USD or EUR. |
| priceQualifier | string | null | always | Wording that changes what `price` means, such as "offers over", "guide price", "per month" or "starting at". |
| pricePerArea | number | null | always | Price per unit of area, in `currency` per `areaUnit`. |
| address | Address | always | |
| latitude | number | null | always | |
| longitude | number | null | always | |
| beds | number | null | always | |
| baths | number | null | always | Number of bathrooms. Can be fractional, such as 2.5. |
| receptions | number | null | always | Number of reception rooms, a figure UK and European listings state. US listings have no equivalent, so it is null there. |
| area | number | null | always | Interior floor area, in `areaUnit`. |
| areaUnit | sqft | sqm | null | always | Unit of `area`: `sqft` for square feet, `sqm` for square metres. Null when the source gave no unit. |
| lotArea | number | null | always | Area of the plot the property stands on, in `lotAreaUnit`. |
| lotAreaUnit | sqft | sqm | null | always | Unit of `lotArea`: `sqft` for square feet, `sqm` for square metres. Null when the source gave no unit. |
| propertyType | string | null | always | Property type as the source words it, so values differ between sources. |
| yearBuilt | number | null | always | |
| description | string | null | always | |
| imageUrls | string[] | always | |
| floorPlanUrls | string[] | always | |
| agentName | string | null | always | |
| agentPhone | string | null | always | |
| brokerageName | string | null | always | The listing brokerage, or the management company for a rental building. |
| amenities | Record<string, string> | always | Further listing facts as label and value pairs, such as HOA dues, a council tax band or an energy rating. Which labels appear varies by source. |
| schools | School[] | always | |
| priceHistory | PriceEvent[] | always | Past events for the property, such as listings, price changes and sales. |
| listedAt | string | null | always | When the property was listed. Usually an ISO 8601 timestamp; some sources pass their own date text through. |
| updatedAt | string | null | always | When the source last updated the listing. Usually an ISO 8601 timestamp; some sources pass their own date text through. |
| fetchedAt | string | always | When this record was retrieved, as an ISO 8601 timestamp. |
curl "$API/v1/redfin/for-sale?url=8XkPqR2nLvE&cache_max_age=7d" \
-H "x-api-key: $KEY"const query = new URLSearchParams({
url: '8XkPqR2nLvE',
cache_max_age: '7d',
});
const response = await fetch(`${API}/v1/redfin/for-sale?${query}`, {
headers: { 'x-api-key': KEY },
});
const body = await response.json();
if (!body.success) throw new Error(body.error.code);
// 1 credit, 0 on a cache hit
console.log(body.data, body.meta.creditsCharged);Python
import os, httpx
r = httpx.get(
f"{os.environ['API']}/v1/redfin/for-sale",
params={
"url": "8XkPqR2nLvE",
"cache_max_age": "7d",
},
headers={"x-api-key": os.environ["KEY"]},
timeout=30,
)
body = r.json()
if not body["success"]:
raise RuntimeError(body["error"]["code"])
print(body["data"], body["meta"]["creditsCharged"])Response codes
| 200 | Success |
| 400 | invalid_request. Not charged |
| 401 | missing_api_key / invalid_api_key / revoked_api_key. Never a billing error |
| 402 | insufficient_credits. The key is valid, the balance is not |
| 429 | daily_cap_exceeded or upstream_rate_limited. Not charged |
| 501 | not_configured. This deployment is not set up to serve this endpoint. Not charged. |
| 502 | upstream_blocked / upstream_schema_drift. Not charged |
| 504 | upstream_timeout. Not charged |
/v1/redfin/search1 creditcacheablebatchableSearch Redfin listing results
Public results from a Redfin search or listing-results page URL (a city, zip code or filtered search).
| Parameter | Type | Required | Description |
|---|---|---|---|
| url | string | yes | Redfin search page URL, e.g. https://www.redfin.com/zipcode/94109 |
| tld | string | no | Redfin marketplace, default com. One of: com, ca |
| country_code | string | no | — |
Also accepts cache_max_age (a hit costs 0 credits) and include_raw (returns the untouched upstream payload under raw).
Returns a list of SearchResultItem in data.items.
Fields
| Field | Type | Present | Description |
|---|---|---|---|
| platform | one of 32 strings | always | |
| position | number | null | always | Rank of the result on the page it was read from, starting at 1, in the order the source served it. |
| id | string | null | always | The listing id on its platform, such as an Amazon ASIN. Where the source shows no id, this is the result URL, or the title when there is no URL. |
| title | string | null | always | |
| url | string | null | always | |
| thumbnailUrl | string | null | always | |
| price | number | null | always | Price shown on the result, in `currency`. Where a price range is shown, the lowest price. |
| currency | string | null | always | ISO 4217 currency code for the amounts in this record, such as USD or EUR. |
| listPrice | number | null | always | The struck-through reference price shown on the result, when there is one. |
| rating | number | null | always | Average customer rating, out of 5. |
| reviewCount | number | null | always | |
| sellerName | string | null | always | |
| availability | in_stock | out_of_stock | preorder | discontinued | unknown | null | always | Stock status. `unknown` means the listing showed a status that could not be read as one of the other values. |
| badges | string[] | always | Labels shown on the result, such as Best Seller or Amazon's Choice. |
| isSponsored | boolean | null | always | True for a paid placement. False only where the source marks a result as not sponsored; a result with no sponsored label is null. |
| fetchedAt | string | always | When this record was retrieved, as an ISO 8601 timestamp. |
curl "$API/v1/redfin/search?url=8XkPqR2nLvE&cache_max_age=7d" \
-H "x-api-key: $KEY"const query = new URLSearchParams({
url: '8XkPqR2nLvE',
cache_max_age: '7d',
});
const response = await fetch(`${API}/v1/redfin/search?${query}`, {
headers: { 'x-api-key': KEY },
});
const body = await response.json();
if (!body.success) throw new Error(body.error.code);
// 1 credit, 0 on a cache hit
console.log(body.data, body.meta.creditsCharged);Python
import os, httpx
r = httpx.get(
f"{os.environ['API']}/v1/redfin/search",
params={
"url": "8XkPqR2nLvE",
"cache_max_age": "7d",
},
headers={"x-api-key": os.environ["KEY"]},
timeout=30,
)
body = r.json()
if not body["success"]:
raise RuntimeError(body["error"]["code"])
print(body["data"], body["meta"]["creditsCharged"])Response codes
| 200 | Success |
| 400 | invalid_request. Not charged |
| 401 | missing_api_key / invalid_api_key / revoked_api_key. Never a billing error |
| 402 | insufficient_credits. The key is valid, the balance is not |
| 429 | daily_cap_exceeded or upstream_rate_limited. Not charged |
| 501 | not_configured. This deployment is not set up to serve this endpoint. Not charged. |
| 502 | upstream_blocked / upstream_schema_drift. Not charged |
| 504 | upstream_timeout. Not charged |
Questions
- How much does the Redfin API cost?
- Calls cost 1 credit each. Every endpoint is cacheable, and a cache hit costs nothing. A failed request and an empty result are both free, on every endpoint.
- Does the Redfin API need a login or cookies?
- No. Every endpoint reads public, logged-out pages only — no account, no cookies, no session. An API key identifies your own TrueScrape account and nothing else.
- Can I fetch many Redfin targets in one request?
- Yes. 4 of 4 are batchable: one POST to /v1/jobs/batch takes many targets and returns a job id to poll.
- Can I watch Redfin endpoints for changes?
- Yes, 3 of 4. A subscription polls on your schedule and fires your webhook only when the content changed; the rest carry x-subscribable: false in the spec.