ng-postcode
Python library for Nigeria's National Digital Alphanumeric Postcode System (NDAPS), the building-level postcode NIPOST launched in October 2026. Parse, validate and format postcodes offline, and call the postcode.gov.ng API for lookup, autocomplete and reverse geocoding.
Also available for Rust as ng-postcode on crates.io for JavaScript as ng-postcode-js on npm, and for Java as ng-postcode-api on Maven Central.
pip install ng-postcode # offline core, no dependencies
pip install "ng-postcode[client]" # adds the API client (httpx)
Format
An 11-character code in five segments: state, LGA, district, area, building unit.
| Form | Example | Use it to |
|---|---|---|
| Canonical | EK-01-A03-FK-01 |
Write a code and pass it between systems |
| Spaced | EK 01 A03 FK 01 |
Show a code to people |
| Compact | EK01A03FK01 |
Store and compare codes |
Compact form as a regular expression: ^[A-Z]{2}(0[1-9]|[1-9][0-9])[A-Z0-9]{3}[A-Z]{2}(0[1-9]|[1-9][0-9])$
Offline
Expected failures are returned as values, so the type checker makes you handle them.
from ng_postcode import Postcode, Segment, parse
match parse("ek 01 a03 fk 01"):
case Postcode() as code:
print(code) # EK-01-A03-FK-01
print(code.compact) # EK01A03FK01, store this
print(code.spaced) # EK 01 A03 FK 01
print(code.prefix(Segment.AREA)) # EK-01-A03-FK
case error:
print(error) # e.g. "invalid lga segment"
parseaccepts hyphenated, spaced or compact input in either case.parse_lenientfirst swaps look-alikes that cannot occur where they stand (O/0,I/1,S/5,B/8) and reports how many it changed.from_segmentsassembles a code from its parts and zero-fills the LGA and unit.parse_prefixreads a code cut off after a segment, such as the districtEK-01-A03, andPostcode.truncatecuts a code down to one. A prefix knows which codes it contains, so it groups them by state, LGA, district, or area.Postcodeis immutable, hashable and sorts by state, LGA, district, area, unit.
A well-formed code is not necessarily assigned to a building. Only the API can confirm that a postcode exists.
Convert a CSV file
The package installs an ng-postcode command that checks a column of postcodes and writes the file back with four columns added.
ng-postcode orders.csv --column postcode --out checked.csv
id,postcode,postcode_compact,postcode_status,postcode_suggestion,postcode_error
1,ek 01 a03 fk 01,EK01A03FK01,valid,,
2,EK-O1-A03-FK-01,,suggested,EK-01-A03-FK-01,invalid lga segment
3,360001,,invalid,,"expected 11 letters and digits, found 6"
4,,,empty,,
validrows carry the compact code to store.suggestedrows only parse after look-alike characters are swapped. The fix is offered for a person to confirm, and the compact column is left empty.invalidrows say what is wrong, andemptyrows had no value.
It works offline and reads one row at a time, so the size of the file does not matter. It never writes over the file it reads, and it stops at a header that already has one of these columns or a row with more cells than the header. Add --confirm to ask NIPOST whether each well-formed code is assigned to a building. That needs pip install "ng-postcode[client]" and your own key in NG_POSTCODE_API_KEY, makes one free level 1 lookup for each distinct code, and adds a postcode_assigned column. When NIPOST's rate limit is reached it waits a minute and asks again; after any other failure the remaining rows are left unconfirmed. A test key may report an assigned code as not assigned, and the command says so when it is given one.
API
from ng_postcode import Postcode, parse
from ng_postcode.api import ApiError, lookup
from ng_postcode.client import Client, TransportError
code = parse("FC-03-B06-AG-12")
assert isinstance(code, Postcode)
with Client(api_key="nipost_live_...") as client:
found = client.send(lookup(code, level=1))
match found:
case ApiError() | TransportError():
print(found) # e.g. "invalid_api_key (401): ..."
case _:
print(found.valid, found.status) # True valid
send returns the typed response, an ApiError (for example auth_required or insufficient_credits) or a TransportError. AsyncClient has the same interface for asyncio.
ng_postcode.api covers lookup, autocomplete, reverse geocoding and nearby search. Each function returns a Request value and decode turns a status and body into a typed result, so it works with any HTTP client without the client extra.
Get a key from the NIPOST developer dashboard. Level 1 lookups confirm a code is assigned; levels 2 and up return address details and consume credits.
License
MIT
Metadata
Release files for ng-postcode 0.3.5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ng_postcode-0.3.5.tar.gz | 24.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ng_postcode-0.3.5-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 43.7 kB
Release files / ng_postcode-0.3.5.tar.gz
| Download URL | ng_postcode-0.3.5.tar.gz |
|---|---|
| Size | 24.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4e1a56c9fa4f7ddde007185af77dccf73748e6b9faa4483ad3d44e3836a4c3a9
|
|
BLAKE2b-256 checksum How to use checksums |
29c3e455f69a383eea8764bcd17acc7f8b1c21b88d2101019152262a943e5254
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logRelease files / ng_postcode-0.3.5-py3-none-any.whl
| Download URL | ng_postcode-0.3.5-py3-none-any.whl |
|---|---|
| Size | 19.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ddd6a44bce8538b7efc0d8cad395a96761bca286ae2be9b6cab408b3daa71496
|
|
BLAKE2b-256 checksum How to use checksums |
6729edf48c462e3ce1e1908bd949b7b09444a864dd42105adfa166eecdace207
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency log