Solution · Real estate
Deduplicating Property Records: Entity Resolution for Real Estate
Real estate data is messy. The same property appears in MLS feeds as "123 Main Street, Apt 2B" and in tax records as "123 Main St #2B." Brokerages show up as "Keller Williams Realty," "Keller Williams," or just "KW Realty." Owners submit records under slight name variations or exact duplicates from different systems.
These inconsistencies make it nearly impossible to build a unified view of properties, owners, agents, and transactions without an entity resolution layer. In this post, we walk through building a real estate knowledge graph using Merge — from schema design to automated deduplication and human review.

The Problem: Fragmented Property Data
A typical real estate platform ingests data from multiple sources:
- MLS feeds with varied address formatting
- County tax records with legal entity names
- Brokerage CRMs with informal naming conventions
- Transaction databases with duplicated owner records
Without entity resolution, you end up with multiple records for the same property, the same owner counted twice, and brokerages fragmented across three different spellings. This breaks analytics, compliance reporting, and customer experience.
Designing the Schema
Merge uses typed schemas with configurable match rules. Each attribute gets a role (key, supporting, or deterministic) and a match strategy (fuzzy or exact).
We defined five entity types for our real estate graph:
# Create the Property schema
curl -X POST https://merge-mdm.com/v1/schemas \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"entity_type": "Property",
"attributes": [
{"name": "address", "type": "key", "match": "fuzzy", "weight": 0.5},
{"name": "city", "type": "supporting", "match": "fuzzy", "weight": 0.3},
{"name": "state", "type": "supporting", "match": "exact", "weight": 0.1},
{"name": "property_type", "type": "supporting", "match": "exact", "weight": 0.1},
{"name": "bedrooms", "type": "supporting", "match": "exact", "weight": 0.1},
{"name": "square_feet", "type": "supporting", "match": "exact", "weight": 0.1}
],
"auto_merge_threshold": 0.9,
"review_threshold": 0.7
}'
# Create the Owner schema
curl -X POST https://merge-mdm.com/v1/schemas \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"entity_type": "Owner",
"attributes": [
{"name": "name", "type": "key", "match": "fuzzy", "weight": 0.5},
{"name": "owner_type", "type": "supporting", "match": "exact", "weight": 0.2},
{"name": "contact_email", "type": "deterministic", "match": "exact", "weight": 1.0}
],
"auto_merge_threshold": 0.9,
"review_threshold": 0.7
}'
# Create the Agent schema
curl -X POST https://merge-mdm.com/v1/schemas \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"entity_type": "Agent",
"attributes": [
{"name": "full_name", "type": "key", "match": "fuzzy", "weight": 0.5},
{"name": "license_number", "type": "deterministic", "match": "exact", "weight": 1.0},
{"name": "brokerage", "type": "supporting", "match": "fuzzy", "weight": 0.2}
],
"auto_merge_threshold": 0.9,
"review_threshold": 0.7
}'
# Create the Brokerage schema
curl -X POST https://merge-mdm.com/v1/schemas \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"entity_type": "Brokerage",
"attributes": [
{"name": "name", "type": "key", "match": "fuzzy", "weight": 0.5},
{"name": "city", "type": "supporting", "match": "fuzzy", "weight": 0.2},
{"name": "state", "type": "supporting", "match": "exact", "weight": 0.2}
],
"auto_merge_threshold": 0.9,
"review_threshold": 0.7
}'
# Create the Transaction schema
curl -X POST https://merge-mdm.com/v1/schemas \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"entity_type": "Transaction",
"attributes": [
{"name": "price", "type": "key", "match": "exact", "weight": 0.3},
{"name": "transaction_type", "type": "supporting", "match": "exact", "weight": 0.2},
{"name": "transaction_date", "type": "supporting", "match": "exact", "weight": 0.3}
],
"auto_merge_threshold": 0.9,
"review_threshold": 0.7
}'

