Python wrapper for bpost External Mailing Address Proofing API with pydantic models and httpx sync/async clients.
Project description
bpost-address-validator
This is a lightweight Python wrapper for bpost's External Mailing Address Proofing API endpoint POST /<env>/externalMailingAddressProofingRest/validateAddresses.
Environments have different base URLs and path prefixes. Presets are provided for convenience:
- prod:
https://api.mailops.bpost.cloud/roa-info/... - test (NP):
https://api.mailops-np.bpost.cloud/roa-info-st2/... - uat (NP):
https://api.mailops-np.bpost.cloud/roa-info-ac/...
It provides:
- Synchronous and asynchronous clients powered by
httpx - Pydantic v2 models for request/response envelopes (flexible inner structure; extra fields allowed)
- A simple, typed interface and explicit error handling via
ApiError - NEW: Convenience functions for simple address validation without complex model construction
- NEW: Factory functions for easy model creation
- NEW: Validation option presets
- NEW: Response helper properties for easier result access
Features
- Sync client:
BpostClient - Async client:
AsyncBpostClient - Automatic
x-api-keyheader handling - Default base URL pointing to bpost NP environment (you should usually provide a preset explicitly)
- Uniform error type
ApiErrorfor transport and non-200 responses - Connection pool configuration for high-throughput scenarios
- Simple convenience methods:
validate_address_simple(),validate_addresses_batch() - Factory functions:
create_structured_address(),create_simple_request(), etc. - Validation presets:
ValidationPresets.FULL,ValidationPresets.WITH_SUGGESTIONS, etc. - Response helpers:
response.results,result.is_valid,result.score, etc.
Requirements
- Python 3.12+
Installation
pip install bpost-address-validator
From source (editable):
pip install -e .
Quick start (simple API)
The simplest way to validate an address:
from bpost_address_validator import BpostClient
with BpostClient(api_key="<YOUR_API_KEY>", preset="test") as client:
# Validate a single address with simple parameters
response = client.validate_address_simple(
street_name="Muntstraat",
street_number="1",
postal_code="1000",
municipality_name="Bruxelles"
)
# Easy access to results
if response.first_result and response.first_result.is_valid:
print(f"Valid! Score: {response.first_result.score}")
else:
print(f"Errors: {response.first_result.errors}")
Quick start (batch validation)
Validate multiple addresses at once:
from bpost_address_validator import BpostClient
addresses = [
{
"street_name": "Muntstraat",
"street_number": "1",
"postal_code": "1000",
"municipality_name": "Bruxelles"
},
{
"address_lines": ["Rue de la Loi 16", "1000 Bruxelles"]
}
]
with BpostClient(api_key="<YOUR_API_KEY>", preset="test") as client:
response = client.validate_addresses_batch(addresses)
for result in response.results:
print(f"ID: {result.id}, Valid: {result.is_valid}, Score: {result.score}")
Quick start (with validation presets)
Use predefined validation option presets:
from bpost_address_validator import BpostClient, ValidationPresets
with BpostClient(api_key="<YOUR_API_KEY>", preset="test") as client:
response = client.validate_address_simple(
street_name="Muntstraat",
street_number="1",
postal_code="1000",
municipality_name="Bruxelles",
options=ValidationPresets.FULL # All validation options enabled
)
print(response.model_dump())
Available presets:
ValidationPresets.BASIC- Minimal validationValidationPresets.FULL- All validation options enabledValidationPresets.WITH_SUGGESTIONS- Include address suggestionsValidationPresets.WITH_FORMATTING- Include formatting informationValidationPresets.WITH_GEO- Include geo-location data
Quick start (factory functions)
Use factory functions for easy model creation:
from bpost_address_validator import (
BpostClient,
create_structured_address,
create_address_to_validate,
create_simple_request,
ValidationPresets,
)
# Create a structured address
address = create_structured_address(
street_name="Muntstraat",
street_number="1",
postal_code="1000",
municipality_name="Bruxelles"
)
# Create an address to validate
addr_to_validate = create_address_to_validate(
id="1",
street_name="Muntstraat",
street_number="1",
postal_code="1000",
municipality_name="Bruxelles"
)
# Create a complete request
request = create_simple_request([addr_to_validate], ValidationPresets.FULL)
with BpostClient(api_key="<YOUR_API_KEY>", preset="test") as client:
response = client.validate_addresses(request)
print(response.model_dump())
Quick start (traditional sync API)
The traditional way using full model construction:
from bpost_address_validator import (
BpostClient,
ValidateAddressesRequest,
ValidateAddressesRequestContent,
AddressToValidateList,
AddressToValidate,
ValidateAddressOptions,
PostalAddress,
DeliveryPointLocation,
StructuredDeliveryPointLocation,
PostalCodeMunicipality,
StructuredPostalCodeMunicipality,
)
req = ValidateAddressesRequest(
validate_addresses_request=ValidateAddressesRequestContent(
address_to_validate_list=AddressToValidateList(
address_to_validate=[
AddressToValidate(
id="1",
dispatching_country_iso_code="BE",
delivering_country_iso_code="BE",
postal_address=PostalAddress(
delivery_point_location=DeliveryPointLocation(
structured_delivery_point_location=StructuredDeliveryPointLocation(
street_name="Muntstraat",
street_number="1",
)
),
postal_code_municipality=PostalCodeMunicipality(
structured_postal_code_municipality=StructuredPostalCodeMunicipality(
postal_code="1000",
municipality_name="Bruxelles",
)
),
),
)
]
),
validate_address_options=ValidateAddressOptions(
include_submitted_address=True,
include_suggestions=True,
include_formatting=True,
),
)
)
with BpostClient(api_key="<YOUR_API_KEY>", preset="test") as client:
resp = client.validate_addresses(req)
print(resp.model_dump())
Quick start (async)
All convenience methods are also available in async:
import asyncio
from bpost_address_validator import AsyncBpostClient, ValidationPresets
async def main():
async with AsyncBpostClient(api_key="<YOUR_API_KEY>", preset="test") as client:
# Simple validation
response = await client.validate_address_simple(
street_name="Muntstraat",
street_number="1",
postal_code="1000",
municipality_name="Bruxelles",
options=ValidationPresets.WITH_SUGGESTIONS
)
if response.first_result and response.first_result.is_valid:
print(f"Valid! Score: {response.first_result.score}")
asyncio.run(main())
Environment configuration
You can configure the target environment in three ways:
- Use a preset (recommended):
# prod
BpostClient(api_key="...", preset="prod")
# test (NP, st2)
BpostClient(api_key="...", preset="test")
# uat (NP, ac)
BpostClient(api_key="...", preset="uat")
- Manually set base URL and path prefix:
# Equivalent to preset="prod"
BpostClient(
api_key="...",
base_url="https://api.mailops.bpost.cloud",
environment="roa-info",
)
# Equivalent to preset="test"
BpostClient(
api_key="...",
base_url="https://api.mailops-np.bpost.cloud",
environment="roa-info-st2",
)
# Equivalent to preset="uat"
BpostClient(
api_key="...",
base_url="https://api.mailops-np.bpost.cloud",
environment="roa-info-ac",
)
- Advanced: Provide your own
httpxclient with custom base URL and headers. In that case, passclient=to the constructor and omitapi_keyor headers accordingly.
Performance tuning (high-throughput scenarios)
Configure connection pool settings for high-volume usage:
from bpost_address_validator import BpostClient
with BpostClient(
api_key="<YOUR_API_KEY>",
preset="test",
max_connections=200, # Max total connections (default: 100)
max_keepalive_connections=50, # Max keep-alive connections (default: 20)
timeout=60.0, # Request timeout in seconds (default: 30.0)
) as client:
# Your high-volume validation code here
pass
Using response helper properties
Response and result objects have convenient helper properties:
from bpost_address_validator import BpostClient
with BpostClient(api_key="<YOUR_API_KEY>", preset="test") as client:
response = client.validate_address_simple(
street_name="Muntstraat",
street_number="1",
postal_code="1000",
municipality_name="Bruxelles"
)
# Access results easily
print(f"Total results: {len(response.results)}")
# Get first result
result = response.first_result
if result:
# Check validity
print(f"Is valid: {result.is_valid}")
# Get score
print(f"Score: {result.score}")
# Get errors
print(f"Errors: {result.errors}")
# Get validated addresses
for addr in result.validated_addresses:
print(f"Address: {addr.postal_address}")
Passing a raw dict payload
You can still use raw dict payloads if needed:
from bpost_address_validator import BpostClient
payload = {
"ValidateAddressesRequest": {
"AddressToValidateList": {
"AddressToValidate": [
{
"@id": "1",
"DispatchingCountryISOCode": "BE",
"DeliveringCountryISOCode": "BE",
"PostalAddress": {
"DeliveryPointLocation": {
"StructuredDeliveryPointLocation": {
"StreetName": "Muntstraat",
"StreetNumber": "1",
}
},
"PostalCodeMunicipality": {
"StructuredPostalCodeMunicipality": {
"PostalCode": "1000",
"MunicipalityName": "Bruxelles",
}
},
},
}
]
},
"ValidateAddressOptions": {"IncludeFormatting": True},
}
}
with BpostClient(api_key="<YOUR_API_KEY>", preset="test") as client:
resp = client.validate_addresses(payload)
print(resp.validate_addresses_response is not None)
API overview
Clients:
BpostClient(api_key, preset="test", timeout=30.0, max_connections=100, max_keepalive_connections=20)validate_addresses(payload)→ ValidateAddressesResponsevalidate_address_simple(...)→ ValidateAddressesResponsevalidate_addresses_batch(addresses, ...)→ ValidateAddressesResponse
AsyncBpostClient(...)- Same methods but async
Convenience Functions:
create_structured_address(street_name, street_number, postal_code, municipality_name, ...)create_unstructured_address(address_lines, locale="nl")create_address_to_validate(id, street_name, ..., OR address_lines, ...)create_simple_request(addresses, options=None)create_batch_request(addresses, dispatching_country="BE", ...)
Validation Presets:
ValidationPresets.BASIC- Minimal validationValidationPresets.FULL- All options enabledValidationPresets.WITH_SUGGESTIONS- Include suggestionsValidationPresets.WITH_FORMATTING- Include formattingValidationPresets.WITH_GEO- Include geo-location
Response Helpers:
ValidateAddressesResponse.results- List of all resultsValidateAddressesResponse.first_result- First result (or None)ValidatedAddressResult.is_valid- Boolean validity checkValidatedAddressResult.errors- List of errorsValidatedAddressResult.validated_addresses- List of validated addressesValidatedAddressResult.score- Validation score
Models (selected):
ValidateAddressesRequestValidateAddressesRequestContentAddressToValidateListAddressToValidateAddressBlockLines,UnstructuredAddressLineItemPostalAddress,DeliveryPointLocation,StructuredDeliveryPointLocationPostalCodeMunicipality,StructuredPostalCodeMunicipalityValidateAddressOptionsValidateAddressesResponse
Errors:
ApiError— for transport errors and non-200 responses. Inspectstatus_codeanddetailsfor context.
Request/Response envelopes
- Request body root:
{"ValidateAddressesRequest": {...}} - Response body root:
{"ValidateAddressesResponse": {...}}
Models allow extra fields to preserve forward-compatibility with upstream changes. See externalMailaddressProofingAPI-OpenAPIspec_v3.yaml for the full schema reference.
Error handling
- Transport issues raise
ApiError("HTTP transport error"). - Non-200 responses raise
ApiErrorwithstatus_codeand parseddetails(JSON when available).
Example:
from bpost_address_validator import BpostClient, ApiError
try:
with BpostClient(api_key="bad-key", preset="test") as client:
client.validate_addresses({"ValidateAddressesRequest": {}})
except ApiError as e:
print(e.status_code, e.details)
Authentication
Provide your API key via the api_key parameter. It is sent as x-api-key automatically.
Typing strategy
- Pydantic v2 models are used for the outer envelopes and key nested structures.
- Public attributes are Pythonic snake_case; JSON aliases match the API (e.g.,
dispatching_country_iso_code→DispatchingCountryISOCode). - Typed models are provided for
address_block_linesandpostal_addressstructures (including delivery point location and postal code/municipality). - Extra keys are allowed across models to avoid breakage if bpost adds new fields.
- For attributes like
"@id", the models expose proper field aliases (e.g.,Field(alias="@id")).
Development
- Python 3.12+
- Runtime deps:
httpx>=0.27.0,pydantic>=2.7.0 - No tests yet — PRs welcome (tests and deeper typed models).
License
MIT — see LICENSE if provided, otherwise follow repository policy.
Disclaimer
This project is not affiliated with or endorsed by bpost. Use at your own risk and comply with bpost's terms.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file bpost_address_validator-0.3.1.tar.gz.
File metadata
- Download URL: bpost_address_validator-0.3.1.tar.gz
- Upload date:
- Size: 4.7 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5b4f7754ba6881ff8f6e2c5aacf3b8d10449af29ece9f033b481324ec93803ce
|
|
| MD5 |
cfa8fb9518495ff0063169925c84f7d4
|
|
| BLAKE2b-256 |
cfdb24fdcc0c8a34d81c1bb82f80dc568e414af80fe5da03e2598fee1e08bbef
|
Provenance
The following attestation bundles were made for bpost_address_validator-0.3.1.tar.gz:
Publisher:
publish.yml on fromej-dev/bpost-address-validator
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bpost_address_validator-0.3.1.tar.gz -
Subject digest:
5b4f7754ba6881ff8f6e2c5aacf3b8d10449af29ece9f033b481324ec93803ce - Sigstore transparency entry: 932382330
- Sigstore integration time:
-
Permalink:
fromej-dev/bpost-address-validator@b8707786c6f2c89fbc96f47c79d02b1cd5ed55a9 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/fromej-dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b8707786c6f2c89fbc96f47c79d02b1cd5ed55a9 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file bpost_address_validator-0.3.1-py3-none-any.whl.
File metadata
- Download URL: bpost_address_validator-0.3.1-py3-none-any.whl
- Upload date:
- Size: 19.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
839a432a38db2f5cae1d457be4205e8ae0a4a5d857930490bc6103666bb3a068
|
|
| MD5 |
a8e810ede575133c821196e671f6d42e
|
|
| BLAKE2b-256 |
4896e70f60a6466dfd1973fb8a6359667057082c1b0478b04ab15984ecc99ad8
|
Provenance
The following attestation bundles were made for bpost_address_validator-0.3.1-py3-none-any.whl:
Publisher:
publish.yml on fromej-dev/bpost-address-validator
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bpost_address_validator-0.3.1-py3-none-any.whl -
Subject digest:
839a432a38db2f5cae1d457be4205e8ae0a4a5d857930490bc6103666bb3a068 - Sigstore transparency entry: 932382404
- Sigstore integration time:
-
Permalink:
fromej-dev/bpost-address-validator@b8707786c6f2c89fbc96f47c79d02b1cd5ed55a9 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/fromej-dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b8707786c6f2c89fbc96f47c79d02b1cd5ed55a9 -
Trigger Event:
workflow_dispatch
-
Statement type: