# UK Land Registry Sold Prices Scraper (`devilscrapes/uk-land-registry-sold-prices-scraper`) Actor

Pull HM Land Registry Price Paid Data — every registered property sale in England and Wales — as flat, spreadsheet-ready rows. We flatten the nested linked-data envelope, convert dates to ISO-8601, and split addresses into BS7666 components. Filter by postcode, town, district, county, price or date.

- **URL**: https://apify.com/devilscrapes/uk-land-registry-sold-prices-scraper.md
- **Developed by:** [DevilScrapes](https://apify.com/devilscrapes) (community)
- **Categories:** Real estate
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — 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

<div align="center">
  <img src=".actor/icon.svg" width="160" alt="Devil Scrapes mark" />

## UK Land Registry Sold Prices Scraper

**💰 $5.05 / 1 000 results**  ·  pay only for results  ·  no credit card to try

*We do the dirty work so your dataset stays clean.* 😈

Pull HM Land Registry Price Paid Data — every registered property sale in England and Wales — as flat, spreadsheet-ready rows. We flatten the nested linked-data envelope, convert dates to ISO-8601, and split addresses into BS7666 components. Filter by postcode, town, district, county, price or date.

</div>

***

### 🎯 What this scrapes

HM Land Registry's Price Paid Data is the official record of every residential property sale registered in England and Wales since 1995, and it is free and keyless — but the public API serves it as a deeply nested linked-data envelope: rows sit three levels down at `result.items`, categorical fields like property type and estate type arrive as `{label:[{_value:...}]}` objects instead of plain strings, and the sale date ships as a non-ISO string like `Thu, 29 Jul 2004` instead of `2004-07-29`. This Actor queries the endpoint on your behalf — filtered by postcode, town, district, county, price band, and transaction-date range — walks every page, and emits one flat row per sale: a plain price, an ISO-8601 date, flattened category labels, and the address split into its individual BS7666 components (house number, street, locality, town, district, county, postcode) plus a single-line assembled address. What normally means writing your own linked-data parser becomes a filtered dataset export.

### 🔥 What we handle for you

- 🧹 **Envelope flattening** — the linked-data `result.items` nesting, `{label:[{_value}]}` category objects, and non-ISO date strings are unpacked into a flat, typed row.
- 📄 **Full pagination** — we walk every page up to your `maxItems` cap so a broad postcode or district search doesn't stop at page one.
- 🔁 **Retries with exponential backoff** on `408 / 429 / 5xx` — up to 5 attempts per page, `Retry-After` honoured.
- 🧩 **Address components kept intact** — `saon`/`locality` stay nullable rather than silently collapsing sparse addresses into a schema-validation failure.
- 🧊 **Clean, typed dataset rows** — Pydantic-validated, ISO-8601 dates, ready for CSV/JSON/Excel export straight from the Apify Console.
- 💰 **Pay-Per-Event pricing** — you only pay for sale rows that land in your dataset. No data, no charge beyond the warm-up fee.

### 💡 Use cases

- **Comparables** — pull every recent sale on a street or postcode to price a listing or valuation.
- **Market research** — track sale-price trends for a town, district, or county over a date range.
- **Proptech data feeds** — ingest a clean, typed sold-price dataset instead of hand-rolling a linked-data parser.
- **Investor due diligence** — size a postcode's price band and turnover before making an offer.

### ⚙️ How to use it

1. Click **Try for free** at the top of the page.
2. Fill in the input form — most fields have sensible defaults.
3. Click **Start**. Output streams into the run's dataset.
4. Export from **Storage → Dataset** as JSON, CSV, or Excel — or fetch via the API.

### 📥 Input

| Field | Type | Required | Default | Notes |
|---|---|:--:|---|---|
| `postcode` | `string` | no | 'M1 1AE' | UK postcode to filter sales by, e.g. <code>M1 1AE</code>. Matched against `propertyAddress.postcode` on the Land… |
| `town` | `string` | no | '—' | Town name to filter sales by, e.g. <code>MANCHESTER</code>. Matched against `propertyAddress.town`. |
| `district` | `string` | no | '—' | District name to filter sales by, e.g. <code>LEEDS</code>. Matched against `propertyAddress.district`. |
| `county` | `string` | no | '—' | County name to filter sales by, e.g. <code>GREATER MANCHESTER</code>. Matched against `propertyAddress.county`. |
| `minPricePaid` | `integer` | no | '—' | Only include sales at or above this price, in GBP. |
| `maxPricePaid` | `integer` | no | '—' | Only include sales at or below this price, in GBP. |
| `minTransactionDate` | `string` | no | '—' | Earliest sale date to include (inclusive), ISO YYYY-MM-DD. |
| `maxTransactionDate` | `string` | no | '—' | Latest sale date to include (inclusive), ISO YYYY-MM-DD. |
| `sort` | `string` | no | '-transactionDate' | Land Registry `_sort` value, e.g. <code>-transactionDate</code> for newest first or <code>transactionDate</code> for… |
| `maxItems` | `integer` | no | 1000 | Stop after this many sold-price rows have been collected across all pages. |
| `proxyConfiguration` | `object` | no | {'useApifyProxy': True} | Apify Proxy configuration. The Land Registry endpoint is public and keyless with no anti-bot layer, but proxy rotation… |

#### Example input

```json
{
  "postcode": "M1 1AE",
  "maxItems": 5,
  "sort": "-transactionDate",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

### 📤 Output

Every row is one dataset item.

| Field | Type | Notes |
|---|---|---|
| `transaction_id` | `string` | Land Registry transaction UUID. |
| `transaction_url` | `string` | Absolute Land Registry transaction URI, from `hasTransaction`. |
| `price_paid_gbp` | `integer` | Sale price in GBP, as recorded by Land Registry. |
| `transaction_date` | `string` | Sale date, ISO-8601 `YYYY-MM-DD`, converted from Land Registry's day-name date string. |
| `property_type` | `string` | Flattened property type label, e.g. 'Flat-maisonette'. |
| `estate_type` | `string` | Flattened estate type label, e.g. 'Leasehold' or 'Freehold'. |
| `new_build` | `boolean` | Whether Land Registry recorded this sale as a new build. |
| `transaction_category` | `string` | Flattened transaction category label (standard price paid entry vs additional price paid entry). |
| `record_status` | `string` | Flattened record status label — 'Add' for a new entry, or a change/delete marker. |
| `paon` | `string` | Primary addressable object name — usually the house number or name. |
| `saon` | `string` | Secondary addressable object name — flat or unit number, when present. |
| `street` | `string` | Street name. |
| `locality` | `string` | Locality, when Land Registry recorded one. |
| `town` | `string` | Town/post-town. |
| `district` | `string` | District. |
| `county` | `string` | County. |
| `postcode` | `string` | Postcode. |
| `address` | `string` | Single-line human-readable address assembled from the individual BS7666 components. |
| `scraped_at` | `string` | When this row was recorded (ISO-8601 UTC). |

#### Example output

```json
{
  "transaction_id": "a1b2c3d4-0000-0000-0000-000000000000",
  "transaction_url": "http://landregistry.data.gov.uk/data/ppi/transaction/a1b2c3d4-0000-0000-0000-000000000000",
  "price_paid_gbp": 140950,
  "transaction_date": "2004-07-29",
  "property_type": "Flat-maisonette",
  "estate_type": "Leasehold",
  "new_build": false,
  "transaction_category": "Standard Price Paid Transaction",
  "record_status": "Add",
  "paon": "12",
  "saon": null,
  "street": "EXAMPLE STREET",
  "locality": null,
  "town": "MANCHESTER",
  "district": "MANCHESTER",
  "county": "GREATER MANCHESTER",
  "postcode": "M1 1AE",
  "address": "12 Example Street, Manchester, M1 1AE",
  "scraped_at": "2026-09-02T12:00:00+00:00"
}
```

### 💰 Pricing

Pay-Per-Event — you pay only when these events fire:

| Event | USD | What it is |
|---|---:|---|
| `actor-start` | $0.05 | One-off warm-up charge per run |
| `transaction-scraped` | $0.005 | PPE event |

Example: 1 000 results at the rates above ≈ **$5.05**. No subscription, no minimum, no card to start — Apify gives every new account $5 of free credit.

### 🚧 Limitations

- Residential Price Paid Data only — no commercial-property special handling beyond passing the `transaction_category` label through as published.
- No geocoding or lat/long enrichment — addresses are the BS7666 components Land Registry publishes, not resolved coordinates.
- Covers England and Wales only, matching HM Land Registry's own coverage — Scotland and Northern Ireland use separate registries not covered by this Actor.

### ❓ FAQ

**Is this legal?**

Yes — HM Land Registry publishes Price Paid Data under the Open Government Licence specifically for reuse, and this Actor only queries the public, keyless API endpoint Land Registry already exposes.

**Why did my search return zero rows?**

A narrow filter combination — a postcode or date range with genuinely no sales — legitimately returns zero rows. The run still succeeds; check the status message for what was searched.

**What does `saon` being empty mean?**

Most properties have no secondary addressable object (flat/unit number) — `saon` and `locality` are optional fields that Land Registry itself frequently leaves blank.

**Can I filter by multiple locations in one run?**

Each run takes one set of location filters (postcode/town/district/county). Run it multiple times, or chain runs via the Apify API, to cover multiple areas.

### 💬 Your feedback

Spotted a bug, hit a weird edge case, or need a new field? Open an
issue on the Actor's **Issues** tab on Apify Console — we ship
fixes weekly and we read every report.

***

<div align="center">

Built by **[Devil Scrapes](https://apify.com/DevilScrapes)** 😈 — a small fleet of
opinionated public-data Actors. Honest pricing, real engineering, zero fine print.

</div>

# Actor input Schema

## `postcode` (type: `string`):

UK postcode to filter sales by, e.g. <code>M1 1AE</code>. Matched against `propertyAddress.postcode` on the Land Registry API.

## `town` (type: `string`):

Town name to filter sales by, e.g. <code>MANCHESTER</code>. Matched against `propertyAddress.town`.

## `district` (type: `string`):

District name to filter sales by, e.g. <code>LEEDS</code>. Matched against `propertyAddress.district`.

## `county` (type: `string`):

County name to filter sales by, e.g. <code>GREATER MANCHESTER</code>. Matched against `propertyAddress.county`.

## `minPricePaid` (type: `integer`):

Only include sales at or above this price, in GBP.

## `maxPricePaid` (type: `integer`):

Only include sales at or below this price, in GBP.

## `minTransactionDate` (type: `string`):

Earliest sale date to include (inclusive), ISO YYYY-MM-DD.

## `maxTransactionDate` (type: `string`):

Latest sale date to include (inclusive), ISO YYYY-MM-DD.

## `sort` (type: `string`):

Land Registry `_sort` value, e.g. <code>-transactionDate</code> for newest first or <code>transactionDate</code> for oldest first.

## `maxItems` (type: `integer`):

Stop after this many sold-price rows have been collected across all pages.

## `proxyConfiguration` (type: `object`):

Apify Proxy configuration. The Land Registry endpoint is public and keyless with no anti-bot layer, but proxy rotation is kept as the fleet-standard default for polite, resilient paging.

## Actor input object example

```json
{
  "postcode": "M1 1AE",
  "sort": "-transactionDate",
  "maxItems": 1000,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `datasetItems` (type: `string`):

All dataset items as JSON.

## `datasetItemsCsv` (type: `string`):

Same data exported to CSV.

## `datasetView` (type: `string`):

Open the run dataset in the Console.

# 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 = {
    "postcode": "M1 1AE",
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("devilscrapes/uk-land-registry-sold-prices-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 = {
    "postcode": "M1 1AE",
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("devilscrapes/uk-land-registry-sold-prices-scraper").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "postcode": "M1 1AE",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call devilscrapes/uk-land-registry-sold-prices-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,devilscrapes/uk-land-registry-sold-prices-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/EeHX8N6A7xitnztYV/builds/hqnDocRNef3X44bEd/openapi.json
