Replacing getAddress.io? Free drop-in replacement →
·

SDKs & Libraries

Lightweight client libraries for Python and JavaScript. Drop-in wrappers around the REST API — no dependencies beyond your language's HTTP library.

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://homedata.co.uk/api/v1) 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/v1/address/find

response = requests.get(
    f"https://homedata.co.uk/api/v1/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 (2 calls, API key required)
resp = requests.get(
    "https://homedata.co.uk/api/v1/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/v1/address/find

const response = await fetch(
  `https://homedata.co.uk/api/v1/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 (2 calls, API key required)
const res = await fetch(
  'https://homedata.co.uk/api/v1/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 (2 calls each)
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 (Starter+)
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 (Growth+)
const { data: valuation } = await client.valuations.estimate(uprn, 'sale');
console.log(`£${valuation.estimate.toLocaleString()} (${valuation.confidence} confidence)`);

// Broadband speeds (free)
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 Plan
client.address.find(query)Address autocomplete2 calls
client.address.retrieve(uprn)Enriched property by UPRN5 calls
client.properties.get(uprn)Full property recordStarter+
client.schools.nearby(opts)Schools by postcode + radiusStarter+
client.broadband.get(postcode)Broadband speedsFree
client.valuations.estimate(uprn)Automated property valuationGrowth+
client.demographics.area(opts)Area demographics + deprivationStarter+
client.priceGrowth.get(outcode)Price growth by outcodeGrowth+
client.postcodeProfile.get(pc)5 datasets in one callStarter+

Source: public/sdk/homedata-js/ — 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://homedata.co.uk/api/v1"


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 (2 calls) and UPRN retrieve (5 calls)."""

    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(f"/valuations/{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 (2 calls)
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 (Growth+)
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://homedata.co.uk/api/v1";

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 — 2 calls per request. */
  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/${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 (2 calls)
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 (Growth+)
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://homedata.co.uk/api/v1',
        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 (find: 2 calls, retrieve: 5 calls) ──
    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 (Growth+) ──
    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 (2 calls)
$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.