UK House Price Growth by Postcode: Tracking Capital Appreciation via API
How to pull 1Y, 3Y, 5Y, and 10Y capital appreciation for any UK outcode — with Python code to rank postcodes by appreciation, time-adjust historic valuations, and calculate LTV movement for mortgage clients.
Why postcode-level price growth matters
National house price indices — Halifax, Nationwide, ONS — tell you that UK house prices rose X% last year. They don't tell you whether your postcode did better or worse. A flat in Bristol city centre and a terrace in a pit-village commuter belt have almost nothing in common as investments, even in the same region.
Outcode-level price growth data bridges that gap. An outcode is the first part of a UK postcode: SW1A, LS1, M4. It covers a tight geographic cluster — typically a few streets to a few square kilometres — which tracks micro-market trends that national indices smooth out completely.
For investors, mortgage brokers, and proptech developers, this matters:
- Investors use capital appreciation data to compare locations before committing — 5Y growth rate is more relevant than last month's index.
- Mortgage advisors use 1Y and 3Y growth to help clients understand loan-to-value movement since purchase.
- Lenders use price growth trends to assess collateral risk on existing mortgage books.
- Proptech platforms surface growth charts on property listings to increase time-on-page and conversion.
The /api/price-growth/{outcode}/ endpoint
Homedata's price growth endpoint returns capital appreciation at four time horizons for any UK outcode:
curl https://api.homedata.co.uk/api/price-growth/SW1A/ \
-H "Authorization: Api-Key YOUR_API_KEY"
Response:
{
"outcode": "SW1A",
"median_price": 875000,
"transaction_count": 142,
"growth_1y": 3.2,
"growth_3y": 11.4,
"growth_5y": 22.8,
"growth_10y": 61.3,
"period": "2026-Q1"
}
All growth figures are percentage change over the period. growth_5y: 22.8 means the median sale price in this outcode is 22.8% higher than it was five years ago. The calculation uses median sold prices from HM Land Registry Price Paid Data — actual completed transactions, not asking prices.
Fetching price growth for a single property
The simplest use case: you have a property's postcode and want to show how the local market has performed:
import requests
API_KEY = "your_api_key_here"
def get_price_growth(postcode: str) -> dict:
"""Fetch price growth data for a postcode's outcode."""
outcode = postcode.strip().upper().split()[0] # 'SW1A 2AA' → 'SW1A'
resp = requests.get(
f"https://api.homedata.co.uk/api/price-growth/{outcode}/",
headers={"Authorization": f"Api-Key {API_KEY}"},
timeout=10
)
resp.raise_for_status()
return resp.json()
data = get_price_growth("SW1A 2AA")
print(f"1Y: {data['growth_1y']:+.1f}%")
print(f"3Y: {data['growth_3y']:+.1f}%")
print(f"5Y: {data['growth_5y']:+.1f}%")
print(f"10Y: {data['growth_10y']:+.1f}%")
print(f"Median price: £{data['median_price']:,}")
Ranking postcodes by 5-year appreciation
The real power comes when you query multiple outcodes and compare. This is useful for investment screening — finding areas with the strongest long-term appreciation trajectory:
import requests
from concurrent.futures import ThreadPoolExecutor
API_KEY = "your_api_key_here"
BASE = "https://api.homedata.co.uk/api"
# A shortlist of outcodes to compare
OUTCODES = ["E1", "E2", "E3", "N1", "N4", "N7", "N16", "SE1", "SE15", "SW9"]
def fetch_growth(outcode: str) -> dict | None:
"""Fetch 5Y growth for one outcode. Returns None on error."""
try:
r = requests.get(
f"{BASE}/price-growth/{outcode}/",
headers={"Authorization": f"Api-Key {API_KEY}"},
timeout=10
)
if r.status_code == 200:
return r.json()
except requests.RequestException:
pass
return None
# Fetch all outcodes in parallel (polite: 5 workers)
with ThreadPoolExecutor(max_workers=5) as pool:
results = list(pool.map(fetch_growth, OUTCODES))
# Filter failures and sort by 5Y growth descending
ranked = sorted(
[r for r in results if r],
key=lambda x: x.get("growth_5y", 0),
reverse=True
)
print(f"\n{'Outcode':<10} {'5Y Growth':>10} {'3Y Growth':>10} {'1Y Growth':>10} {'Median £':>12}")
print("-" * 55)
for r in ranked:
print(
f"{r['outcode']:<10}"
f"{r['growth_5y']:>+9.1f}%"
f"{r['growth_3y']:>+9.1f}%"
f"{r['growth_1y']:>+9.1f}%"
f"£{r['median_price']:>10,}"
)
Example output:
Outcode 5Y Growth 3Y Growth 1Y Growth Median £
-------------------------------------------------------
E3 +31.2% +14.1% +5.2% £ 425,000
N16 +28.7% +12.8% +4.9% £ 510,000
SE15 +26.4% +11.3% +3.8% £ 395,000
N4 +24.1% +10.2% +3.1% £ 445,000
E2 +23.8% +9.7% +2.9% £ 590,000
N7 +21.5% +8.4% +2.7% £ 475,000
E1 +19.2% +7.6% +2.4% £ 640,000
SW9 +18.3% +7.1% +2.1% £ 520,000
SE1 +15.1% +5.2% +1.6% £ 780,000
N1 +14.8% +4.9% +1.3% £ 820,000
A pattern emerges quickly: strong 5Y growth often correlates with lower median prices — these are the areas that gentrified. SE1 and N1 are already expensive; the big moves happened 10+ years ago. E3 and SE15 show the current frontier.
Use case: time-adjusting a valuation
Price growth data matters beyond rankings. It's a core input for AVM models. If you have a property that last sold for £380,000 two years ago, you can project its current value using the outcode's growth rate:
from datetime import datetime
def time_adjust_price(historic_price: float, postcode: str, sale_date: str) -> dict:
"""Project a historic sale price forward using local price growth."""
data = get_price_growth(postcode)
sale_dt = datetime.strptime(sale_date, "%Y-%m-%d")
years_ago = (datetime.now() - sale_dt).days / 365.25
# Use the most appropriate growth rate for the elapsed time
if years_ago <= 1.5:
annual_rate = data["growth_1y"] / 100
elif years_ago <= 4:
annual_rate = (data["growth_3y"] / 100) / 3
elif years_ago <= 7.5:
annual_rate = (data["growth_5y"] / 100) / 5
else:
annual_rate = (data["growth_10y"] / 100) / 10
projected = historic_price * (1 + annual_rate) ** years_ago
return {
"original_price": historic_price,
"projected_price": round(projected, -3),
"years_elapsed": round(years_ago, 1),
"annual_rate_used": round(annual_rate * 100, 2),
"outcode": data["outcode"],
}
result = time_adjust_price(380000, "E3 4AA", "2023-09-15")
print(f"Original: £{result['original_price']:,}")
print(f"Projected: £{result['projected_price']:,}")
print(f"Annual rate used: {result['annual_rate_used']}% ({result['outcode']})")
Use case: mortgage LTV recalculation
Mortgage advisors use this pattern to help clients understand how their LTV has changed since they bought — useful for remortgage conversations:
def calculate_current_ltv(
original_price: float,
purchase_date: str,
postcode: str,
outstanding_mortgage: float
) -> dict:
"""Estimate current LTV based on price growth since purchase."""
adj = time_adjust_price(original_price, postcode, purchase_date)
current_value = adj["projected_price"]
ltv = (outstanding_mortgage / current_value) * 100
return {
"purchase_price": original_price,
"estimated_value": current_value,
"outstanding_mortgage": outstanding_mortgage,
"estimated_ltv": round(ltv, 1),
"equity": round(current_value - outstanding_mortgage, -3),
}
ltv = calculate_current_ltv(
original_price=350000,
purchase_date="2021-03-01",
postcode="N16 8AA",
outstanding_mortgage=252000
)
print(f"Purchase price: £{ltv['purchase_price']:,}")
print(f"Estimated value: £{ltv['estimated_value']:,}")
print(f"Current LTV: {ltv['estimated_ltv']}%")
print(f"Estimated equity: £{ltv['equity']:,}")
JavaScript version
The same postcode ranking in Node.js:
const API_KEY = 'your_api_key_here';
const BASE = 'https://api.homedata.co.uk/api';
const headers = { Authorization: `Api-Key ${API_KEY}` };
const OUTCODES = ['E1', 'E2', 'E3', 'N1', 'N4', 'N7', 'N16', 'SE1', 'SE15', 'SW9'];
async function fetchGrowth(outcode) {
const res = await fetch(`${BASE}/price-growth/${outcode}/`, { headers });
if (!res.ok) return null;
return res.json();
}
async function rankByAppreciation(outcodes) {
const results = await Promise.all(outcodes.map(fetchGrowth));
return results
.filter(Boolean)
.sort((a, b) => b.growth_5y - a.growth_5y);
}
rankByAppreciation(OUTCODES).then(ranked => {
ranked.forEach(r => {
console.log(`${r.outcode.padEnd(8)} 5Y: ${r.growth_5y.toFixed(1)}% Median: £${r.median_price.toLocaleString()}`);
});
});
API endpoints used
| Endpoint | Purpose | Plan |
|---|---|---|
/api/price-growth/{outcode}/ |
1Y/3Y/5Y/10Y capital appreciation | Starter |
/api/postcode-profile/{postcode}/ |
Area demographics, index of multiple deprivation | Starter |
/api/comparables/{uprn}/ |
Nearby sold prices for AVM / valuation | Starter |
/api/property/{uprn}/ |
Property attributes (type, bedrooms, tenure) | Starter |
Rate limits and batching
If you're screening 100+ outcodes, the Starter plan's per-request limit applies to each call individually. To stay within rate limits:
- Use the parallel fetch pattern above with
max_workers=5in Python orPromise.allin JS — don't fire 100 requests simultaneously. - Cache results: price growth data updates quarterly. Store it in Redis or a simple dict with a TTL of 7 days and you'll eliminate 95% of repeat calls.
- For bulk screening (500+ postcodes), the Professional plan includes a higher rate limit and batch endpoint support.
Combining growth with yield for total return
Capital appreciation is only half the picture for investors. The total return on a buy-to-let is appreciation plus rental yield. You can combine the price growth endpoint with Homedata's rental data for a complete picture:
def total_return_estimate(postcode: str, uprn: str) -> dict:
"""Estimate total annual return = gross yield + annual price growth."""
growth = get_price_growth(postcode)
# Pull gross yield from the property endpoint
prop = requests.get(
f"https://api.homedata.co.uk/api/property/{uprn}/",
headers={"Authorization": f"Api-Key {API_KEY}"},
timeout=10
).json()
gross_yield = prop.get("gross_yield_estimate", 0) # e.g. 4.5
appreciation = growth["growth_1y"] # e.g. 3.2
return {
"outcode": growth["outcode"],
"gross_yield": gross_yield,
"appreciation": appreciation,
"total_return": round(gross_yield + appreciation, 1),
}
Ready to start screening? Get a free API key — the Starter plan covers all endpoints in this guide. No credit card required.
Start building with the free API →
100 calls/month · EPC, Land Registry, council tax, schools · No credit card
Track capital appreciation by postcode
1Y, 3Y, 5Y, and 10Y growth rates for any UK outcode. Starter plan, free tier available.