# Sold Prices Forensics — Cross-Border Comp Data API (`sian.agency/sold-prices-forensics-scraper`) Actor

Pull historical sold-price comparables across US (Zillow) and UK (Rightmove + Zoopla) in a single normalized dataset. Per-address forensic lookup, area sweep for comps, native currency declared per row. Built for appraisers, RICS surveyors, mortgage underwriters, cross-border investors.

- **URL**: https://apify.com/sian.agency/sold-prices-forensics-scraper.md
- **Developed by:** [SIÁN OÜ](https://apify.com/sian.agency) (community)
- **Categories:** Real estate, Automation, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $6.00 / 1,000 sold-price comparable extracteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).


# README

## Sold Prices Forensics — US + UK Comparable-Sales Data API 🚀

[![SIÁN Agency Store](https://img.shields.io/badge/Store-SI%C3%81N%20Agency-1AE392)](https://apify.com/sian.agency?fpr=sian) [![SIÁN-Zillow Property Scraper](https://img.shields.io/badge/SI%C3%81N-Zillow%20Property%20Scraper-1F4E79)](https://apify.com/sian.agency/zillow-property-scraper?fpr=sian) [![SIÁN-Zoopla Property Scraper](https://img.shields.io/badge/SI%C3%81N-Zoopla%20Property%20Scraper-1AE392)](https://apify.com/sian.agency/zoopla-property-scraper?fpr=sian) [![SIÁN-Realtor.com Scraper](https://img.shields.io/badge/SI%C3%81N-Realtor.com%20Scraper-D92228)](https://apify.com/sian.agency/realtor-property-scraper?fpr=sian)

#### 🎉 Sold prices across US + UK in one CSV — Zillow, Rightmove + Zoopla in a single normalized dataset
##### Built for appraisers, RICS surveyors, mortgage underwriters, AVM teams and cross-border investors who need forensic comparable-sales data with currency declared per row.

---

### 📋 Overview

**The Sold Prices Forensics Scraper turns three historical sold-price sources into one valuation-grade dataset.** Drop in a US address, a UK postcode, or a neighbourhood name and pull every recorded sale event in a normalized schema — one row per address-date-price — ready for AVM regression, Red Book appraisal, or cross-border thesis work.

Unlike consumer-grade "sold homes near me" scrapers, this actor is built for the appraisal and underwriting persona: repeat-sale rows preserved, native currency declared per row, subject-property summaries joinable by stable upstream ID, and a regulated-buyer pricing posture that passes procurement.

**Why valuation professionals choose us:**
- ✅ **Cross-border in one run**: Zillow (US) + Rightmove (UK) + Zoopla (UK) in a single dataset — no other actor on Apify Store ships more than one historical sold-price source.
- ⚡ **Forensic per-address lookup**: pinpoint mode for a single subject property's full sale history — the dataset RICS surveyors build by hand in a day.
- 🎯 **Normalized schema across 3 platforms**: every row carries the same 20 columns. No JOINs, no schema reconciliation, no per-source parsers.
- 💰 **Regulated-buyer pricing**: BRONZE $0.012/row matches the proven premium tier in this category — high enough to clear compliance, low enough that bulk runs still pay back. **FREE tier**: 25 rows per run, full feature access, no credit card.
- 💎 **Repeat-sale rows preserved**: properties with multiple historical sales (the Case-Shiller pattern) emit one row per sale, keyed by `sourceId + saleDate`.
- ✨ **Currency-explicit output**: every row tags `currency` (`USD` or `GBP`) and `country` (`us` or `uk`). You own the FX step, on your terms.

---

### ✨ Features

- 🏠 **byAddress mode (US + UK)** — per-property forensic lookup. Auto-detects the country from the address and queries the right source.
- 🗺 **byArea mode (UK)** — comparable-sales sweep over a UK neighbourhood, town, or postcode (Hampstead, SW1A, Manchester, Oxford).
- 🔁 **Repeat-sale rows** — Zoopla's historic sale series emits one row per sale event. Same property, different date, separate row.
- 📌 **Subject-address summary row** — `byAddress` mode adds a joinable summary row per resolved address carrying property attributes (UPRN, ZPID, beds, baths, tenure).
- 💱 **Native currency per row** — USD or GBP declared explicitly. No silent FX, no guessing.
- 📅 **Year-range filter** — `yearFrom` / `yearTo` bound the sale-year window across all platforms in one filter.
- 🌍 **Cross-platform deduplication signal** — `sourcePlatform` column lets you filter to one source or compare overlap.
- 🔁 **Auto-pagination** — drains UK area sweeps until exhausted, respects `maxResults` cap and FREE-tier safety net.
- 📊 **20-field normalized schema** — same shape across Zillow, Rightmove and Zoopla rows.
- 🧮 **Derived sqft for Zillow** — computes `squareFeet` from `salePrice / pricePerSquareFoot` when both values are present.

---

### 🎬 Quick Start

So simple no training is needed. Pick a search mode, drop in addresses or areas, click Run.

```bash
curl -X POST https://api.apify.com/v2/acts/sian.agency~sold-prices-forensics-scraper/runs?token=[YOUR_TOKEN] \
-H 'Content-Type: application/json' \
-d '{"searchMode": "byAddress", "addresses": ["48 Elsworthy Road, London NW3 3BU"]}'
````

***

### 🚀 Getting Started (3 Simple Steps)

#### Step 1: Pick a Search Mode

Choose **byAddress** for a per-property forensic lookup (US or UK), or **byArea** for a UK comparable-sales sweep over a neighbourhood, town, or postcode.

#### Step 2: Drop in Addresses or Areas

Paste full property addresses (one per line) for `byAddress`, or area names / postcodes for `byArea`. The country is auto-detected — US ZIP and state codes route to Zillow, UK postcodes to Zoopla and Rightmove.

#### Step 3: Click Run

One click and the actor stitches all enabled sources together. Results stream to your Apify dataset live.

**That's it! In under a minute, you'll have:**

- One row per address-year-price across Zillow, Rightmove and Zoopla
- Native currency declared on every row (USD or GBP)
- Stable upstream IDs (UPRN, ZPID, Rightmove record id) for joining repeat sales
- Property attributes (beds, baths, tenure, type) where the source publishes them
- A subject-address summary row per `byAddress` query (for joining attributes to sales)
- Export-ready data for AVM regression, Red Book appraisal, or insurance valuation

***

### 📥 Input Configuration

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| searchMode | string | Yes | `byAddress` (per-property forensic lookup, US + UK) or `byArea` (UK comparable-sales sweep) |
| platforms | array | No | Subset of `zillow`, `zoopla`, `rightmove`. Defaults to all that apply for the chosen mode |
| addresses | array | byAddress | Full property addresses to look up. Country auto-detected from address |
| areas | array | byArea | UK area names, towns, postcodes, or outcodes to sweep for comps |
| soldInYears | integer | No | Restrict Rightmove results to the last N years (1, 3, 5, 10) |
| yearFrom | integer | No | Only return sales where the sale year ≥ this value |
| yearTo | integer | No | Only return sales where the sale year ≤ this value |
| maxResults | integer | No | Total row cap across all queries and platforms (default 200, FREE tier capped at 25) |

**Example — per-address forensic lookup:**

```json
{
  "searchMode": "byAddress",
  "addresses": [
    "48 Elsworthy Road, London NW3 3BU",
    "1875 AVONDALE Circle, Jacksonville, FL 32205"
  ]
}
```

**Example — UK area sweep with year window:**

```json
{
  "searchMode": "byArea",
  "areas": ["Hampstead", "SW1A"],
  "yearFrom": 2020,
  "yearTo": 2024,
  "maxResults": 1000
}
```

***

### 📤 Output

Results are saved to the Apify dataset with **20+ fields** including the cross-platform disambiguation field `sourcePlatform`:

| Field | Type | Description |
|-------|------|-------------|
| **sourcePlatform** | string | **`zillow`, `zoopla`, or `rightmove`** — the source platform that produced this row. **Key disambiguation field for cross-platform overlap** (the same UK sale can appear in both Rightmove and Zoopla rows) |
| country | string | `us` or `uk` |
| currency | string | `USD` or `GBP` — declared explicitly, no FX applied |
| address | string | Verbatim property address from the upstream source |
| postcode | string | Postal / ZIP code (preserved where the source publishes one) |
| sourceId | string | Stable upstream property id — UPRN (Zoopla), record id (Rightmove), or ZPID (Zillow). Primary key for joining repeat sales |
| sourceUrl | string | Deep link to the source listing or history page |
| saleDate | string | ISO sale date `YYYY-MM-DD` |
| year | integer | Year of sale, derived from saleDate |
| salePrice | number | Sale price in native currency (see currency field) |
| pricePerSquareFoot | number | Price per sqft at time of sale (Zillow only — UK sources do not publish sqft historically) |
| squareFeet | integer | Floor area in sqft (Zillow rows, derived from salePrice / pricePerSquareFoot) |
| propertyType | string | Property type as classified by the source (DETACHED, FLAT, Purpose Built Flat, ...) |
| bedrooms | integer | Bedroom count |
| bathrooms | integer | Bathroom count |
| tenure | string | UK tenure — Freehold, Leasehold, Share of Freehold |
| lat | number | Latitude (when the source publishes coordinates) |
| lon | number | Longitude (when the source publishes coordinates) |
| queryInput | string | Verbatim address or area the user supplied that produced this row — lets you trace every row back to its input |
| scrapedAt | string | ISO timestamp when this row was scraped |
| \_summary | boolean | `true` for the per-address subject-property summary row (one per resolved `byAddress` query) |

**Example row:**

```json
{
  "sourcePlatform": "zoopla",
  "country": "uk",
  "currency": "GBP",
  "address": "48 Elsworthy Road, London NW3 3BU",
  "postcode": "NW3 3BU",
  "sourceId": "5189141",
  "sourceUrl": "https://www.zoopla.co.uk/property-history/48-elsworthy-road-london-nw3-3bu/",
  "saleDate": "2019-04-09",
  "year": 2019,
  "salePrice": 28500000,
  "pricePerSquareFoot": null,
  "squareFeet": null,
  "propertyType": "Detached",
  "bedrooms": 8,
  "bathrooms": 9,
  "tenure": "Freehold",
  "lat": 51.5436632,
  "lon": -0.1558471,
  "queryInput": "48 Elsworthy Road, London NW3 3BU",
  "scrapedAt": "2026-05-21T08:47:00Z"
}
```

***

### 💼 Use Cases & Examples

#### 1. Mortgage Underwriting Comparables

**Underwriters and AVM teams generating 12-month comp packs for a subject property's neighbourhood.**

**Input:** A US ZIP / state location or a UK area (Hampstead, SW1A) in `byArea` mode + a `yearFrom` window covering the trailing 12 months.
**Output:** One CSV of recent sold rows with price, beds, baths, sqft (where available), and lat/lon — currency declared per row.
**Use:** Drop directly into your AVM regression pipeline or hedonic model — no schema mapping, no FX assumptions.

#### 2. RICS Red Book Appraisal

**RICS surveyors building Red Book valuations for UK subject properties.**

**Input:** Subject property address in `byAddress` mode (e.g. `48 Elsworthy Road, London NW3 3BU`), optionally followed by the surrounding postcode area in `byArea` mode.
**Output:** Full UPRN-keyed sale history for the subject + recent area comps with property attributes.
**Use:** Replaces the day of manual Land Registry / portal scraping that traditionally precedes a Red Book valuation.

#### 3. Cross-Border Investment Thesis

**Investors comparing 10-year price velocity across US and UK markets.**

**Input:** A US area + a UK area in the same run, with `yearFrom: 2014`.
**Output:** Normalized rows from both markets in one CSV, each tagged with `currency` and `country`.
**Use:** Build the cross-border price-velocity chart your London Hampstead vs. Brooklyn DUMBO IC deck needs — without juggling five scrapers.

#### 4. AVM Model Training Data

**Data scientists training valuation models across multiple markets.**

**Input:** A list of areas + a multi-year `yearFrom`/`yearTo` window + `maxResults: 50000`.
**Output:** Bulk sold-price comparables (price, sqft where available, beds, baths, lat/lon, property type) ready for feature engineering.
**Use:** No per-source parser code, no CSV schema reconciliation — features land in the same columns.

#### 5. Insurance Reinstatement & Probate Valuation

**Insurance valuers and probate solicitors pulling historical sale data for properties no longer listed.**

**Input:** The subject property address in `byAddress` mode.
**Output:** Full historical sale series for the address (often the only reference value for executor sales and damage claims).
**Use:** Provides the legal benchmark "last recorded sale" figure that probate and reinstatement work routinely require.

#### 6. Property Price History Research

**Journalists, researchers, and market analysts tracking neighbourhood-level price evolution.**

**Input:** A neighbourhood or postcode in `byArea` mode + a multi-year window.
**Output:** Every recorded sale in the area over the window — preserves repeat sales of the same property.
**Use:** Quantify the gentrification curve, build a price index, or fact-check market commentary.

#### 7. Real Estate Comparables for Tax Appeals

**Property tax consultants assembling comp evidence for assessment appeals.**

**Input:** Subject address + surrounding area in two queries.
**Output:** Sale-price evidence at the address level + comparable-sales context.
**Use:** Standard format ready to package as exhibits for an assessment appeal filing.

***

### 🔗 Integration Examples

#### JavaScript / Node.js

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });

const run = await client.actor('sian.agency/sold-prices-forensics-scraper').call({
  searchMode: 'byArea',
  areas: ['Hampstead', 'SW1A'],
  yearFrom: 2020,
  maxResults: 500
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items[0]);
```

#### Python

```python
from apify_client import ApifyClient
client = ApifyClient('YOUR_TOKEN')

run = client.actor('sian.agency/sold-prices-forensics-scraper').call(
    run_input={
        'searchMode': 'byAddress',
        'addresses': ['48 Elsworthy Road, London NW3 3BU'],
    }
)

for item in client.dataset(run['defaultDatasetId']).iterate_items():
    print(item['sourcePlatform'], item['saleDate'], item['salePrice'], item['currency'])
```

#### cURL

```bash
curl -X POST 'https://api.apify.com/v2/acts/sian.agency~sold-prices-forensics-scraper/runs?token=YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"searchMode": "byArea", "areas": ["Hampstead"], "maxResults": 100}'
```

#### Automation Workflows (N8N / Zapier / Make)

1. **Trigger**: Schedule (weekly comp refresh) or webhook (subject-property lookup on demand)
2. **HTTP Request**: Call the actor's run-sync-get-dataset-items endpoint with your address or area list
3. **Process**: Filter by `sourcePlatform`, `year`, or `currency` as the workflow requires
4. **Action**: Push to Google Sheets, Snowflake, Airtable, your CRM, or your AVM pipeline

***

### 📊 Performance & Pricing

#### FREE Tier (Try It Now)

- **25 rows** per run — full feature access, same normalized schema, same fields
- No credit card required
- Perfect for evaluating fit before a production rollout
- Real opportunity to validate the schema against your AVM / appraisal workflow at zero cost

#### PAID Tier (Production Ready)

- **Unlimited rows** per run, capped only by your `maxResults`
- Bulk-friendly pricing: bigger runs unlock cheaper per-row pricing through SILVER → DIAMOND tiers
- Pay-per-event: you only pay for the rows you actually receive
- Native bulk auto-pagination across UK area sweeps

💰 **Regulated-buyer pricing posture** — BRONZE $0.012/row matches the proven premium tier in the sold-prices category. Bulk runs taper through SILVER (~$0.009), GOLD (~$0.006), PLATINUM (~$0.0048), DIAMOND (~$0.0036) per row.

🔗 [View current pricing on the Apify Store](https://apify.com/sian.agency/sold-prices-forensics-scraper?fpr=sian)

***

### ❓ Frequently Asked Questions

**Q: How many rows can I process in a run?**
A: FREE tier: 25 rows per run. PAID tier: unlimited, capped only by your `maxResults` input.

**Q: Why isn't Redfin included?**
A: Redfin's historical sold-prices endpoint returns redacted prices through this data path — the `price` field is returned as a redaction marker rather than a numeric value, which makes the data unusable for forensic valuation work. We chose to ship a smaller, honest v1.0 (3 reliable platforms) rather than include a fourth source that silently degrades downstream pipelines. If the redaction lifts in future, Redfin coverage is on the v1.1 backlog.

**Q: Why isn't Idealista (Spain / Italy / Portugal) included?**
A: Idealista does not expose a unit-level historical-transactions endpoint. Spanish, Italian and Portuguese public records do not publish per-property sold prices the way the UK Land Registry or US MLS systems do. There is no data source to wrap. If a historical endpoint becomes available, EU coverage is on the v1.1 backlog.

**Q: Are sale prices converted to a common currency?**
A: No — output is always in **native currency** (USD or GBP), with the `currency` column declared explicitly per row. This is a deliberate design decision: cross-border buyers (the primary audience) typically need both native and converted values, and FX-conversion conventions vary by use case (transaction-date FX vs. report-date FX vs. period-average FX). You own the conversion step on terms that match your pipeline.

**Q: Will I see the same UK sale twice (in both Rightmove and Zoopla)?**
A: Yes, by design. Both Rightmove and Zoopla source UK sold prices from the Land Registry, so the same sale event will sometimes appear in both feeds. The `sourcePlatform` column lets you filter to a single source (e.g. `sourcePlatform == 'zoopla'`) if you want one row per sale, or keep both rows to cross-check the data. Repeat-sale rows of the *same* property at different dates are kept as separate rows on purpose — that pattern is what Case-Shiller-style analysis requires.

**Q: How accurate is the data?**
A: Sale-event accuracy is bound by the upstream sources. UK rows ultimately trace back to HM Land Registry data and are authoritative. US Zillow rows reflect Zillow's price-history feed and accuracy varies by market. All rows preserve the upstream `sourceUrl` so you can spot-check anything that looks off.

**Q: Is this legal?**
A: Yes — only publicly available sale records are extracted. UK Land Registry sold-prices are public information; US Zillow price history is published on public listing pages. See our [Legal](#is-it-legal-to-scrape-data) section below.

**Q: How long does a run take?**
A: A per-address forensic lookup typically returns in 5–15 seconds. A UK area sweep paginates through the source, so runtime scales with `maxResults` (a 1000-row Hampstead sweep usually finishes inside 60 seconds).

***

### 🐛 Troubleshooting

**No rows returned for a UK address.**

- Confirm the postcode is included in the address string — Zoopla's per-address endpoint matches more reliably with the postcode present.
- Check that the address has actually recorded sales (some properties have never transacted, especially newer builds).
- Try the same property in `byArea` mode (e.g. just the postcode) — area sweeps draw from a larger feed.

**A US address returns no rows.**

- Verify the address resolves on Zillow itself — if Zillow doesn't recognise the address, the actor can't pull its price history.
- Confirm the address ends with a recognisable state code + ZIP (e.g. `..., FL 32205`).

**The same sale appears twice.**

- That's the cross-source overlap between Rightmove and Zoopla. Filter on `sourcePlatform` to keep one source, or treat duplicates as cross-validation.

**FREE tier capped before I expected.**

- FREE tier hard-caps at 25 total rows per run. For production volumes, upgrade to PAID — pricing scales with usage and bulk runs unlock SILVER → DIAMOND tier rates.

**A row has `_summary: true` and no salePrice.**

- That's the subject-address summary row that `byAddress` mode emits per resolved query. It carries property attributes (UPRN/ZPID, beds, baths, tenure) and the resolved address. Filter it out with `_summary !== true` if you only want sale events.

***

### ⚖ Is it legal to scrape data?

Our actors are ethical and do not extract any private user data, such as email addresses, gender, or location. They only extract what the property listing portals have chosen to share publicly — historical sold prices in the UK are published by HM Land Registry as a matter of public record, and US sale histories on Zillow are surfaced on public listing pages. We therefore believe that our actors, when used for ethical purposes by Apify users, are safe.

However, you should be aware that your results could contain personal data. Personal data is protected by the **GDPR** in the European Union and by other regulations around the world. You should not scrape personal data unless you have a legitimate reason to do so. If you're unsure whether your reason is legitimate, consult your lawyers.

You can also read Apify's blog post on the [legality of web scraping](https://blog.apify.com/is-web-scraping-legal/).

***

### 🛡 Trademark Disclaimer

This actor is an **independent tool** built by SIÁN Agency. It is **not affiliated with, endorsed by, or sponsored by Zillow Group, Rightmove plc, Zoopla, Redfin Corporation, or any of their subsidiaries**. "Zillow", "Rightmove", "Zoopla" and "Redfin" are trademarks of their respective owners and are used here only for nominative descriptive purposes — to identify the public data sources this actor reads. All product names, logos, and brands referenced in this documentation are the property of their respective owners.

***

### 🤝 Support

[![Telegram Support](https://img.shields.io/badge/Telegram-Support%20Group-0088cc?logo=telegram)](https://t.me/+vyh1sRE08sAxMGRi)

**Join our active support community**

- For issues or questions, open an issue on the actor's Apify page
- Check the [SIÁN Agency Store](https://apify.com/sian.agency?fpr=sian) for more real-estate automation tools
- 📧 <apify@sian-agency.online>

***

**Built by [SIÁN Agency](https://www.sian-agency.online)** | **[More Tools](https://apify.com/sian.agency?fpr=sian)**

# Actor input Schema

## `searchMode` (type: `string`):

How sold-price comparables are sourced.

• **byAddress** — per-property forensic lookup. Each input address triggers a per-address sold-price call (Zillow for US, Zoopla for UK).
• **byArea** — comparable-sales sweep over a UK neighbourhood, town, or postcode. Hits Rightmove `/sold-prices` and Zoopla `/sold-prices` for the named area; returns up to thousands of rows depending on `maxResults`. (Zillow does not expose area-level historical sweep through this actor — use byAddress for US comps.)

## `platforms` (type: `array`):

Which platforms to query. Leave empty to query all that apply for the chosen mode.

• **zillow** — US per-address sold history. byAddress only.
• **zoopla** — UK per-address sold history + UK area sweep.
• **rightmove** — UK area sweep only.

Known v1.0 limitations: Redfin returns redacted sold prices (unusable); Idealista (ES/IT/PT) does not expose historical transactions.

## `addresses` (type: `array`):

Full property addresses to look up — one per line or array entry. The country is auto-detected from the address (US ZIP / state code → Zillow; UK postcode → Zoopla). Free-text addresses without a country signal are queried against all enabled platforms.

Examples:

- `1875 AVONDALE Circle, Jacksonville, FL 32205`
- `48 Elsworthy Road, London NW3 3BU`
- `10 Downing Street, London SW1A 2AA`

## `areas` (type: `array`):

UK locations to sweep for comparable-sales — towns, neighbourhoods, postcodes, or outcodes. One per line. Each area is sent to Rightmove `/sold-prices` (resolves to a location identifier) and Zoopla `/sold-prices` (resolves via the area-slug convention).

Examples:

- `Hampstead`
- `SW1A`
- `Manchester`
- `Oxford`

## `soldInYears` (type: `integer`):

Restrict Rightmove results to sales completed in the last N years. Leave empty for full history. Allowed values: 1, 3, 5, 10. (Zoopla returns all available history regardless of this field.)

## `yearFrom` (type: `integer`):

Only return sales where the sale year is ≥ this value. Combine with `yearTo` to bound a sale-year window. Optional.

## `yearTo` (type: `integer`):

Only return sales where the sale year is ≤ this value. Optional.

## `maxResults` (type: `integer`):

Hard cap on the total number of sold-price rows pushed in this run (across all queries, all platforms). Auto-paginates until the cap is reached or the search is fully drained. FREE tier is always capped at 25 rows.

## Actor input object example

```json
{
  "searchMode": "byAddress",
  "platforms": [
    "zillow",
    "zoopla",
    "rightmove"
  ],
  "soldInYears": 5,
  "yearFrom": 2018,
  "yearTo": 2024,
  "maxResults": 200
}
```

# Actor output Schema

## `results` (type: `string`):

Normalized historical sold-price rows. One row per sale event (repeat sales preserved). Currency declared per row (USD / GBP).

## `htmlReport` (type: `string`):

HTML summary with sale-price distributions (split by currency), breakdowns by source platform / sale year / property type, and coverage metrics.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("sian.agency/sold-prices-forensics-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("sian.agency/sold-prices-forensics-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call sian.agency/sold-prices-forensics-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=sian.agency/sold-prices-forensics-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Sold Prices Forensics — Cross-Border Comp Data API",
        "description": "Pull historical sold-price comparables across US (Zillow) and UK (Rightmove + Zoopla) in a single normalized dataset. Per-address forensic lookup, area sweep for comps, native currency declared per row. Built for appraisers, RICS surveyors, mortgage underwriters, cross-border investors.",
        "version": "1.0",
        "x-build-id": "6gcbs0gFjuaLfDgGf"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/sian.agency~sold-prices-forensics-scraper/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-sian.agency-sold-prices-forensics-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/sian.agency~sold-prices-forensics-scraper/runs": {
            "post": {
                "operationId": "runs-sync-sian.agency-sold-prices-forensics-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/sian.agency~sold-prices-forensics-scraper/run-sync": {
            "post": {
                "operationId": "run-sync-sian.agency-sold-prices-forensics-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "properties": {
                    "searchMode": {
                        "title": "🧭 Search Mode",
                        "enum": [
                            "byAddress",
                            "byArea"
                        ],
                        "type": "string",
                        "description": "How sold-price comparables are sourced.\n\n• **byAddress** — per-property forensic lookup. Each input address triggers a per-address sold-price call (Zillow for US, Zoopla for UK).\n• **byArea** — comparable-sales sweep over a UK neighbourhood, town, or postcode. Hits Rightmove `/sold-prices` and Zoopla `/sold-prices` for the named area; returns up to thousands of rows depending on `maxResults`. (Zillow does not expose area-level historical sweep through this actor — use byAddress for US comps.)",
                        "default": "byAddress"
                    },
                    "platforms": {
                        "title": "🌐 Source platforms",
                        "uniqueItems": true,
                        "type": "array",
                        "description": "Which platforms to query. Leave empty to query all that apply for the chosen mode.\n\n• **zillow** — US per-address sold history. byAddress only.\n• **zoopla** — UK per-address sold history + UK area sweep.\n• **rightmove** — UK area sweep only.\n\nKnown v1.0 limitations: Redfin returns redacted sold prices (unusable); Idealista (ES/IT/PT) does not expose historical transactions.",
                        "items": {
                            "type": "string",
                            "enum": [
                                "zillow",
                                "zoopla",
                                "rightmove"
                            ],
                            "enumTitles": [
                                "Zillow (US)",
                                "Zoopla (UK)",
                                "Rightmove (UK)"
                            ]
                        },
                        "default": [
                            "zillow",
                            "zoopla",
                            "rightmove"
                        ]
                    },
                    "addresses": {
                        "title": "🏠 Addresses (byAddress mode)",
                        "uniqueItems": true,
                        "type": "array",
                        "description": "Full property addresses to look up — one per line or array entry. The country is auto-detected from the address (US ZIP / state code → Zillow; UK postcode → Zoopla). Free-text addresses without a country signal are queried against all enabled platforms.\n\nExamples:\n- `1875 AVONDALE Circle, Jacksonville, FL 32205`\n- `48 Elsworthy Road, London NW3 3BU`\n- `10 Downing Street, London SW1A 2AA`",
                        "items": {
                            "type": "string"
                        }
                    },
                    "areas": {
                        "title": "🗺 Areas (byArea mode)",
                        "uniqueItems": true,
                        "type": "array",
                        "description": "UK locations to sweep for comparable-sales — towns, neighbourhoods, postcodes, or outcodes. One per line. Each area is sent to Rightmove `/sold-prices` (resolves to a location identifier) and Zoopla `/sold-prices` (resolves via the area-slug convention).\n\nExamples:\n- `Hampstead`\n- `SW1A`\n- `Manchester`\n- `Oxford`",
                        "items": {
                            "type": "string"
                        }
                    },
                    "soldInYears": {
                        "title": "📅 Recently sold (Rightmove only)",
                        "minimum": 1,
                        "maximum": 30,
                        "type": "integer",
                        "description": "Restrict Rightmove results to sales completed in the last N years. Leave empty for full history. Allowed values: 1, 3, 5, 10. (Zoopla returns all available history regardless of this field.)"
                    },
                    "yearFrom": {
                        "title": "📅 Year filter — from",
                        "minimum": 1990,
                        "maximum": 2100,
                        "type": "integer",
                        "description": "Only return sales where the sale year is ≥ this value. Combine with `yearTo` to bound a sale-year window. Optional."
                    },
                    "yearTo": {
                        "title": "📅 Year filter — to",
                        "minimum": 1990,
                        "maximum": 2100,
                        "type": "integer",
                        "description": "Only return sales where the sale year is ≤ this value. Optional."
                    },
                    "maxResults": {
                        "title": "📊 Max results per run",
                        "minimum": 1,
                        "maximum": 100000,
                        "type": "integer",
                        "description": "Hard cap on the total number of sold-price rows pushed in this run (across all queries, all platforms). Auto-paginates until the cap is reached or the search is fully drained. FREE tier is always capped at 25 rows.",
                        "default": 200
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
