If you work with UK open data, you have almost certainly hit the same wall: every dataset uses a different geography code. Deprivation statistics are published by LSOA, public health data by MSOA, and planning and housing data by local authority district (LAD). To combine any of these datasets, you need a fast, reliable way to resolve a UK postcode to the full ONS geography hierarchy. This guide explains how — with working API code in Python, Node.js, and cURL, production-ready patterns for bulk lookups, and a clear explanation of the ONS geography system every UK developer should know.

The ONS geography hierarchy — what every UK developer needs to know

The Office for National Statistics (ONS) divides the UK into a nested hierarchy of statistical geographies. Each level has a specific population range, a unique code format, and a set of datasets published against it. Understanding this hierarchy is the foundation of any UK data project.

Hierarchy overview (smallest to largest)

Output Area (OA)
↓
Lower Super Output Area (LSOA)
↓
Middle Super Output Area (MSOA)
↓
Local Authority District (LAD)
↓
Region
↓
Country

What each level means

  • Output Area (OA) — ~300 residents. The smallest statistical unit, primarily used in Census 2021 data.
  • Lower Super Output Area (LSOA) — ~1,500 residents. The most widely used level for datasets including the Index of Multiple Deprivation (IMD), crime statistics, and NHS data.
  • Middle Super Output Area (MSOA) — ~8,000 residents. Used in public health, GP practice data, and travel-to-work analysis.
  • Local Authority District (LAD) — Councils and boroughs (~317 in England). The standard level for planning, housing, and budget data.
  • Region — Nine regions of England, plus Wales, Scotland, and Northern Ireland.

Quick reference table

LevelApprox. populationPrimary use caseCode example
OA~300Census 2021E00012345
LSOA~1,500IMD, NHS, crimeE01000001
MSOA~8,000Public health, travelE02000001
LAD~170,000Planning, housingE07000008
Region~5.5MEconomic reportingE12000007

Important: ONS geography boundaries change — most significantly after each Census. Always match the vintage year of your lookup data with the vintage year of the dataset you are joining.

How UK postcode geography actually works

Royal Mail creates and maintains postcodes — not ONS. But postcodes appear in almost every UK dataset, which makes postcode-to-geography lookup the most common operation in UK data work.

The key concept is straightforward: every postcode maps to a single geographic centroid. That centroid falls inside exactly one OA, one LSOA, one MSOA, and one LAD. This makes postcode-to-ONS-geography lookup deterministic, fast, and reliable.

The official reference dataset for this mapping is the ONS Postcode Directory (ONSPD), updated quarterly and available free under the Open Government Licence. The raw file is ~500MB CSV — manageable for batch processing, but impractical for real-time lookups. That is where a postcode API adds real value.

Postcode to LSOA, MSOA and LAD via API — working code examples

The following examples use the APITier Postcode API to return LSOA, MSOA, LAD, and region in a single request. Substitute your own API key and postcode.

Python

import requests

API_KEY = "your_api_key_here"

def get_ons_geography(postcode):
    clean = postcode.replace(" ", "").upper()
    response = requests.get(
        f"https://postcode.apitier.com/v1/postcodes/{clean}",
        params={"x-api-key": API_KEY},
        timeout=5
    )
    response.raise_for_status()
    data = response.json()
    return {
        "postcode": data["postcode"],
        "lsoa":     data.get("lsoa_code"),
        "msoa":     data.get("msoa_code"),
        "lad":      data.get("lad_code"),
        "region":   data.get("region_name")
    }

print(get_ons_geography("M1 1AE"))

Node.js

const axios = require("axios");

const API_KEY = "your_api_key_here";

async function getONSGeography(postcode) {
  const clean = postcode.replace(/s/g, "").toUpperCase();
  const { data } = await axios.get(
    `https://postcode.apitier.com/v1/postcodes/${clean}`,
    { params: { "x-api-key": API_KEY } }
  );
  return {
    postcode: data.postcode,
    lsoa:     data.lsoa_code,
    msoa:     data.msoa_code,
    lad:      data.lad_code,
    region:   data.region_name
  };
}

getONSGeography("M1 1AE").then(console.log);

cURL

curl "https://postcode.apitier.com/v1/postcodes/M11AE?x-api-key=your_api_key_here"

Sample API response

{
  "postcode":    "M1 1AE",
  "lsoa_code":   "E01033668",
  "msoa_code":   "E02001069",
  "lad_code":    "E08000003",
  "region_name": "North West"
}

Bulk postcode resolution — patterns for large datasets

For datasets with thousands of postcodes, two approaches work well depending on your infrastructure.

Option 1: Async batching with Python (API-based)

Use asyncio and httpx with a semaphore to limit concurrency and respect rate limits.

import asyncio
import httpx

API_KEY = "your_api_key_here"
CONCURRENCY = 20

