API reference

Overview

Base URL /v1. All requests and responses are JSON. Send a UPRN or a UK postcode; get back coordinates and a precision tier. 1,000 successful lookups are free per account, then a one-time £20 unlocks unlimited use. Unmatched lookups never count against your allowance.

Authentication

HEADER X-API-Key: <your-key>

Get a key from the dashboard or via POST /v1/signup. The demo endpoints below work without a key.

Precision & coverage

Every response carries a precision field:

TierMeaning
propertyExact building coordinate, via a UPRN or an address match against the register. matched_via says which.
postcodePostcode-centroid fallback. Typically 50 m – several hundred metres from the building.
unmatchedNothing resolved. Free — not counted against your allowance.

Property-level coverage: England & Wales — homes, commercial premises, and public buildings, via the domestic and non-domestic EPC registers and Display Energy Certificates. Postcode centroids: England, Wales, Scotland. Northern Ireland (BT) postcodes return unmatched for data-licensing reasons.

How matching works

An address is text, not a place. Turning one into the other is a matching problem, and it pays to know exactly how the matching is done, because the failure mode of a geocoder is not "no answer": it is a confident answer in the wrong place.

The reference set

Ordnance Survey gives every addressable building in Britain a Unique Property Reference Number (UPRN) and publishes each one's coordinates as open data. Energy Performance Certificates carry both a written address and its UPRN, and nearly every home sold or rented since 2008 has one. Joining the two gives roughly 20 million verified examples of "this text means that building". That join is the reference set every lookup runs against.

Cleaning and variants

The same address gets written many ways: "Apartment A" against "Flat A", "St Johns Road" against "Saint Johns Road", "12A" against "12 A". Both the reference set and your input are cleaned by one shared set of rules: abbreviations spelled out, punctuation dropped, number and letter fused. Each reference address is then stored under several spellings at once, including a form with words like Flat and Apartment removed (the identifying letter or number always stays) and a form with all spaces removed. Your input is looked up the same way, most exact form first. A match only ever happens within the input's own postcode.

Refusing to guess

Two rules keep precision up. First, when two different buildings produce the same cleaned text, that spelling is dropped from the reference set entirely, because answering through it would be a coin flip. Second, every reference entry is tested against its postcode's real boundary polygon; entries that sit outside the postcode they claim (the register does contain UPRNs pointing at the wrong town) are removed, and the removals are kept on file so a future rebuild cannot reintroduce them.

The fallback ladder

You always get the most precise answer the evidence supports, labelled. A verified building match returns property. Failing that, the postcode centroid returns postcode, usually within a street or two. Failing that, unmatched, which costs you nothing. The response never dresses one tier up as another.

Single geocode

GET /v1/geocode?uprn={uprn}
GET /v1/geocode?postcode={postcode}
ParamNotes
uprnPositive integer, up to 12 digits. Tried first.
postcodeUK postcode, any casing or spacing.
addressOptional address text. Fuzzy-matched against the register within the postcode; a confident match returns building-level coordinates with matched_via: "address" and a confidence score.
curl -H "X-API-Key: $KEY" \
  "BASE/v1/geocode?uprn=100012"

{
  "query": { "uprn": 100012 },
  "matched": true,
  "precision": "property",
  "lat": 51.4566671,
  "lon": -2.5591032,
  "uprn": 100012
}

Batch geocode

POST /v1/geocode/batch

Up to 500 queries per call, answered in order. The response includes a per-tier summary. This is what the upload tool uses under the hood, and it suits spreadsheet pipelines: send address rows from Excel or CSV, get lat and lon back in order.

curl -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{ "queries": [ {"uprn": 100012}, {"postcode": "SW1A 1AA"} ] }' \
  "BASE/v1/geocode/batch"

{
  "results": [
    { "query": {"uprn": 100012}, "matched": true, "precision": "property",
      "lat": 51.4566671, "lon": -2.5591032, "uprn": 100012 },
    { "query": {"postcode": "SW1A 1AA"}, "matched": true, "precision": "postcode",
      "lat": 51.5010, "lon": -0.1416 }
  ],
  "summary": { "property": 1, "postcode": 1, "unmatched": 0 }
}

Demo endpoints

GET /v1/demo?postcode={postcode}
POST /v1/demo/batch

No key required — these power the site's try-it box and the tool's 25-row preview. Same response shapes as above. Allowance: 300 lookups per IP per day; the batch form accepts up to 25 queries per call and returns demo_remaining.

Signup

POST /v1/signup
curl -X POST -H "Content-Type: application/json" \
  -d '{ "email": "you@company.co.uk" }' "BASE/v1/signup"

{ "api_key": "…", "usage_count": 0, "free_limit": 1000, "unlocked": false }

One key per email. The key is shown once in the response and also emailed to you.

POST /v1/recover-key

Forgotten keys: send { "email": "..." } and the key is re-emailed to that address (the response never reveals whether an account exists).

Usage

GET /v1/usage
curl -H "X-API-Key: $KEY" "BASE/v1/usage"

{
  "email": "you@company.co.uk",
  "usage_count": 342,
  "free_limit": 1000,
  "unlocked": false,
  "breakdown": { "property": 291, "postcode": 44, "unmatched": 7 }
}

Checkout

POST /v1/checkout

Returns a Stripe Checkout URL for the one-time £20 unlock. On completion your key becomes unlimited — nothing else changes.

curl -X POST -H "X-API-Key: $KEY" "BASE/v1/checkout"

{ "checkout_url": "https://checkout.stripe.com/c/pay/…" }

Errors & limits

StatusMeaning
400Malformed request — missing or invalid parameters.
401invalid_api_key
402free_tier_exhausted — body includes checkout_url. Unlock for £20 once.
429Rate limited — 600 requests/minute per key, or the daily demo allowance.
{
  "error": "free_tier_exhausted",
  "usage_count": 1000,
  "free_limit": 1000,
  "checkout_url": "BASE/dashboard"
}

Rate limits protect the infrastructure, not revenue: 600 requests/minute per key, 3 signups per IP per day.