postfinder
Post offices, parcel lockers and post boxes, as an API. Ask what is nearest a coordinate, look up what a postcode covers, or read a suburb's locations.
- Free and keyless. No account, no quota to buy, nothing to configure
- Answers are cached at the edge, so a repeated question is fast and costs nobody anything
- Every row knows the page it belongs to on postfinder.io, so linking out takes no second request
- No dependencies
pip install postfinder
Quick start
from postfinder import PostFinder
pf = PostFinder()
for p in pf.nearby(-37.7404, 144.9633, category="post-offices", country="australia"):
print(p.name, p.address, f"{p.distance_m} m")
Coburg Post Office 484 Sydney Rd 420 m
Coburg North LPO 12 Elizabeth St 1800 m
Nearest first, within 50km, at most 30 rows. That is the question "where do I post this", which is a different question from "list every post box in Victoria".
The six categories
from postfinder import CATEGORIES # what nearby() accepts
post-offices, post-boxes, express-post-boxes, parcel-lockers,
drop-off-points, collection-points.
A name that is not one of those raises ValueError before a request goes out,
rather than spending a round trip to be told 400.
Typeahead
for hit in pf.search("coburg"):
print(hit.kind, hit.name, hit.postcode, hit.path)
locality Coburg 3058 /en/australia/victoria/coburg/
place Coburg Post Office 3058 /en/australia/victoria/coburg/
Two characters minimum: below that the client returns an empty list without asking, which is what the service answers anyway. Debounce by at least 150ms. Firing on every keystroke spends bandwidth for no better answer.
Postcodes
A postcode is not a suburb. 3058 is Coburg, Coburg North and Merlynston, and an address in any of them is written with the same four digits.
detail = pf.postcode("australia", "3058")
for suburb in detail.localities:
print(suburb.name, suburb.place_count, suburb.path)
The whole country comes in one response, and it is meant to be kept:
index = pf.postcodes("australia") # one request
index.suburbs_in("3058") # no request, and no rescan
index.suburbs_in("2044")
suburbs_in builds its map on first use and keeps it, so resolving a column of
ten thousand postcodes is one pass over the index rather than ten thousand walks
through every state.
A location, and a suburb
place = pf.place("k7m2p9x4")
print(place.place.name, place.locality.name, place.path)
print(place.reviews.summary.count, place.reviews.summary.average)
suburb = pf.locality("australia", "victoria", "coburg")
print(suburb.locality.place_count, suburb.count_of("parcel-lockers"))
for p in suburb.places:
print(p.name, suburb.path_of(p))
A place's public id is permanent. It is minted once and never derived from a source record, so a feed that renumbers its rows does not change it. Store the id, not the name or the path.
Browsing
pf.countries() # every country with pages
pf.country("australia") # its states
pf.region("australia", "victoria", limit=100) # its localities, a page at a time
pf.category("australia", "parcel-lockers") # counts per state, busiest suburbs
Errors
from postfinder import NotFound, BadRequest, RateLimited, PostFinderError
try:
pf.place(stored_id)
except NotFound:
... # retired, or never there
NotFound is ordinary rather than a failure: a suburb with no locations has no
page, and a location that closed is retired. Every error carries the status,
title and detail the service sent, because that is the part that says what to
do about it.
Being a good citizen
The API is free and asks for care in return: around a thousand requests a month from one address, results kept rather than re-fetched, typing debounced. If you need more than that, say what you are building at postfinder.io/en/contact/.
Introduce yourself and it is easier to help you before a rate limit does:
pf = PostFinder(contact="https://example.com/about-our-bot")
Attribution
Locations come from OpenStreetMap (ODbL), localities and postcodes from GeoNames (CC BY 4.0). If you publish what you get back, you carry those credits with it. The sources page names each one.
Also available
| JavaScript and TypeScript | @postfinder/client |
| React | @postfinder/react |
| Vue | @postfinder/vue |
| Go | postfinder-go |
Looking for Australian street addresses rather than locations? That is Locio: G-NAF address autocomplete, validation and geocoding.
Licence
MIT.
Release files for postfinder 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| postfinder-0.1.0.tar.gz | 20.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| postfinder-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 38.2 kB
Release files / postfinder-0.1.0.tar.gz
| Download URL | postfinder-0.1.0.tar.gz |
|---|---|
| Size | 20.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1790de8039169e353fdaeae4bc4d65398df3193dba1766720c77a0284e8c5bc1
|
|
BLAKE2b-256 checksum How to use checksums |
39e25f560a647a0ccfe387cd1d86df3a68f28dfbd5b1bed742295fa66f094ce3
|
| 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 Sep 29, 2026.
Transparency logRelease files / postfinder-0.1.0-py3-none-any.whl
| Download URL | postfinder-0.1.0-py3-none-any.whl |
|---|---|
| Size | 17.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
24d05f3c6f381d2f0fa0680530f5118adcad6c8d4d87743fb5f220efabc1678a
|
|
BLAKE2b-256 checksum How to use checksums |
eab7ab88b4c43c7fdc7214cfbba8ebad3b138ef393b2ec0ae66fd833b81f7ec5
|
| 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 Sep 29, 2026.
Transparency log