UK Land Registry Sold Prices Scraper icon

Real estate

UK Land Registry Sold Prices Scraper

Flat, spreadsheet-ready HM Land Registry Price Paid Data — ISO dates, split BS7666 addresses, filtered by postcode/town/district/county/price/date — instead of the raw nested linked-data envelope.

Free Apify credit covers a first run. No credit card to try.

What this Actor 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 intactsaon/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.

Input

Paste this into the Apify Console, or send it as the run input over the API. Proxy settings are on by default; you rarely need to touch them.

FieldTypeRequiredWhat it does
postcode string no UK postcode to filter sales by, e.g. M1 1AE. Matched against `propertyAddress.postcode` on the Land Registry API.
town string no Town name to filter sales by, e.g. MANCHESTER. Matched against `propertyAddress.town`.
district string no District name to filter sales by, e.g. LEEDS. Matched against `propertyAddress.district`.
county string no County name to filter sales by, e.g. GREATER MANCHESTER. 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 Land Registry `_sort` value, e.g. -transactionDate for newest first or transactionDate for oldest first.
maxItems integer no Stop after this many sold-price rows have been collected across all pages.
{
  "postcode": "M1 1AE",
  "maxItems": 5,
  "sort": "-transactionDate",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}

Output

One row per result, schema-validated before it is written. Export JSON, CSV, Excel or XML from the run, or read it over the API.

transaction_idprice_paid_gbptransaction_dateproperty_typeestate_typenew_buildaddresspostcode

{
  "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

EventPriceWhen
Actor start$0.05Once per run, covers warm-up and proxy session setup.
Transaction scraped$0.0050Per result written to the dataset.

You pay only for results that land. Cap any run with maxTotalChargeUsd. See pricing & billing for worked examples.

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.

Ready to run it?

Open the listing on Apify, paste the input above, and watch rows land. If it ever breaks, it is our problem before it is yours.

Related Actors

Teams that run this also run