Skip to content

postio-uk/postio-python

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Postio Python SDK

PyPI Python versions License: MIT

Python SDK for Postio — the UK validation API for addresses, emails and phone numbers. Sync + async, type-safe via Pydantic v2. Backed by Royal Mail PAF and Ordnance Survey.

First time? Sign up free — first 100 lookups on us, no card needed.

Install

pip install postio

Requires Python 3.10+.

30-second example

from postio import PostioClient

client = PostioClient(api_key="pk_...")  # or set POSTIO_API_KEY

result = client.address.search("downing street")
for hit in result.results:
    print(hit.udprn, hit.suggestion)

print("request id:", result.meta.requestId)

Async

import asyncio
from postio import AsyncPostioClient

async def main():
    async with AsyncPostioClient(api_key="pk_...") as client:
        result = await client.address.postcode("SW1A 2AA")
        for addr in result.results:
            print(addr.address_line_1, addr.post_town)

asyncio.run(main())

API

Method Returns Notes
client.address.search(q, max_results=None) AddressSearchEnvelope Free-text typeahead lookup
client.address.postcode(postcode, max_results=None) AddressPostcodeEnvelope Full addresses for a postcode
client.address.udprn(udprn) AddressUdprnEnvelope Single address by UDPRN
client.email.validate(address) EmailEnvelope Syntax + MX + SMTP + deliverability
client.phone.validate(number) PhoneEnvelope E.164 format + carrier + reachability
client.connect() ConnectSuccess Free health probe

AsyncPostioClient exposes the same surface, awaitable.

Errors

Every non-2xx response raises a typed exception. PostioError is the base.

from postio import (
    PostioClient,
    PostioInvalidKey,       # 401
    PostioOutOfCredit,      # 402
    PostioForbidden,        # 403
    PostioNotFound,         # 404
    PostioValidationError,  # 400 / 422
    PostioRateLimit,        # 429 — has .retry_after
    PostioServerError,      # 5xx — retried by default
    PostioTimeout,
    PostioConnectionError,
)

try:
    client.address.postcode("not-a-postcode")
except PostioValidationError as err:
    print(err.status, err.code, err.request_id, err.envelope)

Every error carries status, code, details, request_id, and the raw envelope. The request_id is the support handle to quote when reporting issues at https://postio.co.uk/contact.

Configuration

from postio import PostioClient, RetryConfig

client = PostioClient(
    api_key="pk_...",
    base_url="https://api.postio.co.uk/v1",   # default
    timeout=10.0,                              # seconds
    retries=2,                                 # or RetryConfig(...) or None to disable
    headers={"x-tracking-id": "..."},          # extra headers, merged
)

Default retry policy: 2 retries, exponential backoff with full jitter (0.5s → 8s cap), retries on 408, 409, 429, 5xx, and network/timeout errors. Pass retries=None to disable.

Frameworks

The SDK is framework-agnostic but ships classifiers for Django, Flask, and FastAPI. Drop the client on your app state at startup:

FastAPI

from contextlib import asynccontextmanager
from fastapi import FastAPI
from postio import AsyncPostioClient

@asynccontextmanager
async def lifespan(app: FastAPI):
    app.state.postio = AsyncPostioClient()  # reads POSTIO_API_KEY
    yield
    await app.state.postio.close()

app = FastAPI(lifespan=lifespan)

Django — instantiate one PostioClient in apps.py's ready() and stash it on a module-level singleton; close it in a shutdown signal handler.

FlaskPostioClient on app.extensions["postio"], close in app.teardown_appcontext.

Links

License

MIT — see LICENSE.

Postio is a trading name of Onno Group Limited, registered in England & Wales (company no. 08622799). Registered office: Suite 22 Trym Lodge, 1 Henbury Road, Westbury-On-Trym, Bristol BS9 3HQ.

Packages

 
 
 

Contributors

Languages