Guides

GetAddress.io Alternatives: Direct Royal Mail PAF vs Open-Data APIs (with Migration Code)

Looking for a getAddress.io alternative? Side-by-side API diffs, drop-in Node.js and Python migration code, and direct Royal Mail PAF vs open-data compared.

# GetAddress.io Alternatives: Direct Royal Mail PAF vs Open-Data Address APIs

Looking for a getAddress.io alternative? Address validation APIs divide into two architectures: direct Royal Mail Postcode Address File (PAF) licensees and open-data derived lookup providers. Direct PAF APIs resolve deliverable delivery points with daily carrier updates, whereas open-data APIs rely on compiled public datasets.

This guide contrasts the technical differences, data provenance, pricing mechanics, and gives drop-in Node.js and Python migration code for switching from getAddress.io to Postio.

---

Summary Comparison

Feature / MetricDirect Royal Mail PAF (e.g. Postio)Open-Data / Compiled APIs (e.g. getAddress.io relaunch)
Primary Data SourceRoyal Mail Postcode Address File (PAF)Compiled open data (Ordnance Survey / Open Names)
Address Count~31+ million UK delivery pointsVariable coverage depending on public dataset sync
Update CadenceDaily direct sync from Royal MailPeriodic public release cycles
Licensing ComplianceDirect Royal Mail licenseeUS entity (GetAddress, LLC, Dover DE) compiled model
Billing ModelTransparent per-lookup / per-sessionTiered plans / subscription quotas
Autocomplete SupportYes (session-based keystrokes)Yes (suggest + get step)
Coverage Accuracy100% postal delivery pointsHigh on major roads; gaps on new builds and sub-units

---

Technical Architecture Differences

1. Royal Mail PAF (Direct Licensing)

Royal Mail maintains the Postcode Address File (PAF), the authoritative database of every deliverable postal address in the United Kingdom. PAF contains over 31 million delivery points and is updated daily by postal carriers on the ground.

  • Sub-building granularity: Accurate apartment numbers, flat designations (e.g. Flat 2B), and industrial unit numbers.
  • New development indexing: New housing developments receive operational postal codes and premise records immediately upon carrier route creation.
  • Delivery clearance: Guaranteed alignment with courier and Royal Mail routing logic.

2. Open-Data and Compiled Datasets

Services operating without direct PAF licensing compile records from open sources such as OS Open UPRN, OS Open Names, and Land Registry open datasets. As of 2026, getAddress.io operates under GetAddress, LLC (registered in Dover, Delaware) using compiled open data models.

  • Coverage tradeoffs: Public datasets often lack delivery-specific premise details (sub-building identifiers, business names, or temporary postal routing quirks).
  • Sync lag: Open datasets update on quarterly or bi-annual government release cycles rather than daily postal feeds.

---

Response Schema Diff: getAddress.io vs Postio

The two APIs model an address differently. getAddress.io returns a flat object with a formatted_address array. Postio returns structured PAF fields. Postio also returns ONS district and ward administrative geography alongside the postal fields.

Field-by-field mapping

getAddress.io fieldPostio fieldNote
postcodepostcodeSame, formatted with a space
formatted_address[0..2]address_line_1 / address_line_2 / address_line_3Array becomes named lines
formatted_address[3] / town_or_citypost_townRoyal Mail post town
formatted_address[4]*(no equivalent — see note below)*Postio returns district and ward (ONS geography) instead
thoroughfarethoroughfareSame name
building_numberbuilding_numberSame name
latitude, longitudelatitude, longitudePostio adds eastings, northings
*(none)*udprnRoyal Mail per-delivery-point ID
*(none)*organisation_name, sub_building_name, po_boxPAF premise detail

getAddress.io response (from their published examples)

{
  "postcode": "NN1 3ER",
  "latitude": 52.245937,
  "longitude": -0.891636,
  "formatted_address": ["10 Watkin Terrace", "", "", "Northampton", "Northamptonshire"],
  "thoroughfare": "Watkin Terrace",
  "building_number": "10",
  "town_or_city": "Northampton",
  "country": "England"
}

Postio response (per the Postio address docs)

{
  "success": true,
  "results": [
    {
      "udprn": 50905588,
      "postcode": "W1G 8YW",
      "postcode_outward": "W1G",
      "postcode_inward": "8YW",
      "address_line_1": "57 Wimpole Street",
      "post_town": "London",
      "building_number": "57",
      "thoroughfare": "Wimpole Street",
      "country": "England",
      "latitude": 51.5173,
      "longitude": -0.1459
    }
  ],
  "meta": { "countResults": 1, "requestId": "..." }
}

Postio wraps results in a success / results envelope; getAddress.io returns the address object bare. Every Postio result carries a udprn, which is what you resolve a picked suggestion against.

---

Endpoint Mapping

