Skip to main content
Free UK property data API Start free →
·

Code Examples

Copy-paste examples for Python, JavaScript, and PHP. Drop them into your project and make your first API call in minutes — no dependencies, no setup.

Copy-paste or download. All three clients are single-file wrappers — drop them straight into your project with zero build step. npm and PyPI packages planned.

All clients target the V1 API (https://api.homedata.co.uk/api) with namespaced resources, typed errors, and rate-limit tracking built in.

Zero to first call

The fastest way to make your first API call — no wrapper, no installation. All you need is your API key and the language you already know.

Requires requests. Install with pip install requests.

quickstart.py 10 lines
import requests

API_KEY = "your_api_key_here"
UPRN    = "100023336956"  # get UPRNs via /api/address/find

response = requests.get(
    f"https://api.homedata.co.uk/api/properties/{UPRN}",
    headers={"Authorization": f"Api-Key {API_KEY}"},
)

data = response.json()
print(data["address"], data["bedrooms"], "bed", data["property_type"])
Find a property by address first
# Step 1: resolve address → UPRN (API key required)
resp = requests.get(
    "https://api.homedata.co.uk/api/address/find",
    headers={"Authorization": f"Api-Key {API_KEY}"},
    params={"q": "10 Downing Street London"},
)
suggestions = resp.json()["data"]
uprn = suggestions[0]["uprn"]

Uses the native fetch API — no dependencies, works in Node.js 18+ and all modern browsers.

quickstart.js 10 lines
const API_KEY = 'your_api_key_here';
const UPRN    = '100023336956';  // get UPRNs via /api/address/find

const response = await fetch(
  `https://api.homedata.co.uk/api/properties/${UPRN}`,
  { headers: { 'Authorization': `Api-Key ${API_KEY}` } }
);

const property = await response.json();
console.log(property.address, property.bedrooms + ' bed', property.property_type);
Find a property by address first
// Step 1: resolve address → UPRN (API key required)
const res = await fetch(
  'https://api.homedata.co.uk/api/address/find?q=10+Downing+Street+London',
  { headers: { 'Authorization': `Api-Key ${API_KEY}` } }
);
const { data: suggestions } = await res.json();
const uprn = suggestions[0].uprn;

npm package planned

The homedata npm package is not yet published. In the meantime, copy homedata.js from the JavaScript tab below — it's a single-file zero-dependency wrapper that drops straight into any project.

quickstart.ts homedata v1.0 — TypeScript + JavaScript
import Homedata from 'homedata';

// Initialise once — all methods use the key automatically
const client = new Homedata('hd_your_api_key');

// Search addresses
const { data: addresses } = await client.address.find('10 Downing Street');
const uprn = addresses[0].uprn;  // 100023336956

// Get full property record
const { data: property } = await client.properties.get(uprn);
console.log(property.address, property.bedrooms + ' bed', property.epc_rating);

// Nearby schools
const { data: schools } = await client.schools.nearby({ postcode: 'SW1A 2AA', phase: 'Primary' });
schools.forEach(s => console.log(s.name, s.ofsted.rating, `${s.distance_miles}mi`));

// Automated valuation
const { data: valuation } = await client.valuations.estimate(uprn, 'sale');
console.log(`£${valuation.estimate.toLocaleString()} (${valuation.confidence} confidence)`);

// Broadband speeds
const { data: broadband } = await client.broadband.get('SW1A 2AA');
console.log(`${broadband.avg_download_speed} Mbps down / ${broadband.avg_upload_speed} Mbps up`);

Full API surface — v1.0

Method Description
client.address.find(query)Address autocomplete
client.address.retrieve(uprn)Enriched property by UPRN
client.properties.get(uprn)Full property record
client.schools.nearby(opts)Schools by postcode + radius
client.broadband.get(postcode)Broadband speeds
client.valuations.estimate(uprn)Automated property valuation
client.demographics.area(opts)Area demographics + deprivation
client.priceGrowth.get(outcode)Price growth by outcode
client.postcodeProfile.get(pc)5 datasets in one call

TypeScript, fully typed, zero dependencies beyond node-fetch (Node 18+ ships it natively).


Full client libraries

Complete wrappers with all 8 V1 resource namespaces. Copy the single file directly into your project — no build step, no dependencies beyond your language's HTTP library. npm and PyPI packages planned.

Python Client

Requires Python 3.9+ and the requests library.

PyPI package planned

pip install homedata is not yet available. Copy the class below instead — save it as homedata.py and import it directly. One file, no build step.

homedata.py copy-paste — pip package planned
"""Homedata V1 API client — namespaced resources, zero deps beyond requests."""
import requests

BASE_URL = "https://api.homedata.co.uk/api"


class HomedataError(Exception):
    def __init__(self, message: str, status: int, code: str):
        super().__init__(message)
        self.status = status
        self.code = code


class _Resource:
    def __init__(self, client):
        self._client = client

    def _get(self, path: str, params: dict = None) -> dict:
        return self._client._request(path, params)


class AddressResource(_Resource):
    """Address search and UPRN retrieve."""

    def find(self, query: str, limit: int = 20) -> list:
        resp = self._get("/address/find", {"q": query, "limit": limit})
        return resp.get("data", resp)

    def retrieve(self, uprn: int) -> dict:
        resp = self._get(f"/address/retrieve/{uprn}")
        return resp.get("data", resp)


class PropertiesResource(_Resource):
    def search(self, postcode: str, page: int = 1) -> list:
        resp = self._get("/properties", {"postcode": postcode, "page": page})
        return resp.get("data", resp)

    def get(self, uprn: int) -> dict:
        resp = self._get(f"/properties/{uprn}")
        return resp.get("data", resp)


class ValuationsResource(_Resource):
    def estimate(self, uprn: int, type: str = "sale") -> dict:
        return self._get("/valuations/estimate/", {"uprn": uprn, "type": type})


class SchoolsResource(_Resource):
    def nearby(self, postcode: str = None, lat: float = None, lng: float = None,
               radius: float = 0.5, limit: int = 20, phase: str = None) -> list:
        params = {"radius": radius, "limit": limit}
        if postcode: params["postcode"] = postcode
        if lat: params["lat"] = lat
        if lng: params["lng"] = lng
        if phase: params["phase"] = phase
        resp = self._get("/schools/nearby", params)
        return resp.get("schools", resp)


class BroadbandResource(_Resource):
    def get(self, postcode: str) -> dict:
        return self._get("/broadband", {"postcode": postcode})


class DemographicsResource(_Resource):
    def area(self, postcode: str = None, lat: float = None, lng: float = None) -> dict:
        params = {}
        if postcode: params["postcode"] = postcode
        if lat: params["lat"] = lat
        if lng: params["lng"] = lng
        return self._get("/demographics", params)


class PriceGrowthResource(_Resource):
    def get(self, outcode: str) -> dict:
        return self._get(f"/price-growth/{outcode}")


class PostcodeProfileResource(_Resource):
    def get(self, postcode: str) -> dict:
        return self._get("/postcode-profile", {"postcode": postcode})


class Homedata:
    """Homedata V1 API client.

    Usage:
        client = Homedata("hd_your_api_key")
        results = client.address.find("10 Downing Street")
        prop    = client.properties.get(100023336956)
        schools = client.schools.nearby(postcode="SW1A 2AA")
    """

    def __init__(self, api_key: str, base_url: str = BASE_URL, timeout: int = 30):
        if not api_key:
            raise ValueError("API key required — get one at homedata.co.uk/register")
        self._base_url = base_url.rstrip("/")
        self._timeout = timeout
        self._session = requests.Session()
        self._session.headers.update({
            "Authorization": f"Api-Key {api_key}",
            "Accept": "application/json",
        })
        self.rate_limit = {"limit": 0, "remaining": 0, "reset": 0}

        # Resource namespaces
        self.address         = AddressResource(self)
        self.properties      = PropertiesResource(self)
        self.valuations      = ValuationsResource(self)
        self.schools         = SchoolsResource(self)
        self.broadband       = BroadbandResource(self)
        self.demographics    = DemographicsResource(self)
        self.price_growth    = PriceGrowthResource(self)
        self.postcode_profile = PostcodeProfileResource(self)

    def _request(self, path: str, params: dict = None) -> dict:
        url = f"{self._base_url}{path}"
        clean = {k: v for k, v in (params or {}).items() if v is not None}
        resp = self._session.get(url, params=clean, timeout=self._timeout)
        self.rate_limit = {
            "limit":     int(resp.headers.get("X-RateLimit-Limit", 0)),
            "remaining": int(resp.headers.get("X-RateLimit-Remaining", 0)),
            "reset":     int(resp.headers.get("X-RateLimit-Reset", 0)),
        }
        if not resp.ok:
            body = resp.json() if resp.content else {}
            err  = body.get("error", {})
            raise HomedataError(
                err.get("message", f"HTTP {resp.status_code}"),
                resp.status_code,
                err.get("code", "UNKNOWN"),
            )
        return resp.json()

Usage Examples

Quick start
from homedata import Homedata, HomedataError

client = Homedata("hd_your_api_key")

# Address search
results = client.address.find("10 Downing Street")
for addr in results:
    print(f"{addr['address']}, {addr['postcode']} — UPRN: {addr['uprn']}")

# Full property record by UPRN
prop = client.properties.get(100023336956)
print(f"Type: {prop['property_type']}, Beds: {prop['bedrooms']}, EPC: {prop['epc_rating']}")

# Automated valuation
val = client.valuations.estimate(100023336956, type="sale")
print(f"Estimate: £{val['estimate']:,} ({val['confidence']} confidence)")

# Nearby schools
schools = client.schools.nearby(postcode="SW1A 2AA", phase="Primary")
for s in schools:
    print(f"{s['name']} — Ofsted: {s['ofsted']['rating']}, {s['distance_miles']:.1f}mi")

# Rate limit tracking
print(f"{client.rate_limit['remaining']}/{client.rate_limit['limit']} calls remaining")

# Error handling
try:
    prop = client.properties.get(999)
except HomedataError as e:
    print(f"Error {e.status}: {e.code}")
Bulk property lookup
# Get properties from a postcode + enrich each one
from homedata import Homedata
import time

client = Homedata("hd_your_api_key")

# Search properties at a postcode
properties = client.properties.search("SW1A 2AA")
for summary in properties[:5]:
    uprn = summary["uprn"]
    prop = client.properties.get(uprn)
    val  = client.valuations.estimate(uprn)
    print(f"{prop['address']} — EPC: {prop.get('epc_rating', 'N/A')}, AVM: £{val.get('estimate', 0):,}")
    time.sleep(0.1)  # respect rate limits

# Full area profile in one call
profile = client.postcode_profile.get("SW1A 2AA")
print(profile.keys())  # deprivation, broadband, schools, transport, prices

JavaScript / TypeScript Client

Zero-dependency client for Node.js 18+ (uses native fetch) and modern browsers. Save as homedata.js or homedata.ts.

homedata.js copy-paste · npm planned
/** Homedata V1 API client — namespaced resources, zero dependencies. */

const BASE_URL = "https://api.homedata.co.uk/api";

export class HomedataError extends Error {
  constructor(message, status, code) {
    super(message);
    this.status = status;
    this.code = code;
  }
}

class _Resource {
  constructor(client) { this._client = client; }
  _get(path, params) { return this._client._request(path, params); }
}

class AddressResource extends _Resource {
  /** Search addresses. */
  find(query, { limit = 20 } = {}) {
    return this._get("/address/find", { q: query, limit })
      .then(r => r.data ?? r);
  }
  /** Enriched property by UPRN. */
  retrieve(uprn) {
    return this._get(`/address/retrieve/${uprn}`).then(r => r.data ?? r);
  }
}

class PropertiesResource extends _Resource {
  search(postcode, { page = 1 } = {}) {
    return this._get("/properties", { postcode, page }).then(r => r.data ?? r);
  }
  get(uprn) {
    return this._get(`/properties/${uprn}`).then(r => r.data ?? r);
  }
}

class ValuationsResource extends _Resource {
  estimate(uprn, type = "sale") {
    return this._get("/valuations/estimate/", { uprn, type });
  }
}

class SchoolsResource extends _Resource {
  nearby({ postcode, lat, lng, radius = 0.5, limit = 20, phase } = {}) {
    return this._get("/schools/nearby", { postcode, lat, lng, radius, limit, phase })
      .then(r => r.schools ?? r);
  }
}

class BroadbandResource extends _Resource {
  get(postcode) { return this._get("/broadband", { postcode }); }
}

class DemographicsResource extends _Resource {
  area({ postcode, lat, lng } = {}) {
    return this._get("/demographics", { postcode, lat, lng });
  }
}

class PriceGrowthResource extends _Resource {
  get(outcode) { return this._get(`/price-growth/${outcode}`); }
}

class PostcodeProfileResource extends _Resource {
  get(postcode) { return this._get("/postcode-profile", { postcode }); }
}

export class Homedata {
  /**
   * @param {string} apiKey   Your Homedata API key
   * @param {{ baseUrl?: string, timeout?: number }} [opts]
   *
   * const client = new Homedata('hd_your_api_key');
   * const addresses = await client.address.find('10 Downing Street');
   * const property  = await client.properties.get(100023336956);
   * const schools   = await client.schools.nearby({ postcode: 'SW1A 2AA' });
   */
  constructor(apiKey, { baseUrl = BASE_URL, timeout = 30_000 } = {}) {
    if (!apiKey) throw new Error("API key required — get one at homedata.co.uk/register");
    this._baseUrl = baseUrl.replace(/\/$/, "");
    this._headers = { "Authorization": `Api-Key ${apiKey}`, "Accept": "application/json" };
    this._timeout = timeout;
    this.rateLimit = { limit: 0, remaining: 0, reset: 0 };

    this.address         = new AddressResource(this);
    this.properties      = new PropertiesResource(this);
    this.valuations      = new ValuationsResource(this);
    this.schools         = new SchoolsResource(this);
    this.broadband       = new BroadbandResource(this);
    this.demographics    = new DemographicsResource(this);
    this.priceGrowth     = new PriceGrowthResource(this);
    this.postcodeProfile = new PostcodeProfileResource(this);
  }

  async _request(path, params = {}) {
    const url = new URL(`${this._baseUrl}${path}`);
    Object.entries(params).forEach(([k, v]) =>
      v !== undefined && v !== null && url.searchParams.set(k, v));

    const ctrl = new AbortController();
    const timer = setTimeout(() => ctrl.abort(), this._timeout);
    try {
      const res = await fetch(url.toString(), {
        headers: this._headers, signal: ctrl.signal
      });
      this.rateLimit = {
        limit:     +res.headers.get("X-RateLimit-Limit")     || 0,
        remaining: +res.headers.get("X-RateLimit-Remaining") || 0,
        reset:     +res.headers.get("X-RateLimit-Reset")     || 0,
      };
      const body = await res.json();
      if (!res.ok) {
        const e = body?.error || {};
        throw new HomedataError(e.message || `HTTP ${res.status}`, res.status, e.code || "UNKNOWN");
      }
      return body;
    } finally { clearTimeout(timer); }
  }
}

export default Homedata;

Usage Examples

Quick start (Node.js)
import { Homedata, HomedataError } from "./homedata.js";

const client = new Homedata("hd_your_api_key");

// Address search
const addresses = await client.address.find("10 Downing Street");
addresses.forEach(a => console.log(`${a.address}, ${a.postcode} — UPRN: ${a.uprn}`));

// Full property record
const prop = await client.properties.get(100023336956);
console.log(`${prop.address} — ${prop.bedrooms} bed, EPC: ${prop.epc_rating}`);

// Automated valuation
const val = await client.valuations.estimate(100023336956, "sale");
console.log(`AVM: £${val.estimate.toLocaleString()} (${val.confidence} confidence)`);

// Nearby schools — Ofsted ratings
const schools = await client.schools.nearby({ postcode: "SW1A 2AA", phase: "Primary" });
schools.forEach(s => console.log(`${s.name}: ${s.ofsted.rating} — ${s.distance_miles.toFixed(1)}mi`));

// Rate limit tracking
console.log(`${client.rateLimit.remaining}/${client.rateLimit.limit} calls remaining`);

// Error handling
try {
  await client.properties.get(999);
} catch (e) {
  if (e instanceof HomedataError) {
    console.error(`Error ${e.status}: ${e.code}`);
  }
}
Express.js middleware example
// Property lookup API route
import express from "express";
import { Homedata } from "./homedata.js";

const app = express();
const hd = new Homedata(process.env.HOMEDATA_API_KEY);

app.get("/property/:uprn", async (req, res) => {
  try {
    const uprn = Number(req.params.uprn);
    const [prop, val, schools] = await Promise.all([
      hd.properties.get(uprn),
      hd.valuations.estimate(uprn),
      hd.schools.nearby({ postcode: req.query.postcode }),
    ]);
    res.json({ property: prop, valuation: val, schools });
  } catch (e) {
    res.status(e.status || 500).json({ error: e.message });
  }
});

PHP Client

Requires PHP 8.0+ with curl extension (enabled by default). Save as Homedata.php.

Homedata.php PHP 8.0+
<?php
/** Homedata V1 API client — PHP 8.0+, cURL only. */

class HomedataError extends \RuntimeException
{
    public string $errorCode;
    public int $httpStatus;

    public function __construct(string $code, string $message, int $status)
    {
        $this->errorCode = $code;
        $this->httpStatus = $status;
        parent::__construct("{$code}: {$message}", $status);
    }
}

class Homedata
{
    private string $apiKey;
    private string $base;
    private int $timeout;
    public array $rateLimit = ['limit' => 0, 'remaining' => 0, 'reset' => 0];

    public function __construct(
        string $apiKey,
        string $baseUrl = 'https://api.homedata.co.uk/api',
        int $timeout = 30,
    ) {
        $this->apiKey  = $apiKey;
        $this->base    = rtrim($baseUrl, '/');
        $this->timeout = $timeout;
    }

    private function get(string $path, array $params = []): array
    {
        $url = $this->base . $path;
        if ($params) $url .= '?' . http_build_query(array_filter($params, fn($v) => $v !== null));

        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HEADER         => true,
            CURLOPT_HTTPHEADER     => [
                "Authorization: Api-Key {$this->apiKey}",
                'Accept: application/json',
            ],
            CURLOPT_TIMEOUT => $this->timeout,
        ]);
        $response   = curl_exec($ch);
        $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
        $status     = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $headers    = substr($response, 0, $headerSize);
        $body       = substr($response, $headerSize);
        curl_close($ch);

        // Track rate limits
        preg_match('/X-RateLimit-Limit:\s*(\d+)/i', $headers, $m);
        $this->rateLimit['limit'] = (int) ($m[1] ?? 0);
        preg_match('/X-RateLimit-Remaining:\s*(\d+)/i', $headers, $m);
        $this->rateLimit['remaining'] = (int) ($m[1] ?? 0);
        preg_match('/X-RateLimit-Reset:\s*(\d+)/i', $headers, $m);
        $this->rateLimit['reset'] = (int) ($m[1] ?? 0);

        $data = json_decode($body, true) ?: [];
        if ($status >= 400) {
            $err = $data['error'] ?? [];
            throw new HomedataError(
                $err['code'] ?? 'UNKNOWN',
                $err['message'] ?? $body,
                $status,
            );
        }
        return $data;
    }

    // ── Address ──
    public function addressFind(string $query, int $limit = 20): array
    {
        $r = $this->get('/address/find', ['q' => $query, 'limit' => $limit]);
        return $r['data'] ?? $r;
    }

    public function addressRetrieve(int $uprn): array
    {
        $r = $this->get("/address/retrieve/{$uprn}");
        return $r['data'] ?? $r;
    }

    // ── Properties ──
    public function propertiesSearch(string $postcode, int $page = 1): array
    {
        $r = $this->get('/properties', ['postcode' => $postcode, 'page' => $page]);
        return $r['data'] ?? $r;
    }

    public function propertiesGet(int $uprn): array
    {
        $r = $this->get("/properties/{$uprn}");
        return $r['data'] ?? $r;
    }

    // ── Valuations ──
    public function valuationsEstimate(int $uprn, string $type = 'sale'): array
    {
        return $this->get("/valuations/{$uprn}", ['type' => $type]);
    }

    // ── Schools ──
    public function schoolsNearby(array $params): array
    {
        $r = $this->get('/schools/nearby', $params);
        return $r['schools'] ?? $r;
    }

    // ── Broadband (free) ──
    public function broadband(string $postcode): array
    {
        return $this->get('/broadband', ['postcode' => $postcode]);
    }

    // ── Demographics ──
    public function demographics(?string $postcode = null, ?float $lat = null, ?float $lng = null): array
    {
        return $this->get('/demographics', ['postcode' => $postcode, 'lat' => $lat, 'lng' => $lng]);
    }

    // ── Price Growth ──
    public function priceGrowth(string $outcode): array
    {
        return $this->get("/price-growth/{$outcode}");
    }

    // ── Postcode Profile (5 datasets in one call) ──
    public function postcodeProfile(string $postcode): array
    {
        return $this->get('/postcode-profile', ['postcode' => $postcode]);
    }
}

