UK Land Registry Sold Prices Scraper
Pricing
Pay per event
UK Land Registry Sold Prices Scraper
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.
Pricing
Pay per event
Rating
0.0
(0)
Developer
DevilScrapes
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
Share
🎯 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.itemsnesting,{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
maxItemscap 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-Afterhonoured. - 🧩 Address components kept intact —
saon/localitystay 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
- Click Try for free at the top of the page.
- Fill in the input form — most fields have sensible defaults.
- Click Start. Output streams into the run's dataset.
- 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. M1 1AE. Matched against propertyAddress.postcode on the Land… |
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 | '-transactionDate' | Land Registry _sort value, e.g. -transactionDate for newest first or transactionDate 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
{"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
{"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_categorylabel 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.