Schema Design Decisions
addressas fuzzy key — Street abbreviations ("St" vs "Street"), unit notation ("#2B" vs "Apt 2B"), and formatting differences are the most common source of duplicates in real estate.license_numberas deterministic — An agent's license number is a guaranteed unique identifier. If two records share the same license number, they are the same agent regardless of name spelling.contact_emailas deterministic — Same logic for owners. An email match is conclusive.- Dual thresholds — Records scoring above 0.9 merge automatically. Between 0.7 and 0.9, they enter human review. Below 0.7, they stay separate.
Defining Relationships
Entity resolution becomes far more powerful when entities are connected in a graph. We defined four relationship types:
# Property → Owner
curl -X POST https://merge-mdm.com/v1/relationships \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{"type": "owned_by", "from_entity_type": "Property", "to_entity_type": "Owner"}'
# Property → Agent
curl -X POST https://merge-mdm.com/v1/relationships \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{"type": "listed_by", "from_entity_type": "Property", "to_entity_type": "Agent"}'
# Agent → Brokerage
curl -X POST https://merge-mdm.com/v1/relationships \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{"type": "works_at", "from_entity_type": "Agent", "to_entity_type": "Brokerage"}'
# Property → Transaction
curl -X POST https://merge-mdm.com/v1/relationships \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{"type": "sold_in", "from_entity_type": "Property", "to_entity_type": "Transaction"}'
These relationships let you traverse the graph: starting from a property, you can find its owner, the listing agent, which brokerage that agent works at, and the sale transaction — all in a single query.
Ingesting Records
With schemas and relationships in place, we batch-ingested records from across our data sources. Here is an example with brokerage records that intentionally include real-world naming variations:
curl -X POST https://merge-mdm.com/v1/entities/batch \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"entity_type": "Brokerage",
"records": [
{"source_id": "brok-2a", "attributes": {"name": "Keller Williams Realty", "city": "Austin", "state": "TX"}},
{"source_id": "brok-2b", "attributes": {"name": "Keller Williams", "city": "Austin", "state": "TX"}},
{"source_id": "brok-2c", "attributes": {"name": "KW Realty", "city": "Austin", "state": "TX"}},
{"source_id": "brok-3a", "attributes": {"name": "RE/MAX", "city": "Chicago", "state": "IL"}},
{"source_id": "brok-3b", "attributes": {"name": "Remax", "city": "Chicago", "state": "IL"}},
{"source_id": "brok-3c", "attributes": {"name": "RE MAX", "city": "Chicago", "state": "IL"}}
]
}'
We also ingested agents with their brokerage relationships:
curl -X POST https://merge-mdm.com/v1/entities \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"entity_type": "Agent",
"source_id": "agt-1",
"attributes": {
"full_name": "Lisa Park",
"license_number": "NY-2019-44821",
"brokerage": "Compass"
},
"relationships": [
{"relationship_type": "works_at", "to_entity_id": "ent_5261977d-9014-447b-bfeb-f94afa452184"}
]
}'

Resolution Results
After ingesting 42 source records across all entity types, Merge's resolution engine produced the following results:
| Test Case | Input Variations | Result | Method |
|---|---|---|---|
| Keller Williams | "Keller Williams Realty", "Keller Williams", "KW Realty" | 3 → 1 entity | Auto-merge (2) + Review (1) |
| RE/MAX | "RE/MAX", "Remax", "RE MAX" | 3 → 1 entity | Auto-merge (2) + Review (1) |
| Coldwell Banker | "Coldwell Banker", "Coldwell Banker Realty" | 2 → 1 entity | Auto-merge |
| Sotheby's | "Sothebys International Realty", "Sothebys Intl Realty" | 2 → 1 entity | Auto-merge |
| Robert Johnson | Exact duplicate with same email | 2 → 1 entity | Deterministic (email) |
| 123 Main St | "123 Main Street, Apt 2B", "123 Main St #2B" | 2 separate | Below review threshold |
What Merged Automatically
The resolution engine correctly identified:
- "Keller Williams Realty" and "Keller Williams" as the same entity (0.90 confidence — fuzzy name match with supporting attribute overlap)
- "RE/MAX" and "Remax" as the same entity (0.95 confidence — the engine handles punctuation and casing normalization)
- "Coldwell Banker" and "Coldwell Banker Realty" (0.90 confidence — substring containment with matching city/state)
- "Sothebys International Realty" and "Sothebys Intl Realty" (0.90 confidence — abbreviation recognition)
- "Robert Johnson" exact duplicate merged instantly via deterministic email match (1.0 confidence)
What Went to Review
Two cases landed in the review queue between the 0.7 and 0.9 thresholds:
- "KW Realty" → Keller Williams (0.72 confidence) — The acronym "KW" matched via alias detection, but the name similarity alone was low (0.62)
- "RE MAX" → Remax (0.72 confidence) — Without the slash, the fuzzy match scored lower than the "Remax" variant

