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 / Metric | Direct Royal Mail PAF (e.g. Postio) | Open-Data / Compiled APIs (e.g. getAddress.io relaunch) |
|---|---|---|
| Primary Data Source | Royal Mail Postcode Address File (PAF) | Compiled open data (Ordnance Survey / Open Names) |
| Address Count | ~31+ million UK delivery points | Variable coverage depending on public dataset sync |
| Update Cadence | Daily direct sync from Royal Mail | Periodic public release cycles |
| Licensing Compliance | Direct Royal Mail licensee | US entity (GetAddress, LLC, Dover DE) compiled model |
| Billing Model | Transparent per-lookup / per-session | Tiered plans / subscription quotas |
| Autocomplete Support | Yes (session-based keystrokes) | Yes (suggest + get step) |
| Coverage Accuracy | 100% postal delivery points | High 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 field | Postio field | Note |
|---|---|---|
postcode | postcode | Same, formatted with a space |
formatted_address[0..2] | address_line_1 / address_line_2 / address_line_3 | Array becomes named lines |
formatted_address[3] / town_or_city | post_town | Royal Mail post town |
formatted_address[4] | *(no equivalent — see note below)* | Postio returns district and ward (ONS geography) instead |
thoroughfare | thoroughfare | Same name |
building_number | building_number | Same name |
latitude, longitude | latitude, longitude | Postio adds eastings, northings |
| *(none)* | udprn | Royal Mail per-delivery-point ID |
| *(none)* | organisation_name, sub_building_name, po_box | PAF 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 call | Postio call | Notes |
|---|---|---|
GET api.getAddress.io/autocomplete/{term}?api-key={key} | GET api.postio.co.uk/v1/address/search?q={query} with x-api-key header | Postio 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-*.js | Direct REST calls from your own code | No 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/searchtypeahead is free; you are billed only when a suggestion is resolved.
---
Migration Checklist
- Audit current endpoints: Identify all frontend widgets (
cdn.getaddress.io) and backend API calls (api.getAddress.io). - Map the schema: Use the field table above —
formatted_addressarray → namedaddress_line_*fields,town_or_city→post_town, and decide what to do with the fifthformatted_addresselement. - Verify sub-building requirements: Test edge cases such as multi-occupancy flats, business parks, and newly assigned postcodes.
- Switch API keys and endpoints: Replace the
getAddress.autocompleteor/findJavaScript script tags with the Node.js or Python calls above, key in thex-api-keyheader. - 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.