getAddress.io callPostio callNotes
GET api.getAddress.io/autocomplete/{term}?api-key={key}GET api.postio.co.uk/v1/address/search?q={query} with x-api-key headerPostio search is free; key goes in a header, not the query string
GET api.getAddress.io/get/{id}?api-key={key}GET api.postio.co.uk/v1/address/udprn/{udprn}Billable resolve; the Postio suggestion's udprn replaces getAddress's suggestion id
GET api.getAddress.io/find/{postcode}?api-key={key}GET api.postio.co.uk/v1/address/postcode/{postcode}Lists every delivery point on a postcode
cdn.getaddress.io/scripts/getaddress-autocomplete-*.jsDirect REST calls from your own codeNo vendor script tag required

---

Drop-in Migration Code

Node.js — before (getAddress.io)

const res = await fetch(
  `https://api.getAddress.io/autocomplete/${encodeURIComponent(term)}?api-key=${KEY}`
);
const { suggestions } = await res.json();

// on user pick:
const picked = await fetch(
  `https://api.getAddress.io/get/${suggestions[0].id}?api-key=${KEY}`
);
const address = await picked.json();
form.town_or_city.value = address.town_or_city;
form.postcode.value = address.postcode;

Node.js — after (Postio)

const BASE = 'https://api.postio.co.uk/v1';
const headers = { 'x-api-key': process.env.POSTIO_API_KEY };

// 1. Typeahead — free, safe for keystrokes
async function search(q, maxResults = 10) {
  const res = await fetch(`${BASE}/address/search?q=${encodeURIComponent(q)}&max_results=${maxResults}`, { headers });
  const body = await res.json();
  return body.results; // [{ udprn, suggestion }]
}

// 2. Resolve the picked suggestion — billable on a hit
async function resolve(udprn) {
  const res = await fetch(`${BASE}/address/udprn/${udprn}`, { headers });
  const body = await res.json();
  return body.results[0];
}

// 3. Or list every delivery point on a postcode
async function byPostcode(postcode) {
  const res = await fetch(`${BASE}/address/postcode/${encodeURIComponent(postcode)}`, { headers });
  const body = await res.json();
  return body.results; // empty array (not an error) if no delivery points
}

// Field mapping from the getAddress.io shape you had before:
function mapLegacy(a) {
  return {
    line1: a.address_line_1,
    line2: a.address_line_2,
    town: a.post_town,        // was town_or_city
    postcode: a.postcode
  };
}

Python — before (getAddress.io)

import requests

r = requests.get(
    f"https://api.getAddress.io/autocomplete/{term}",
    params={"api-key": KEY},
)
suggestions = r.json()["suggestions"]

addr = requests.get(
    f"https://api.getAddress.io/get/{suggestions[0]['id']}",
    params={"api-key": KEY},
).json()

Python — after (Postio)

import requests

BASE = "https://api.postio.co.uk/v1"
HEADERS = {"x-api-key": POSTIO_API_KEY}

def search(q: str, max_results: int = 10) -> list:
    """Typeahead — free."""
    r = requests.get(f"{BASE}/address/search", headers=HEADERS,
                     params={"q": q, "max_results": max_results})
    r.raise_for_status()
    return r.json()["results"]  # [{"udprn": int, "suggestion": str}]

def resolve(udprn: int) -> dict:
    """Resolve a picked suggestion — billable on a hit."""
    r = requests.get(f"{BASE}/address/udprn/{udprn}", headers=HEADERS)
    r.raise_for_status()
    return r.json()["results"][0]

def by_postcode(postcode: str) -> list:
    """Every delivery point on a postcode. Empty list if none."""
    r = requests.get(f"{BASE}/address/postcode/{postcode}", headers=HEADERS)
    r.raise_for_status()
    return r.json()["results"]

The fifth formatted_address element

getAddress.io's fifth formatted_address element has no Postio equivalent. Royal Mail removed that field from PAF in 2000; post town and postcode are the only mandatory elements of a correct UK postal address. Postio returns district and ward (ONS administrative geography) instead — useful for analytics and routing, but not part of the postal address. Either drop the column, or map it from district with that caveat.

---

Pricing Mechanics: Pay-As-You-Go vs Rigid Tiers

Legacy address APIs frequently impose monthly lookup buckets with hard cut-offs or expensive overage charges. Direct modern providers offer pay-as-you-go per-lookup billing.

  • Predictable unit economics: Pay strictly for resolved queries without paying for unused tier allocations.
  • Zero overage lockouts: Forms and checkout flows remain live during seasonal traffic spikes.
  • Free search: Postio's /address/search typeahead is free; you are billed only when a suggestion is resolved.

---

Migration Checklist

  1. Audit current endpoints: Identify all frontend widgets (cdn.getaddress.io) and backend API calls (api.getAddress.io).
  2. Map the schema: Use the field table above — formatted_address array → named address_line_* fields, town_or_city → post_town, and decide what to do with the fifth formatted_address element.
  3. Verify sub-building requirements: Test edge cases such as multi-occupancy flats, business parks, and newly assigned postcodes.
  4. Switch API keys and endpoints: Replace the getAddress.autocomplete or /find JavaScript script tags with the Node.js or Python calls above, key in the x-api-key header.
  5. Validate address line formatting: Ensure your database schema correctly receives address_line_1, address_line_2, post_town, postcode.

Ready in under a minute.

Sign up, grab a key, paste it in. Your first hundred lookups are on us.