The Address Challenge
The address pair "123 Main Street, Apt 2B" vs "123 Main St #2B" did not trigger even a review. While visually similar to humans, the differences (Street→St, Apt→#, comma removal) dropped the fuzzy score below the 0.7 review threshold. This is by design — aggressive address merging risks combining genuinely different units in the same building.
Tip: For production use, consider a preprocessing step that normalizes addresses (expanding abbreviations, standardizing unit notation) before ingestion. This moves the matching burden out of the fuzzy engine and into a deterministic normalization layer.
The Review Workflow
Merge's review queue provides comparison details showing exactly why a match was flagged:
{
"confidence_score": 0.72,
"comparison_details": {
"name_sim": 0.62,
"alias_sim": 1.0,
"attr_boost": true,
"attr_overlap": 1
},
"source_attributes": {"name": "KW Realty", "city": "Austin", "state": "TX"},
"candidate": {"name": "Keller Williams", "city": "Austin", "state": "TX"}
}
The alias_sim: 1.0 tells us the engine recognized "KW" as a known acronym for "Keller Williams." Combined with full city/state overlap (attr_overlap: 1), a reviewer can confidently accept this merge.
# Accept a review
curl -X POST https://merge-mdm.com/v1/reviews/9ef63f7f-ed9d-43c5-ba88-4a9a7152fede/accept \
-H "X-API-Key: $API_KEY"

The Knowledge Graph
With resolution complete and relationships defined, entities connect into a traversable graph:
# Query the graph around Compass brokerage (2-hop traversal)
curl https://merge-mdm.com/v1/entities/ent_5261977d-9014-447b-bfeb-f94afa452184/graph?hops=2 \
-H "X-API-Key: $API_KEY"
This returns Compass connected to its agents (Lisa Park, David Kim, Thomas Wright) via works_at edges — a single query revealing the full brokerage roster.

Search and Discovery
Merge provides fuzzy search across all resolved entities:
# Search across all entity types
curl "https://merge-mdm.com/v1/entities/search?q=Robert+Johnson&entity_type=Owner" \
-H "X-API-Key: $API_KEY"
The search returns the single resolved "Robert Johnson" entity with source_count: 2, confirming both original records collapsed into one golden record.

Final Entity State
After resolution, our data consolidated from 42 source records into clean golden entities:
| Entity Type | Source Records | Resolved Entities | Reduction |
|---|---|---|---|
| Brokerage | 11 | 5 | 55% |
| Owner | 7 | 6 | 14% |
| Agent | 8 | 8 | 0% |
| Property | 11 | 10-11 | ~5% |
| Transaction | 5 | 5 | 0% |
The biggest wins are in Brokerages (55% reduction) where naming inconsistencies are rampant, and Owners where deterministic email matching catches exact duplicates instantly.

Merged Entity Detail
After accepting reviews, the Keller Williams entity now contains all three source variations unified under a single ID:

Similarly, RE/MAX consolidated "RE/MAX", "Remax", and "RE MAX" into one golden entity:

Tips for Real Estate Entity Resolution
-
Use deterministic fields when available. License numbers, tax IDs, and email addresses give you guaranteed matches regardless of name spelling.
-
Normalize addresses before ingestion. Expand abbreviations (St→Street, Ave→Avenue, Blvd→Boulevard) and standardize unit notation (#→Apt) in a preprocessing step.
-
Set conservative thresholds for properties. False merges (combining two different units) are harder to undo than false non-merges. Start with a higher auto-merge threshold (0.95) for addresses and lower it after reviewing false negatives.
-
Leverage supporting attributes. City + state overlap provides strong confirmation signals. A fuzzy name match in the same city is much more likely to be a true match than one across different states.
-
Use the review queue for acronyms and abbreviations. "KW Realty" matching "Keller Williams" is correct but hard to verify automatically. Let humans confirm these edge cases, then the system learns.
-
Connect entities via relationships early. The graph enables richer deduplication signals — if two "Robert Johnson" records own properties listed by the same agent, that's strong evidence they're the same person.
-
Batch ingest for throughput. The
/v1/entities/batchendpoint processes up to hundreds of records per call, making initial data loads fast.
Conclusion
Entity resolution transforms fragmented real estate data into a connected knowledge graph. Merge handles the hard parts — fuzzy matching, acronym detection, threshold-based routing — while keeping humans in the loop for ambiguous cases.
The combination of deterministic fields (email, license numbers) for guaranteed matches and fuzzy matching (names, addresses) for probabilistic resolution covers the full spectrum of real estate data quality issues. The result: a single source of truth for properties, owners, agents, brokerages, and transactions.
Get started at merge-mdm.com.