Postal Code Validation in Python — Multi-Country Formats & Geolocation
Every country has its own postal code format. Some share the same codes. A five-digit number could be a US ZIP, a German PLZ, or an Italian CAP. Here's how to validate them properly and get geolocation data in the process.
In this guide
1. Why postal code validation is harder than it looks
Unlike email or phone numbers, postal codes have no single international standard. Each country defines its own format, length, and character set. What looks like a valid code in one country may be valid in another — or meaningless in both.
60+ distinct formats
The US uses 5 digits, the UK uses an alphanumeric pattern like "SW1A 1AA", Poland uses "NN-NNN", Japan uses "NNN-NNNN", and Canada alternates letters and digits as "A9A 9A9".
Wildly different lengths
From 3 digits (Iceland) to 10 characters (Iran). Some include spaces or hyphens as mandatory separators.
Cross-country ambiguity
The code "10115" is valid in both the United States (a New York ZIP) and Germany (Berlin). Without a country hint, you cannot know which one the user means.
A simple regex can tell you whether a string is five digits — but it cannot tell you whether that string is a real postal code, which country it belongs to, or where in the world it points.
2. Postal code formats around the world
Here is a sampling of how different countries structure their postal codes. The "N" stands for a digit and "A" for a letter.
| Country | Code | Format | Example | Length |
|---|---|---|---|---|
| United States | US | NNNNN | 90210 | 5 |
| United Kingdom | GB | A9 9AA / A9A 9AA / A99 9AA | SW1A 1AA | 6–8 |
| Germany | DE | NNNNN | 10115 | 5 |
| Poland | PL | NN-NNN | 00-001 | 6 |
| Japan | JP | NNN-NNNN | 100-0001 | 8 |
| Canada | CA | A9A 9A9 | K1A 0B1 | 7 |
| France | FR | NNNNN | 75008 | 5 |
| Brazil | BR | NNNNN-NNN | 01001-000 | 9 |
| India | IN | NNNNNN | 110001 | 6 |
| Netherlands | NL | NNNN AA | 1011 AB | 7 |
3. Why regex per country is a maintenance nightmare
The natural instinct is to build a dictionary of country codes to regex patterns. It works at first — then reality sets in.
import re # Seems manageable at first... POSTAL_REGEX = { "US": re.compile(r"^\d{5}(-\d{4})?$"), "GB": re.compile(r"^[A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2}$", re.IGNORECASE), "PL": re.compile(r"^\d{2}-\d{3}$"), "DE": re.compile(r"^\d{5}$"), "JP": re.compile(r"^\d{3}-\d{4}$"), "CA": re.compile(r"^[A-Z]\d[A-Z] ?\d[A-Z]\d$", re.IGNORECASE), # ... 54 more countries ... } # Problems: # 1. You need 60+ regex patterns, each hand-crafted and tested # 2. You need country detection logic when no country is provided # 3. Regex only checks format — "00000" passes US regex but is not a real ZIP # 4. No geolocation — you still don't know the city or region # 5. Formats change: Royal Mail updates UK postcodes regularly
Maintenance burden
Every time a country reforms its postal system, you need to update your regex dictionary and redeploy.
No existence check
A regex confirms format but cannot tell you whether "99999" is an actual US ZIP code or a placeholder.
No country detection
When the user enters "10115" without specifying a country, your code has no way to determine if it is Berlin, Germany or New York, US.
No geolocation
Even with a valid code and country, regex gives you zero information about the city, region, or coordinates.
4. The right solution
The IsValid Postal Code API handles format validation, country detection, and geolocation in a single request. Pass an optional country code for a definitive answer, or omit it to get all matching countries.
Multi-country format validation
Validates against official format rules for 60+ countries
Country detection
When no country is specified, returns all countries where the code is valid
Geolocation data
Returns city, region, sub-region, latitude, and longitude when a country is provided
60+ countries supported
From US ZIP codes to Japanese postal codes, all maintained and updated
Full parameter reference and response schema: Postal Code Validation API docs →
5. Python code example
Using the IsValid SDK or the requests library.
from isvalid import create_client iv = create_client(api_key="YOUR_API_KEY") # ── With country hint ──────────────────────────────────────────────────────── # Get definitive validation + geolocation for a Polish postal code pl_result = iv.postal_code("00-001", country_code="PL") print(pl_result["valid"]) # True print(pl_result["country"]) # "PL" print(pl_result["format"]) # "NN-NNN" print(pl_result["location"]["city"]) # "Warszawa" print(pl_result["location"]["lat"]) # 52.23 print(pl_result["location"]["lon"]) # 21.01 # ── Without country hint ───────────────────────────────────────────────────── # Detect which countries match the code ambiguous = iv.postal_code("90210") print(ambiguous["valid"]) # True print(len(ambiguous["matchingCountries"])) # may match US, etc. print(ambiguous["matchingCountries"][0]["country"]) # "US" print(ambiguous["matchingCountries"][0]["format"]) # "NNNNN"
In a shipping form handler — validate the postal code against the selected country:
# views.py (Django) from django.http import JsonResponse from django.views.decorators.http import require_POST import json @require_POST def checkout(request): body = json.loads(request.body) postal_code = body.get("postalCode", "") country = body.get("country", "") try: check = validate_postal_code(postal_code, country_code=country) except requests.RequestException: return JsonResponse( {"error": "Postal code validation service unavailable"}, status=502 ) if not check["valid"]: return JsonResponse( { "error": f"Invalid postal code for {country}. " f"Expected format: {check.get('format', 'unknown')}." }, status=400, ) # Use geolocation to auto-fill city and region location = check.get("location") if location: body.setdefault("city", location.get("city")) body.setdefault("region", location.get("region")) # Proceed with order order = create_order(body) return JsonResponse({"success": True, "orderId": order.id})
country_code parameter for a definitive answer and geolocation data. If you only need a quick format check (e.g., in an international search bar), omit the country to see all matches.6. cURL example
With a country hint (returns geolocation):
curl -G -H "Authorization: Bearer YOUR_API_KEY" \ --data-urlencode "value=00-001" \ --data-urlencode "countryCode=PL" \ "https://api.isvalid.dev/v0/postal-code"
Without a country hint (returns matching countries):
curl -G -H "Authorization: Bearer YOUR_API_KEY" \ --data-urlencode "value=90210" \ "https://api.isvalid.dev/v0/postal-code"
Invalid postal code:
curl -G -H "Authorization: Bearer YOUR_API_KEY" \ --data-urlencode "value=INVALID" \ --data-urlencode "countryCode=US" \ "https://api.isvalid.dev/v0/postal-code"
7. Understanding the response
With country code — validated against a specific country, includes geolocation:
{ "valid": true, "country": "PL", "countryName": "Poland", "format": "NN-NNN", "location": { "city": "Warszawa", "region": "Masovian Voivodeship", "subregion": "Warsaw", "lat": 52.23, "lon": 21.01 } }
Without country code — returns all countries where the format matches:
{ "valid": true, "matchingCountries": [ { "country": "DE", "countryName": "Germany", "format": "NNNNN" }, { "country": "US", "countryName": "United States", "format": "NNNNN" } ] }
Invalid code:
{ "valid": false }
| Field | Type | Description |
|---|---|---|
| valid | boolean | Whether the postal code matches a known format |
| country | string | ISO 3166-1 alpha-2 code (present when countryCode was provided) |
| countryName | string | Full country name in English |
| format | string | Format pattern for the country (e.g., "NN-NNN", "NNNNN") |
| location | object | Geolocation data (present when countryCode was provided and the code is valid) |
| location.city | string | City or primary locality name |
| location.region | string | State, province, voivodeship, or equivalent |
| location.subregion | string | County, district, or sub-division |
| location.lat | number | Latitude of the postal code area centroid |
| location.lon | number | Longitude of the postal code area centroid |
| matchingCountries | array | List of countries where the code is format-valid (present when countryCode was omitted) |
8. Edge cases
Ambiguous codes matching multiple countries
Five-digit postal codes are used by at least a dozen countries. The code "10115" is valid in both the US and Germany. When you call the API without a countryCode, the response lists all matching countries in the matchingCountries array.
result = iv.postal_code("10115") # result["matchingCountries"] might include DE, US, IT, FR... # Let the user pick their country, then re-validate with country_code # to get definitive geolocation data.
UK outward vs full postcode
A UK postcode has two parts: the outward code (e.g., "SW1A") identifies the postal district, and the inward code (e.g., "1AA") narrows it to a delivery point. Some systems accept just the outward code, but the API validates the full postcode. Always collect the complete code from users (e.g., "SW1A 1AA") for accurate validation and geolocation.
US ZIP+4 codes
The US Postal Service uses an extended format called ZIP+4 (e.g., "90210-1234") for more precise delivery. The API accepts both the standard 5-digit ZIP and the extended ZIP+4 format. If your application collects shipping addresses, accept both but do not require the +4 extension — most users do not know it.
Countries without postal codes
Not every country uses postal codes. Notable examples include Ireland (which uses Eircode, a unique system), Hong Kong, Panama, and several island nations. If your application supports these countries, make the postal code field optional in your forms rather than forcing users to enter a value.
Summary
See also
Validate postal codes instantly
Free tier includes 100 API calls per day. No credit card required. Format validation, country detection, and geolocation for 60+ countries.