# UK Property Sale Comps (`zinin/uk-property-sale-comps`) Actor

Retrieve bounded HM Land Registry residential sales for permitted residential-price-information use only.

- **URL**: https://apify.com/zinin/uk-property-sale-comps.md
- **Developed by:** [Tim Zinin](https://apify.com/zinin) (community)
- **Categories:** Real estate
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.85 / 1,000 comparable sale delivereds

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/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

## UK Property Sale Comps

Turn one to five exact England and Wales postcodes into a bounded Dataset of factual residential sales from HM Land Registry Price Paid Data. The Actor validates every fixed-source response before delivery, keeps the official attribution on every row, and never presents the result as a valuation or complete market coverage.

![Exact postcodes become validated HM Land Registry sale rows](https://api.apify.com/v2/key-value-stores/sBU83NsTQIHFtCLh6/records/store-sale-comps-workflow.png)

### What you get

- Transaction date and integer GBP price from the official Price Paid Data record.
- Full postcode plus optional PAON/SAON, street, town, district, and county facts.
- Property type, estate type, new-build flag, and transaction category.
- Stable transaction URI, query and row fingerprints, source counts, and explicit truncation flags.
- Exact HM Land Registry/Open Government Licence attribution and the permitted address-use boundary in every row.

The Actor returns zero to 100 rows in deterministic sale-date order. It is not a valuation: it does not estimate value, identify an owner or resident, enrich a contact, find listings, or claim that a bounded result is every relevant sale.

### Who it is for

This Actor fits property-market researchers, residential price-information services, analysts, local-content teams, and automation builders who need a small, attributable comparable-sales input. It is designed for factual residential price context, not direct-mail prospecting, identity inference, credit or insurance decisions, tenancy screening, or an automated valuation model.

### Quick start

Run public Task `find-uk-property-sale-comps` with a full postcode and inclusive date window:

```json
{
  "postcodes": ["PL6 8RU"],
  "soldFrom": "2020-01-01",
  "soldTo": "2026-08-31",
  "propertyTypes": [],
  "newBuild": "ANY",
  "maxResults": 25,
  "includePaonSaon": true
}
```

The example asks for at most 25 rows. At FREE-tier prices, set `maxTotalChargeUsd` to `$0.038`: one `$0.013` Actor Start plus at most 25 delivered rows at `$0.001` each.

### Input reference

| Field | Required | Meaning |
| --- | --- | --- |
| `postcodes` | Yes | One to five unique full England/Wales postcodes. Whitespace and case are normalized to canonical form. |
| `soldFrom` | Yes | First transaction date, inclusive, as a real Gregorian `YYYY-MM-DD` date. |
| `soldTo` | Yes | Last transaction date, inclusive; it must not precede `soldFrom`. |
| `propertyTypes` | Yes | Zero or more of `DETACHED`, `SEMI_DETACHED`, `TERRACED`, `FLAT_MAISONETTE`, `OTHER`; empty means all. |
| `newBuild` | Yes | `ANY`, `YES`, or `NO`. |
| `maxResults` | Yes | Maximum complete rows across all requested postcodes, from 1 to 100. |
| `includePaonSaon` | Yes | Include PAON/SAON address-object facts when the source supplies them. |

Unknown fields, malformed or duplicate postcodes, impossible dates, unsafe characters, duplicate filters, and out-of-range limits fail as `input_invalid` before the source is contacted. Input cannot select a URL, query, proxy, browser, model, or credential.

### How it works

1. The Actor validates and normalizes the complete closed input.
2. For each postcode, it builds one fixed SPARQL query to `https://landregistry.data.gov.uk/landregistry/query`; only validated postcode/date literals vary.
3. It makes one fixed request per postcode with a 20-second timeout and a 1 MiB response limit. A transport failure or `429`/selected `5xx` status gets one bounded retry after 750 ms; every other response fails without retry.
4. Every response must be identity-encoded SPARQL Results JSON with the exact expected variables, datatypes, official identifiers, and no more than 101 bindings.
5. Duplicate transactions are collapsed only when all retained facts agree. Local filters, deterministic ordering, `maxResults`, fingerprints, and truncation flags are applied after every source response validates.
6. The Actor checks the complete selected batch against row and batch size limits, then stores it in the default Dataset.
7. In Pay Per Event runs, `result-found` is requested only after that complete Dataset write succeeds. A failed delivery never creates a named result charge.

There is no browser, proxy, geocoder, portal scrape, arbitrary SPARQL, redirect following, AI model, or downstream contact action. Source retry is limited to the one transient-failure retry described above.

### Output Dataset

![Factual comparable-sale fields, rights boundary, and paid delivery order](https://api.apify.com/v2/key-value-stores/sBU83NsTQIHFtCLh6/records/store-sale-comps-dataset-rights.png)

The default Dataset contains only complete factual sale rows. This field-complete example is based on the shipped bounded source fixture and illustrates the contract; it is not a live valuation or a claim that this transaction is currently representative:

```json
{
  "schemaVersion": "1.0",
  "source": "hm-land-registry-price-paid-data",
  "sourceEndpoint": "https://landregistry.data.gov.uk/landregistry/query",
  "sourceDocumentationUrl": "https://www.gov.uk/government/statistical-data-sets/price-paid-data-downloads",
  "sourceAttribution": "Contains HM Land Registry data © Crown copyright and database right 2021. This data is licensed under the Open Government Licence v3.0.",
  "rightsBoundary": "CONDITIONAL_RESIDENTIAL_PRICE_INFORMATION_ONLY",
  "rightsNotice": "Address fields are supplied only for personal/non-commercial use or display in residential property price information services unless separately licensed; no direct marketing, contact enrichment, identity inference, lead generation, or credit, insurance, or tenancy decisions.",
  "securityNotice": "Fixed official HM Land Registry endpoint and closed SPARQL template only; buyer input cannot select a URL, query, proxy, browser, or LLM.",
  "transactionId": "3B5CE841-9457-EE3A-E063-4804A8C0D6F1",
  "transactionUri": "http://landregistry.data.gov.uk/data/ppi/transaction/3B5CE841-9457-EE3A-E063-4804A8C0D6F1/current",
  "transactionDate": "2025-06-30",
  "priceGbp": 217750,
  "postcode": "PL6 8RU",
  "paon": "104",
  "saon": null,
  "street": "PATTINSON DRIVE",
  "town": "PLYMOUTH",
  "district": "CITY OF PLYMOUTH",
  "county": "CITY OF PLYMOUTH",
  "propertyType": "SEMI_DETACHED",
  "estateType": "FREEHOLD",
  "newBuild": false,
  "transactionCategory": "STANDARD_PRICE_PAID",
  "requestPostcode": "PL6 8RU",
  "requestSoldFrom": "2020-01-01",
  "requestSoldTo": "2026-08-31",
  "queryFingerprint": "fe1bbb433fa4840255062f4b5b29af793b058ed991a1db212a861d378e53a2ad",
  "rowFingerprint": "efbab00b1a5864bb008727797c96780ce9e3560372895de4f3325820a17db633",
  "sourcePostcodeBindingCount": 1,
  "sourcePostcodeTruncated": false,
  "resultPosition": 1,
  "resultCount": 1,
  "resultSetTruncated": false
}
```

### Field interpretation

- `transactionUri` and `transactionId` identify the HM Land Registry Price Paid Data transaction, not a current listing or a person.
- `priceGbp` is the recorded price paid for the transaction; it is not an asking price, present value, forecast, or appraisal.
- `propertyType`, `estateType`, `newBuild`, and `transactionCategory` are closed mappings of source facts. The Actor does not infer missing categories.
- `sourcePostcodeBindingCount` describes the validated response for that requested postcode before global selection.
- `sourcePostcodeTruncated=true` means the fixed query reached its 101-binding sentinel.
- `resultSetTruncated=true` means the source sentinel or the buyer's `maxResults` prevented the delivered set from being represented as complete.
- `queryFingerprint` identifies the exact fixed query; `rowFingerprint` identifies the normalized delivered facts. Neither is a source signature or proof of legal authenticity.
- An empty Dataset means every source response validated but no row matched the declared filters. It does not prove that no relevant sale exists.

### Pricing

The Actor uses Pay Per Event with only the automatic Actor Start and the primary `result-found` event. One `result-found` corresponds to one complete comparable-sale row already stored in the default Dataset.

| Tier | Actor start | Each delivered sale | 25-row Task maximum |
| --- | ---: | ---: | ---: |
| FREE | $0.013000 | $0.001000 | $0.038000 |
| BRONZE | $0.012350 | $0.000950 | $0.036100 |
| SILVER | $0.011700 | $0.000900 | $0.034200 |
| GOLD | $0.011050 | $0.000850 | $0.032300 |
| PLATINUM | $0.010660 | $0.000820 | $0.031160 |
| DIAMOND | $0.010400 | $0.000800 | $0.030400 |

Cost is `Actor start + delivered rows × row price`. The hard 100-row maximum costs at most `$0.113` on FREE pricing. Invalid input, source failure, failed Dataset delivery, and zero-result runs create no `result-found`; the automatic start charge still applies.

Before any source request, a paid run verifies its active event prices, prior counters, and capacity for the requested maximum batch. It fails closed instead of starting work that the run budget cannot fund. After delivery it validates the charge receipt and counter; inspect the original Run and Dataset before retrying an ambiguous client timeout.

### API and automation

Start the public Task and wait for a terminal status. Replace `USERNAME` and keep the token outside source control and shared logs:

```bash
curl -X POST \
  "https://api.apify.com/v2/actor-tasks/USERNAME~find-uk-property-sale-comps/runs?token=YOUR_APIFY_TOKEN&waitForFinish=240" \
  -H "Content-Type: application/json" \
  --data '{"postcodes":["PL6 8RU"],"soldFrom":"2020-01-01","soldTo":"2026-08-31","propertyTypes":[],"newBuild":"ANY","maxResults":25,"includePaonSaon":true}'
```

After a `SUCCEEDED` Run, read `defaultDatasetId` from the Run object and fetch its rows:

```bash
curl "https://api.apify.com/v2/datasets/DEFAULT_DATASET_ID/items?clean=1&format=json&token=YOUR_APIFY_TOKEN"
```

In **Make**, use Apify's “Run a Task” module, select `find-uk-property-sale-comps`, wait for completion, then use “Get dataset items”. Branch on terminal status and `resultSetTruncated` before publishing or aggregating results.

In **n8n**, use an HTTP Request node for the Task run endpoint, verify `status === "SUCCEEDED"`, then request the returned default Dataset. Preserve `transactionId`, `queryFingerprint`, and `rowFingerprint` as idempotency/provenance keys. Do not automatically retry a timeout: a paid Dataset batch may already exist even if the initiating client disconnected.

### Source rights and attribution

The fixed source is [HM Land Registry Price Paid Data](https://www.gov.uk/government/statistical-data-sets/price-paid-data-downloads), queried through its official linked-data endpoint. HM Land Registry permits commercial and non-commercial reuse of Price Paid Data under the Open Government Licence v3.0, subject to third-party address-data rights.

Every row includes the required statement: “Contains HM Land Registry data © Crown copyright and database right 2021. This data is licensed under the Open Government Licence v3.0.”

Royal Mail and Ordnance Survey permit the included address fields for personal/non-commercial use or display in residential property price information services. Other address-data use requires separate permission from Royal Mail. This Actor therefore limits its sellable scope to residential price information and explicitly prohibits direct marketing, contact enrichment, identity inference, lead generation, and credit, insurance, or tenancy decisions.

### Security and data handling

- Runtime permission is Apify `LIMITED_PERMISSIONS`; the Actor does not call another Actor or an AI service.
- Buyer input cannot select the host, path, query syntax, protocol, proxy, browser, or redirect target.
- Postcodes and dates pass closed validation before interpolation into a fixed SPARQL template.
- Source responses use fatal UTF-8 decoding, strict datatypes and official identifiers, bounded streamed bodies, no followed redirects, and at most one transient-failure retry.
- Retained address text rejects controls, format characters, and HTML-like markup; spreadsheet-formula prefixes are neutralized.
- The runtime logs only safe aggregate counts and status codes, not input postcodes, address facts, source bodies, or arbitrary errors.
- Apify stores run INPUT in the default Key-Value Store and successful rows in the default Dataset under account permissions and retention settings. Submit only the declared postcode/date/filter fields.

### Hard limits

| Boundary | Limit |
| --- | ---: |
| Postcodes per run | 1–5 |
| Source requests | 1–5 |
| Automatic retries / followed redirects | At most 1 / 0 |
| Timeout per source request | 20 seconds |
| Response body per postcode | 1,048,576 bytes |
| Combined source bodies | 5,242,880 bytes |
| Source bindings per postcode | 101 sentinel rows |
| Delivered rows | 0–100 |
| Encoded row / complete batch | 32,768 / 3,276,801 bytes |
| Actor memory / timeout | 128 MB / 240 seconds |

### Limits and interpretation

- Price Paid Data excludes transactions that have not been lodged with HM Land Registry and sales that were not for value; source records can be corrected later.
- The fixed query reads only the first 101 ordered bindings per postcode. Always inspect both truncation fields before treating the result as a bounded sample.
- A postcode/date sample is not proof of neighbourhood comparability. Property condition, floor area, lease terms, tenure details, transaction circumstances, and current market conditions are outside the output.
- Address fields are property-related source data, not evidence of current occupancy, ownership, identity, or consent to contact.
- `includePaonSaon=false` suppresses those two Dataset fields but does not widen the permitted-use boundary or change the other returned address facts.
- No result should be used alone for valuation, planning, legal, title, credit, insurance, tenancy, or investment decisions.

### Troubleshooting

**Why did the run report `input_invalid`?**

Use complete England/Wales postcodes, real inclusive dates, unique closed filters, a 1–100 result limit, and no unknown keys. `soldFrom` must not follow `soldTo`.

**Why did the run report `source_request_failed` or `source_status_invalid`?**

The fixed HM Land Registry endpoint remained unavailable after its single bounded transient retry or returned another non-success response. Run again only after checking the original Run state.

**Why did the run report `source_shape_invalid`?**

The response did not match the closed factual contract, contained an unsupported source value, or included conflicting duplicate transactions. The complete run is withheld instead of guessing.

**Why did the run report `source_too_large` or `output_too_large`?**

A streamed source response or complete selected Dataset batch exceeded its fixed byte ceiling. Lowering `maxResults` may reduce output but cannot reduce the already bounded source response.

**Why did the run report `budget_insufficient` or `pricing_misconfigured`?**

The run cap could not fund the selected maximum rows, or active event pricing did not match the delivery contract. Increase `maxTotalChargeUsd` or lower `maxResults`; no Dataset delivery starts for this rejection.

**Why is the Dataset empty?**

No validated source transaction matched the input. The automatic Actor-start charge can still apply, but no `result-found` is charged.

### FAQ

**Does this Actor scrape Rightmove, Zoopla, or estate-agent listings?** No. It makes only fixed queries to official HM Land Registry Price Paid Data.

**Does it return a valuation or recommended offer?** No. It returns factual recorded transactions and explicit sampling limits.

**Can I search an address, owner, resident, or arbitrary radius?** No. Input is limited to one to five exact postcodes and fixed factual filters.

**Can I use the rows for direct mail or contact enrichment?** No. That is outside the address-data permission and this product's contract.

**Is every run charged for 25 rows?** No. The named event is charged only for rows actually delivered; the Task cap merely funds the maximum. Actor Start is automatic.

**Can zero results succeed?** Yes. A fully validated zero-result run succeeds with an empty Dataset and no `result-found` charge.

# Actor input Schema

## `postcodes` (type: `array`):

One to five exact full England/Wales postcodes. Only these literals enter a fixed SPARQL template; no URL, query, proxy, browser, or LLM can be selected.

## `soldFrom` (type: `string`):

First sale date, inclusive, as a real Gregorian YYYY-MM-DD date.

## `soldTo` (type: `string`):

Last sale date, inclusive, as a real Gregorian YYYY-MM-DD date.

## `propertyTypes` (type: `array`):

Optional closed property-type subset. Empty means all documented types.

## `newBuild` (type: `string`):

Include any sales, new builds only, or non-new-build sales only.

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

Deliver at most one to 100 complete factual sale rows after every source response validates. Under pay-per-event pricing, each safely persisted row maps to one result-found event.

## `includePaonSaon` (type: `boolean`):

Include PAON/SAON object-name facts when present. This never authorizes direct marketing, contact enrichment, identity inference, lead generation, or eligibility decisions.

## Actor input object example

```json
{
  "postcodes": [
    "PL6 8RU"
  ],
  "soldFrom": "2022-01-01",
  "soldTo": "2026-08-31",
  "propertyTypes": [],
  "newBuild": "ANY",
  "maxResults": 25,
  "includePaonSaon": true
}
```

# Actor output Schema

## `comparableSales` (type: `string`):

HM Land Registry rows with OGL attribution, conditional address-use boundary, and fixed-source security notice; a truncated result is not complete coverage or a valuation.

# 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 = {
    "postcodes": [
        "PL6 8RU"
    ],
    "soldFrom": "2022-01-01",
    "soldTo": "2026-08-31",
    "propertyTypes": [],
    "newBuild": "ANY",
    "maxResults": 25,
    "includePaonSaon": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("zinin/uk-property-sale-comps").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 = {
    "postcodes": ["PL6 8RU"],
    "soldFrom": "2022-01-01",
    "soldTo": "2026-08-31",
    "propertyTypes": [],
    "newBuild": "ANY",
    "maxResults": 25,
    "includePaonSaon": True,
}

# Run the Actor and wait for it to finish
run = client.actor("zinin/uk-property-sale-comps").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 '{
  "postcodes": [
    "PL6 8RU"
  ],
  "soldFrom": "2022-01-01",
  "soldTo": "2026-08-31",
  "propertyTypes": [],
  "newBuild": "ANY",
  "maxResults": 25,
  "includePaonSaon": true
}' |
apify call zinin/uk-property-sale-comps --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,zinin/uk-property-sale-comps"
        }
    }
}

```

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/MabRaLQRrRGgiZRgI/builds/BXoKGd1KQpKgzArnS/openapi.json
