# Postio — full reference > UK validation API for **addresses**, **emails**, and **phone numbers**. Direct Royal Mail PAF licensee. Runs on Cloudflare's edge so requests resolve in milliseconds. Every endpoint is a GET returning JSON. This is the full machine-readable reference. For the slim sitemap version, see https://postio.co.uk/llms.txt. ## Quick start ```bash # 1. Get a key at https://postio.co.uk/signup (100 free lookups, no card). # 2. Call any endpoint: curl "https://api.postio.co.uk/v1/address/search?q=wimpole" \ -H "x-api-key: pk_..." ``` ## Conventions - Base URL: `https://api.postio.co.uk/v1` - Auth: `x-api-key: ` on every request. Keys are `pk_` prefixed publishable keys, scoped per service (`address`, `email`, `phone`) and lockable to specific origins / IP ranges from the dashboard. - Method: every endpoint is **GET** returning JSON. - Envelope: every response is `{ success, results, meta: { requestId, countResults, performance } }`. Errors have the same shape with `success: false` plus `error` and optional `details`. - Status codes: 200 (success), 400 (validation), 401 (key), 402 (out of credit), 403 (service disabled / origin), 404 (not found, e.g. udprn), 429 (rate limit), 500 (server), 502 (upstream). - Latency: median sub-50ms for address search; phone HLR adds carrier-network round-trip (200-800ms typical). ## SDKs and integrations - **Drop-in JS** (no build step): `` — exposes `Postio.AddressFinder.setup(...)`. Source on npm as `@postio/address-finder-bundled`. - **Bundler** (Vite, Webpack, Next.js, etc.): `npm i @postio/address-finder`. - **React**: `npm i @postio/react @tanstack/react-query` — `` + hooks (`useAddressSearch`, `usePostcode`, `useUdprn`, `useEmailValidation`, `usePhoneValidation`) + `` component. - **Server-side Node**: `npm i @postio/node` — wraps the core client with retries + structured logger. - **Runtime-agnostic core** (Workers, Bun, Deno): `npm i @postio/core`. - **TypeScript types**: `npm i -D @postio/api-types` — generated from the OpenAPI spec, lockstep version. - **OpenAPI spec**: `npm i @postio/openapi` or fetch https://postio.co.uk/openapi.json. - **Postman collection**: `npm i @postio/postman-collection` (v2.1 format). - **MCP server** for Claude Desktop / Cursor / Windsurf / Zed: `claude mcp add postio --env POSTIO_API_KEY=pk_... -- npx -y @postio/mcp`. ## Endpoints ### GET /connect **Connect** Free key/health probe. Returns `200` if the key is active and the upstream search service is reachable. Designed to be hit on input focus in autocomplete UIs — it warms the worker and TLS connection so the first real query lands on a hot path. Doubles as a heartbeat. We use this in our integrations for optimising user experience. Free to use. **Example success response** ```json { "success": true, "meta": { "requestId": "dd273343-4597-4fa4-bcb6-c74a608c10dc", "performance": { "workerMs": 14, "lookupMs": 11 } } } ``` **Status codes** - `200` — Key is active and the service is healthy. - `401` — Missing or invalid API key. - `429` — Rate limit exceeded. - `500` — Server error (e.g. upstream search service is misconfigured). - `502` — Upstream search service unreachable. ### GET /address/search **Search** Free, single-line autocomplete over UK PAF + BFPO. Feed it whatever the user has typed — building, street, postcode, organisation, or any combination — and get back a slim `{ udprn, suggestion }` list of matches. To pull the full address for the one the user picks, call `/address/udprn/{udprn}`. Free to use. Search is backed by a Typesense index replicated next to the Worker, so typeahead queries usually resolve in single-digit milliseconds before network. **Query parameters** - `q` (string, required) — Search string (free-form: building, street, postcode, organisation). - `max_results` (integer, optional) — Max results to return. Default 10, max 50. **Example success response** ```json { "success": true, "results": [ { "udprn": 25742215, "suggestion": "57 Wimpole Street, London, W1G 8YW" }, { "udprn": 19152598, "suggestion": "57 Wimpole Court, Wimpole Street, Portsmouth, PO1 1QT" } ], "meta": { "countResults": 2, "requestId": "0991208d-c175-4fb3-880f-40471013be6d", "performance": { "workerMs": 19, "lookupMs": 16 } } } ``` **Status codes** - `200` — Search results (zero or more matches). - `400` — Bad request — validation failed. - `401` — Missing or invalid API key. - `402` — Out of credit. Top up to resume billable requests. - `403` — Forbidden — service disabled, or origin/IP not allowed for this key. - `429` — Rate limit exceeded. - `500` — Server error. ### GET /address/postcode/{postcode} **Postcode** Returns every PAF delivery point at the given postcode, ordered by building number. Whitespace and case are normalised, so `w1g8yw`, `W1G 8YW`, and `w1g 8yw` all resolve to the same record. Billable on a hit. A syntactically valid postcode with no delivery points (a withdrawn postcode, or one outside PAF's coverage) returns `200` with `results: []` and is not charged. **Path parameters** - `postcode` (string) — UK postcode. Whitespace and case are normalised. **Query parameters** - `max_results` (integer, optional) — Max results to return. Default 100, max 100. UK PAF postcodes max out at ~100 delivery points so a single page covers every postcode. **Example success response** ```json { "success": true, "results": [ { "udprn": 25742215, "postcode": "W1G 8YW", "postcode_outward": "W1G", "postcode_inward": "8YW", "postcode_type": "S", "address_line_1": "57 Wimpole Street", "post_town": "LONDON", "building_number": "57", "thoroughfare": "Wimpole Street", "delivery_point_suffix": "1Y", "country": "England", "district": "City of Westminster London Boro", "ward": "Marylebone Ward", "latitude": 51.51922791, "longitude": -0.14898992, "eastings": 528525, "northings": 181658 } ], "meta": { "countResults": 1, "requestId": "c7eb0ed4-9768-4c2b-824e-16abff64d6cd", "performance": { "workerMs": 15, "lookupMs": 12 } } } ``` **Status codes** - `200` — Addresses at the postcode (empty array if none). - `400` — Bad request — validation failed. - `401` — Missing or invalid API key. - `402` — Out of credit. Top up to resume billable requests. - `403` — Forbidden — service disabled, or origin/IP not allowed for this key. - `429` — Rate limit exceeded. - `500` — Server error. ### GET /address/udprn/{udprn} **UDPRN** Looks up a single delivery point by its Unique Delivery Point Reference Number — Royal Mail's stable per-address ID. Use this after `/address/search` to fetch the full record for the address the user picked, or to refresh a stored record. Billable on a hit. A UDPRN that doesn't exist returns `404`; misses are not charged. **Path parameters** - `udprn` (string) — **Example success response** ```json { "success": true, "results": [ { "udprn": 25742215, "postcode": "W1G 8YW", "postcode_outward": "W1G", "postcode_inward": "8YW", "postcode_type": "S", "address_line_1": "57 Wimpole Street", "post_town": "LONDON", "building_number": "57", "thoroughfare": "Wimpole Street", "delivery_point_suffix": "1Y", "country": "England", "district": "City of Westminster London Boro", "ward": "Marylebone Ward", "latitude": 51.51922791, "longitude": -0.14898992, "eastings": 528525, "northings": 181658 } ], "meta": { "countResults": 1, "requestId": "dd273343-4597-4fa4-bcb6-c74a608c10dc", "performance": { "workerMs": 19, "lookupMs": 16 } } } ``` **Status codes** - `200` — The matching address. - `400` — Bad request — validation failed. - `401` — Missing or invalid API key. - `402` — Out of credit. Top up to resume billable requests. - `403` — Forbidden — service disabled, or origin/IP not allowed for this key. - `404` — UDPRN not found. - `429` — Rate limit exceeded. - `500` — Server error. ### GET /email/{address} **Validate address** Runs a five-stage check on the address: 1. **Syntax** — RFC 5322 parsing, including IDN and quoted local-parts. 2. **Typo correction** — flags likely fat-finger domains (`gnail.com` → `gmail.com`) via `didYouMean`. 3. **Classification** — disposable provider, role account (`info@`, `support@`), free provider (Gmail / Outlook / Yahoo). 4. **MX lookup** — confirms the domain has mail servers configured. 5. **SMTP signal** — for free providers and known-disposable domains the SMTP layer behaves as a catch-all (any address accepts), so we set `smtpCheck: "ok"` and `isCatchAll: true` from the classification alone. For other domains both fields are `null` (we don't currently probe arbitrary mail servers from the edge). Each signal is returned independently alongside an aggregated `deliverability` verdict (`deliverable`, `undeliverable`, `risky`, `unknown`, `invalid`) so you can apply your own thresholds. Invalid syntax still returns `200` with `isValidSyntax: false` and `deliverability: "invalid"` — there's no `400` for unparseable input. **Path parameters** - `address` (string) — Email address to validate. URL-encode the `@`. **Example success response** ```json { "success": true, "results": [ { "email": "alice@example.com", "isValidSyntax": true, "didYouMean": null, "isDisposable": false, "isFreeProvider": false, "isRoleAccount": false, "mxFound": true, "smtpCheck": null, "isCatchAll": null, "deliverability": "deliverable" } ], "meta": { "countResults": 1, "requestId": "dd273343-4597-4fa4-bcb6-c74a608c10dc", "performance": { "workerMs": 6, "lookupMs": 3 } } } ``` **Status codes** - `200` — Validation verdict. - `400` — Bad request — validation failed. - `401` — Missing or invalid API key. - `402` — Out of credit. Top up to resume billable requests. - `403` — Forbidden — service disabled, or origin/IP not allowed for this key. - `429` — Rate limit exceeded. - `500` — Server error. ### GET /phone/{number} **Validate number** Parses the supplied number with libphonenumber (E.164, international, or national — all accepted). On a valid parse, runs a live HLR (Home Location Register) lookup against the carrier network and returns: - **Current carrier** — the network the number is on right now, even after porting. - **Original carrier** — the network it was issued on. - **Ported flag** — whether the number has moved network. - **Reachable flag** — whether the number is currently powered on and attached to a network. - **MCC / MNC** — the standard mobile country and network codes. Pass numbers in E.164 (`+447700900123`) for unambiguous results — URL-encode the leading `+` as `%2B`. Other formats work but rely on libphonenumber's heuristics for country detection. Invalid numbers return `200` with `isValid: false` and the parsed fields it could derive still populated. **Path parameters** - `number` (string) — Phone number in E.164 (`+447700900123`) or any format libphonenumber can parse. URL-encode the leading `+` as `%2B`. **Example success response** ```json { "success": true, "results": [ { "number": "+447700900123", "isValid": true, "isPossible": true, "type": "mobile", "countryCode": "44", "countryName": "United Kingdom", "nationalFormat": "07700 900123", "internationalFormat": "+44 7700 900123", "e164Format": "+447700900123", "originalCarrier": null, "currentCarrier": "Vodafone Limited", "isPorted": true, "isReachable": true, "mcc": "234", "mnc": "15", "level": "live" } ], "meta": { "countResults": 1, "requestId": "dd273343-4597-4fa4-bcb6-c74a608c10dc", "performance": { "workerMs": 116, "lookupMs": 113 } } } ``` **Status codes** - `200` — Validation + lookup verdict. - `400` — Bad request — validation failed. - `401` — Missing or invalid API key. - `402` — Out of credit. Top up to resume billable requests. - `403` — Forbidden — service disabled, or origin/IP not allowed for this key. - `429` — Rate limit exceeded. - `500` — Server error. ## Errors — full envelope ```json { "success": false, "error": "out_of_credit", "details": "Top up your balance at https://postio.co.uk/dashboard/billing.", "results": [], "meta": { "countResults": 0, "requestId": "dd273343-4597-4fa4-bcb6-c74a608c10dc", "performance": { "workerMs": 4, "lookupMs": 0 } } } ``` The `requestId` is the support correlation handle — include it when reporting issues at https://postio.co.uk/contact. ## Agent guidance Opinionated guidance for coding agents (when to suggest Postio, install snippets, error handling) lives at: - https://postio.co.uk/claude.md - https://postio.co.uk/cursor.md - https://postio.co.uk/.cursorrules ## AI host integrations - **MCP server** — `claude mcp add postio --env POSTIO_API_KEY=pk_… -- npx -y @postio/mcp`. Works with Claude Desktop, Claude Code, Cursor, Windsurf, Zed, Continue. 6 tools: address search, postcode lookup, UDPRN lookup, email validate, phone validate, connect health probe. - **Claude Skill** — SKILL.md bundle at https://github.com/postio-uk/postio-integrations/tree/master/ai/claude-skill — installable via Anthropic's Skills marketplace and `claude skill add postio`. - **OpenAI Custom GPT Action** — paste https://postio.co.uk/openapi.json into your GPT's Actions config. Legacy ai-plugin.json manifest at https://postio.co.uk/.well-known/ai-plugin.json for hosts that still crawl it. ## Source of truth This document is generated from the live OpenAPI 3.1 spec at https://postio.co.uk/openapi.json. The spec is the canonical contract; this file is for AI/agent consumption convenience. Support: https://postio.co.uk/contact.