Usage Example

Laravel integration
// In a Laravel controller or service
$hd = new Homedata(config('services.homedata.key'));

// Address search
$addresses = $hd->addressFind('10 Downing Street');
$uprn = $addresses[0]['uprn'];

// Property + valuation + schools
$property = $hd->propertiesGet($uprn);
$valuation = $hd->valuationsEstimate($uprn);
$schools = $hd->schoolsNearby(['postcode' => $property['postcode'], 'phase' => 'Primary']);

// Rate limit tracking
$remaining = $hd->rateLimit['remaining'];

return view('property.show', compact('property', 'valuation', 'schools'));

Library Features

Feature Python JavaScript PHP
8 V1 resource namespaces
TypeScript types
Typed error handling
Rate limit tracking
Zero external dependencies requests
Configurable timeout
Package manager install pip (soon) npm (soon)
Single file, copy-paste

Using Claude, Cursor, or Windsurf?

Skip the wrapper code entirely — our MCP server gives your AI tool direct access to all 16 endpoints. One config block, no code.

Prefer raw HTTP?

These wrappers are optional. The API is standard REST — any HTTP client works. See the Getting Started guide for cURL examples, or use the Interactive API Explorer to test endpoints in your browser.

Integrate into your own product

Free to start
Homedata API Scientific, granular measurements

The Homedata Python, JavaScript, and PHP clients are single-file wrappers with zero build steps — covering address lookup, property intelligence, EPC, risks, comparables, and live listings across 29M UK properties with typed errors and rate-limit tracking built in.

Structured as JSON · queryable by UPRN or postcode · ready to embed in any application

Exact measurements

Real values — distances, concentrations, counts — not rounded ratings

29M+ UK properties

Every address queryable by UPRN or postcode

REST API

JSON responses, OpenAPI docs, sandbox — first call in under 5 minutes

Free tier: 100 API calls/month across all endpoints, no credit card required. Paid plans from £29/month for production use. Compare plans →