# UK Property Description Parser (`automation-lab/uk-property-description-field-extractor`) Actor

Parse supplied UK property descriptions into bedrooms, property type, postcode syntax and floor area, with evidence spans and missing/conflict flags. No scraping or guessed attributes.

- **URL**: https://apify.com/automation-lab/uk-property-description-field-extractor.md
- **Developed by:** [Automation Lab](https://apify.com/automation-lab) (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.38 / 1,000 item processeds

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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## UK Property Description Parser

Turn already-held English UK listing descriptions into auditable attributes for analytics and database imports. This **property description parser** extracts explicit bedroom counts, property types, postcode syntax and floor area. It retains source evidence and flags missing or conflicting fields rather than filling them with guesses.

### What does it do?

Supply JSON description rows or comma-delimited CSV text. Each supplied data row produces one structured dataset row in the original order. Empty descriptions and duplicates are preserved. There is no portal scraping, deduplication, valuation, geocoding, address validation or inferred enrichment.

### Who is it for?

- Property analysts standardizing listing text before loading a warehouse.
- Data engineers importing recurring CSV exports into a database.
- Researchers who need traceable evidence instead of opaque generated attributes.

### Why use this parser?

Deterministic rules make outputs repeatable. Each match carries exact text and offsets so an analyst can inspect its origin. Conservative context guards suppress candidates associated with negation, possible conversions and nearby properties. No LLM, paid enrichment provider, login, API key or proxy is needed.

This is a rule-based first pass, not comprehensive language understanding. Review flags are an important part of the product, not errors to remove automatically.

### Getting started

1. Export the description column from Excel as UTF-8 CSV, or construct JSON rows.
2. Supply **one** input route: `rows` or `csv`.
3. For CSV, choose the exact description header; ID is optional.
4. Start the Actor and inspect the default dataset.
5. Review missing/conflicting fields before importing the extracted attributes.

```json
{"rows":[{"id":"sample-1","description":"Two-bedroom flat in G12 8QQ. Total floor area 800 sq ft."}]}
```

### Inputs

| Input | Meaning |
|---|---|
| rows | Objects with required string `description` and optional string `id`. Additional columns are discarded. |
| csv | CSV **text**, not a URL or file path. Header required. |
| descriptionColumn | CSV-only exact case-sensitive header, default `description`. |
| idColumn | CSV-only optional header, default `id`. Missing column produces null IDs. |
| maxItems | Global first-N output rows, default 10, range 1–200000. Zero invalid. |

Column selectors are rejected with JSON rows. The routes are mutually exclusive. Entire input, including rows beyond maxItems, is validated before any output or start charge.

### CSV handling

Quoted commas, multiline quoted fields, doubled quotes, CRLF and UTF-8 BOM are supported. Duplicate headers, malformed quoting and inconsistent column counts fail validation. Blank physical lines are skipped; rows with an empty description cell are retained. Original row IDs such as `001` remain strings.

```json
{"csv":"listing_id,notes\n001,\"Two-bedroom flat, G12 8QQ\"\n002,\"\"\n","descriptionColumn":"notes","idColumn":"listing_id"}
```

Excel binaries and remote downloads are not supported. Export spreadsheets to CSV and paste the text or send it through the API.

### Extracted fields

| Field | Behavior |
|---|---|
| rowIndex | One-based data-row position, not physical CSV line. |
| recordId | Supplied ID, or null. |
| description | Original supplied description. |
| bedrooms | Numeric or English-word explicit count; studio alone does not imply zero. |
| propertyType | Explicit type: flat/apartment, studio, house, detached/semi-detached/terraced/end-of-terrace house, bungalow, cottage, townhouse or maisonette. |
| postcode | UK syntax match, uppercased and spaced. No deliverability verification. |
| floorArea | Numeric value and original unit spelling. No unit conversion; explicit area ranges remain conflicts. |
| missingFields | Fields with no accepted candidate. |
| conflictingFields | Fields with multiple distinct accepted candidates. |
| evidence | Candidate spans, normalized values, units and exclusion marker. |
| reviewRequired | Missing, conflict or context-excluded candidate exists. |
| parserVersion | Version of extraction rules. |

### Output example

The sample above returns the following fields (evidence abbreviated here only):

```json
{
 "rowIndex":1,
 "recordId":"sample-1",
 "description":"Two-bedroom flat in G12 8QQ. Total floor area 800 sq ft.",
 "bedrooms":2,
 "propertyType":"flat",
 "postcode":"G12 8QQ",
 "floorArea":{"value":800,"unit":"sq ft."},
 "missingFields":[],
 "conflictingFields":[],
 "evidence":[{"field":"bedrooms","text":"Two-bedroom","start":0,"end":11,"value":2,"unit":null,"excluded":false}],
 "reviewRequired":false,
 "parserVersion":"1.0.0"
}
```

Dataset export supports JSON and CSV through Apify; nested evidence is easiest to inspect as JSON. Original descriptions are retained in your run storage, so avoid submitting secrets or unnecessary personal information.

### Evidence and ambiguity

Matching is case-insensitive and uses word boundaries. Counts support digits up to two characters and English words zero through twelve, optionally followed by double or single. Detached/terraced modifiers retain only their explicit wording: house is not added when absent. Count ranges such as `3-4 bedrooms` produce conflicting candidates, not a guessed count. Apartment normalizes to flat. Explicit counts are not a verification that the listing is truthful.

Spans use JavaScript UTF-16 offsets: start is inclusive and end exclusive. Repeated equivalent candidates do not conflict. Different numeric area values or distinct unit families do conflict; even plausible square-foot/metre equivalents remain unresolved. Original unit punctuation is preserved, including a matched trailing period.

Context exclusions inspect up to 90 preceding characters within a punctuation-delimited fragment. Terms indicating negation, planning, possible conversion or nearby properties suppress candidates. Room/garden/plot area is excluded unless explicitly qualified as total/internal/floor area. These conservative guards can miss or suppress valid information: inspect evidence and the original text. They do not provide semantic certainty, resolve anaphora, or distinguish every multi-property narrative.

### Limits and failure behavior

Maximum input is 4 MiB encoded JSON, 200000 rows and 10000 UTF-16 characters per description. Split larger imports into batches. CSV records are limited to 100000 characters. No promise is made that a particular 200000-row file fits the byte ceiling.

Invalid input fails before output. Empty descriptions produce missing-field flags. A billing limit can stop output early; retain run status and compare dataset counts before treating an import as complete. Failed runs can contain partial output and must not be represented as complete imports. No automatic source retries or network fallbacks are used.

### How much does it cost to parse UK property descriptions?

Pay-per-event pricing consists of a **$0.001 start fee** plus one `item` event for each delivered parsed row, including blank or unresolved descriptions. Evidence and flags have no separate charge.

| Store spend tier | Per parsed row |
|---|---:|
| FREE | $0.000736 |
| BRONZE | $0.00064 |
| SILVER | $0.0004992 |
| GOLD | $0.000384 |
| PLATINUM | $0.000384 |
| DIAMOND | $0.000384 |

At BRONZE, 10 rows are approximately $0.0074; 100 rows $0.065; 1000 rows $0.641. These are event-price estimates, not guaranteed invoices. Spend tiers reflect qualifying aggregate monthly Apify Store spend, not a private volume discount schedule for this Actor. Check the live pricing tab and set your spending limit. Platform subscription terms apply. We make no earnings or payout guarantees.

### Integrations

Use Apify dataset exports to load parsed rows into a warehouse or spreadsheet. Join on recordId and rowIndex, not on inferred addresses. Keep conflicts in a review table, and retain the full evidence JSON separately from flat CSV attributes. Schedule runs only when you provide fresh descriptions; this Actor does not discover or monitor new listings.

### API usage

Replace YOUR_TOKEN with your Apify token. Do not embed secrets in descriptions.

```bash
curl -X POST 'https://api.apify.com/v2/acts/automation-lab~uk-property-description-field-extractor/runs' \
 -H 'Authorization: Bearer YOUR_TOKEN' -H 'Content-Type: application/json' \
 -d '{"rows":[{"description":"Three-bedroom house. Floor area 90 m²."}]}'
```

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/uk-property-description-field-extractor').call({ rows: [{ description: 'Three-bedroom house. Floor area 90 m².' }] });
const page = await client.dataset(run.defaultDatasetId).listItems({ limit: 20 });
console.log(page.items);
```

```python
from apify_client import ApifyClient
import os
client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/uk-property-description-field-extractor').call(run_input={'rows': [{'description': 'Three-bedroom house. Floor area 90 m².'}]})
print(client.dataset(run['defaultDatasetId']).list_items(limit=20).items)
```

### MCP setup

Claude Code:

```bash
claude mcp add --transport http apify "https://mcp.apify.com?tools=automation-lab/uk-property-description-field-extractor"
```

Claude Desktop, Cursor, and VS Code: use your client's HTTP MCP configuration and authentication flow. Equivalent server configuration:

```json
{"mcpServers":{"apify":{"url":"https://mcp.apify.com?tools=automation-lab/uk-property-description-field-extractor"}}}
```

Discover actual tool names and argument schemas with scoped `tools/list`. The selection also adds run/data retrieval tools; it is not exactly one tool. Discovery does not authorize execution.

Example prompt: “Parse these supplied UK descriptions once; retrieve up to 20 rows and summarize missing/conflicting fields without guessing values.”

For authorized execution, start once and retain the run and storage IDs. If pending, check the same run with bounded 2/4/8-second backoff (cap 10), up to a 120-second total deadline. A client timeout is not a reason to start another run. Report pending or failed status honestly.

After success, retrieve dataset pages of 20 rows with explicit offset and fields, at most 100 rows or 64 KiB of host-inspected UTF-8 content admitted to model context. Enforce bytes host-side before injection; omit or summarize oversized rows with disclosure. Report truncation and continuation offset. If your client cannot intercept responses, do not claim a hard byte guarantee. Keep full exports outside model context.

### Legality, responsible use and privacy

Process only descriptions you are authorized to use. This tool does not grant rights to portal data. Remove unnecessary owner/contact details and secrets before submission. There is no runtime AI provider and no training on your descriptions.

Descriptions and extracted rows remain in Apify input/dataset storage under your account retention settings until you delete them. We do not automatically delete your run storage; use Apify storage controls for deletion. Failed operations send sanitized diagnostic input, exceptions and actor/build/run IDs to our private GlitchTip service for repair; secret fields and URL queries are removed and reports are retained for 30 days. Free-text descriptions may still contain personal information after sanitization: minimize before submission. No raw descriptions are logged by the parser.

### FAQ and troubleshooting

**Why is a field null?** It was absent, excluded by context, or had conflicting candidates. Inspect missingFields, conflictingFields and evidence.

**Is a postcode verified?** No. This is syntax extraction only, not postal address verification.

**Can it read Excel or scrape Rightmove?** No. Export your spreadsheet to CSV; portal scraping is outside scope.

**Why did CSV validation fail?** Check the exact description header, duplicate headers, quoting and consistent column counts. Supply CSV text, not its URL.

**Can it deduplicate?** No. Duplicate and blank-description rows are intentionally preserved.

### Related Actors and support

For optional table cleanup before parsing or validation afterward, consider our [CSV & Excel Data Quality Cleaner](https://apify.com/automation-lab/csv-excel-data-quality-cleaner). Its deduplication workflow is separate from description extraction.

For support, open the Actor's Apify issue tab with a minimal sanitized sample and expected result. Do not post private descriptions or credentials publicly. We investigate supported extraction defects; unusual prose should remain reviewable rather than silently enriched.

# Changelog

This Actor's version history is a separate document: https://apify.com/automation-lab/uk-property-description-field-extractor/changelog.md

# Actor input Schema

## `rows` (type: `array`):

JSON objects with description (required string, may be empty) and optional id (string). Other row columns are discarded. Mutually exclusive with csv; column selectors are rejected in this route.

## `csv` (type: `string`):

Comma-delimited CSV with unique case-sensitive headers. Quoted commas, multiline fields, escaped quotes and UTF-8 BOM supported. Blank physical lines skipped; empty description cells retained. Mutually exclusive with rows. Export Excel to CSV first.

## `descriptionColumn` (type: `string`):

Exact, case-sensitive CSV header holding description strings. CSV only; rejected with rows. Defaults to description; must differ from idColumn.

## `idColumn` (type: `string`):

Exact, case-sensitive optional CSV header for ID. CSV only; rejected with rows. Missing column yields null IDs; values retained as strings. Defaults to id.

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

Global first-N output rows in input order, including blanks and duplicates. Default 10; zero is invalid. Whole input validated even beyond this limit. Apify spending limit may stop output earlier; large files over 4 MiB must be split.

## Actor input object example

```json
{
  "rows": [
    {
      "id": "sample-1",
      "description": "Two-bedroom flat in G12 8QQ. Total floor area 800 sq ft."
    }
  ],
  "maxItems": 10
}
```

# Actor output Schema

## `dataset` (type: `string`):

One structured result per supplied row, in input order.

# 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 = {
    "rows": [
        {
            "id": "sample-1",
            "description": "Two-bedroom flat in G12 8QQ. Total floor area 800 sq ft."
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/uk-property-description-field-extractor").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 = { "rows": [{
            "id": "sample-1",
            "description": "Two-bedroom flat in G12 8QQ. Total floor area 800 sq ft.",
        }] }

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/uk-property-description-field-extractor").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 '{
  "rows": [
    {
      "id": "sample-1",
      "description": "Two-bedroom flat in G12 8QQ. Total floor area 800 sq ft."
    }
  ]
}' |
apify call automation-lab/uk-property-description-field-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/uk-property-description-field-extractor"
        }
    }
}
```

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/3rcZsTofGzg7s07F3/builds/eHQvQeWIi9tXrgWrk/openapi.json