async def fetch(postcode, client, sem):
    async with sem:
        res = await client.get(
            f"https://postcode.apitier.com/v1/postcodes/{postcode.replace(' ', '').upper()}",
            params={"x-api-key": API_KEY}
        )
        return res.json()

async def resolve_all(postcodes):
    sem = asyncio.Semaphore(CONCURRENCY)
    async with httpx.AsyncClient() as client:
        tasks = [fetch(p, client, sem) for p in postcodes]
        return await asyncio.gather(*tasks)

results = asyncio.run(resolve_all(["M1 1AE", "SW1A 2AA", "EC1A 1BB"]))

Option 2: ONSPD local SQL join (database-based)

For millions of records, load the ONSPD CSV into a database table and join locally — no API calls needed.

SELECT
    a.postcode,
    o.lsoa11cd,
    o.msoa11cd,
    o.ladcd
FROM addresses a
LEFT JOIN onspd o
    ON REPLACE(a.postcode, ' ', '') = REPLACE(o.pcds, ' ', '')

Real-world use cases

1. Deprivation scoring (IMD)

Resolve customer postcodes to LSOA, then join with the MHCLG Index of Multiple Deprivation to add a deprivation score and decile to each record. Useful for targeting, grant eligibility, and social impact reporting.

2. Sales and performance by local authority

Map order or transaction postcodes to LAD codes, then group and aggregate by council area. Instantly answers “which local authorities are underperforming?” without any manual geography work.

3. Public health data mapping

Resolve patient or survey postcodes to MSOA, then join with NHS Digital datasets on GP registrations, A&E attendances, or disease prevalence. MSOA is the standard geography for NHS statistical outputs.

Common developer mistakes to avoid

1. Ignoring boundary changes

LAD codes are reorganised regularly — councils merge, split, and rename. Always verify that your lookup data and your target dataset use the same geography vintage. ONS publishes change history files for each boundary revision.

2. Rejecting terminated postcodes

Terminated postcodes still appear in older datasets. The ONSPD retains them with a termination date field — a good API will still return their last-known geography codes rather than a 404 error.

3. Assuming England-only coverage

CountryEquivalent to LSOANotes
EnglandLSOA / MSOAStandard ONS codes
ScotlandData Zones (DZ)Different code format
WalesLSOA (shared format)Separate IMD dataset
Northern IrelandSOAPublished by NISRA

4. Not handling missing geography data

A small number of special postcodes (non-geographic, Crown dependencies, BFPO) do not map to standard ONS geographies. Always handle null values in your LSOA, MSOA, and LAD fields.

Datasets you can join once you have ONS codes

DatasetGeography levelWhat it addsSource
Index of Multiple Deprivation (IMD)LSOADeprivation score + decileMHCLG
Census 2021OA / MSOAPopulation, age, ethnicityONS
NHS Digital statsMSOAGP registrations, A&ENHS Digital
Land Registry PPDLADProperty pricesHMLR
EA Flood RiskLSOAFlood zone classificationEnvironment Agency

Frequently asked questions

What is the difference between LSOA and MSOA?

An LSOA (Lower Super Output Area) contains approximately 1,500 residents. An MSOA (Middle Super Output Area) contains approximately 8,000 residents and is made up of several LSOAs. Use LSOA for fine-grained analysis such as deprivation mapping; use MSOA for health and demographic datasets that are not available at LSOA level.

Does each UK postcode map to exactly one LSOA?

Yes. Each postcode centroid falls within exactly one OA, which in turn belongs to exactly one LSOA, MSOA, and LAD. The mapping is one-to-one and deterministic.

How often do ONS geography boundaries change?

LSOA and MSOA boundaries are redesigned after each Census (approximately every 10 years — the latest revision was for Census 2021). LAD boundaries change more frequently, typically following local government reorganisations. Always check ONS boundary change notices when working with multi-year data.

Can I download the postcode-to-LSOA mapping for free?

Yes. The ONS Postcode Directory (ONSPD) is published free of charge under the Open Government Licence. It is updated quarterly and available from the ONS Open Geography Portal. The full file is approximately 500MB.

When should I use an API instead of the ONSPD file?

Use the ONSPD file for bulk batch processing of millions of records. Use an API for real-time lookups, small-to-medium volumes, or when you want to avoid hosting and maintaining a 500MB dataset that updates quarterly.

Conclusion

ONS geography codes are the connective tissue of UK open data. Once you can reliably resolve a postcode to its LSOA, MSOA, and LAD, you can join almost any government dataset — deprivation indices, health statistics, planning data, property prices, and more.

A postcode API removes the friction of managing the ONSPD locally. The APITier Postcode API returns all ONS geography codes in a single call, stays current with quarterly ONSPD releases, and handles edge cases like terminated and non-geographic postcodes.