UN/LOCODE Validation in Python — Ports & Trade Locations
Every port, airport, rail terminal, and inland clearance depot in international trade is identified by a UN/LOCODE — a five-character code maintained by the United Nations. Here's how the standard works, what each part of the code means, and how to validate and look up LOCODEs in your Python application.
In this guide
1. What is a UN/LOCODE?
UN/LOCODE (United Nations Code for Trade and Transport Locations) is an international standard maintained by UNECE (United Nations Economic Commission for Europe). It assigns a unique five-character code to over 100,000 locations in more than 240 countries and territories.
The directory covers seaports, inland ports, airports, rail terminals, road terminals, postal exchange offices, multimodal facilities, border crossing points, and other locations relevant to international trade and transport. Each location receives a code regardless of its size — from the world's busiest container ports to small inland customs posts.
UN/LOCODEs appear throughout trade documentation: bills of lading, customs declarations, letters of credit, certificates of origin, dangerous goods manifests, and EDI messages such as EDIFACT and UN/CEFACT. Using the standardised code instead of free-text location names eliminates ambiguity — there is no confusion between "Warsaw", "Warszawa", and "Varsovie" when the code is simply PLWAW.
UNECE publishes updated directories twice a year, adding new locations and decommissioning codes that are no longer in active use. The registry is freely available but validation requires checking both format correctness and existence in the current directory.
2. LOCODE structure
A UN/LOCODE consists of exactly five characters, split into two parts:
Country Code
PL
2-letter ISO 3166-1 alpha-2
Location Code
WAW
3-character alphanumeric identifier
The first two characters are the ISO 3166-1 alpha-2 country code (e.g. PL for Poland, DE for Germany, US for the United States). The remaining three characters identify the specific location within that country. The location part can contain both letters and digits (A-Z, 2-9), though most codes use only letters.
Each location in the UN/LOCODE directory is tagged with one or more function classifiers indicating the type of facility available at that location:
| Code | Function | Description |
|---|---|---|
| 1 | Port | Seaport or river port with vessel operations |
| 2 | Rail terminal | Rail freight or passenger terminal |
| 3 | Road terminal | Road freight terminal or truck depot |
| 4 | Airport | Airport with cargo or passenger operations |
| 5 | Postal exchange | Postal exchange office |
| 6 | Multimodal | Facility handling multiple transport modes |
| 7 | Fixed transport | Fixed transport installation (pipeline, cableway) |
| B | Border crossing | International border crossing point |
Here are some well-known UN/LOCODEs:
| LOCODE | Location | Functions |
|---|---|---|
| PLWAW | Warszawa, Poland | Rail, Road, Airport, Postal |
| DEHAM | Hamburg, Germany | Port, Rail, Road, Airport, Postal |
| USNYC | New York, United States | Port, Rail, Road, Airport, Postal |
| CNSHA | Shanghai, China | Port, Rail, Road, Airport |
| SGSIN | Singapore | Port, Rail, Road, Airport, Postal |
| NLRTM | Rotterdam, Netherlands | Port, Rail, Road, Airport, Postal |
3. Why LOCODE validation matters
Trade documentation accuracy
Bills of lading, commercial invoices, and packing lists reference ports of loading and discharge by their UN/LOCODE. An incorrect code can misdirect cargo, cause delays in customs processing, or trigger compliance alerts when the declared route does not match the physical movement of goods.
EDI and electronic messaging
EDIFACT messages (IFTMIN, COPARN, BAPLIE) and other electronic data interchange formats use UN/LOCODEs to identify origin, destination, and transshipment points. Invalid codes cause message rejection at the receiving system, breaking automated booking and planning workflows.
Customs automation
Modern customs systems (e.g. EU ICS2, US ACE) rely on UN/LOCODEs for automated risk assessment and routing decisions. Submitting an invalid or unknown LOCODE can result in declaration rejection, manual inspection queues, and fines for non-compliance.
Supply chain visibility
Freight visibility platforms map shipment milestones to geographic locations using UN/LOCODEs. Invalid codes create gaps in the tracking chain — a container may appear to vanish between two milestones because the intermediate location code was not recognised by the tracking system.
4. The right solution
The IsValid UN/LOCODE API provides two endpoints: a validate endpoint that checks a single LOCODE and returns its full metadata (name, subdivision, functions, IATA code, coordinates), and a list endpoint that returns all LOCODEs for a given country.
Validate endpoint
Check a single LOCODE, get location name, subdivision, transport functions, IATA code, and geographic coordinates.
List endpoint
Retrieve all LOCODEs for a specific country. Useful for building dropdowns, autocomplete inputs, and data migration scripts.
Full parameter reference and response schema: UN/LOCODE Validation API docs →
5. Python code example
Using the isvalid-sdk package or the requests library. Install with pip install isvalid-sdk or pip install requests.
Validate a single LOCODE:
# locode_validator.py import os from isvalid_sdk import IsValidConfig, create_client iv = create_client(IsValidConfig(api_key=os.environ["ISVALID_API_KEY"])) # ── Validate a UN/LOCODE ──────────────────────────────────────────────────── result = iv.locode("PLWAW") if not result["found"]: print("LOCODE not found in the UN directory") else: print(f"Name: {result['name']}") # → 'Warszawa' print(f"Country: {result['country']}") # → 'PL' print(f"Functions: {result['functions']}") # → ['rail', 'road', 'airport', 'postal'] print(f"IATA: {result['iata']}") # → 'WAW' print(f"Coordinates: {result['coordinates']}") # → '5215N 02100E'
List all LOCODEs for a country:
# locode_list.py import os from isvalid_sdk import IsValidConfig, create_client iv = create_client(IsValidConfig(api_key=os.environ["ISVALID_API_KEY"])) # ── List LOCODEs for Poland ───────────────────────────────────────────────── locations = iv.locode.list(country="PL") print(f"Found {len(locations)} locations in Poland") for loc in locations[:5]: funcs = ", ".join(loc["functions"]) print(f" {loc['locode']} — {loc['name']} ({funcs})")
In a shipment booking pipeline — validate origin and destination LOCODEs:
# Validate LOCODEs in a booking request def validate_booking_locations(booking: dict) -> dict: origin = booking["origin"] destination = booking["destination"] origin_result = validate_locode(origin) dest_result = validate_locode(destination) errors = [] if not origin_result["found"]: errors.append(f"Unknown origin LOCODE: {origin}") if not dest_result["found"]: errors.append(f"Unknown destination LOCODE: {destination}") if errors: return {"valid": False, "errors": errors} return { "valid": True, "origin": { "locode": origin_result["locode"], "name": origin_result["name"], "country": origin_result["country"], "functions": origin_result["functions"], }, "destination": { "locode": dest_result["locode"], "name": dest_result["name"], "country": dest_result["country"], "functions": dest_result["functions"], }, }
plwaw and PLWAW, but the canonical form returned in responses is always uppercase. Normalise user input to uppercase before storing.6. cURL example
Validate a single UN/LOCODE:
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/locode?value=PLWAW"
Validate a port LOCODE:
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/locode?value=DEHAM"
List all LOCODEs for a country:
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/locode/list?country=PL"
7. Understanding the response
Validate endpoint — known LOCODE:
{ "valid": true, "found": true, "locode": "PLWAW", "country": "PL", "location": "WAW", "name": "Warszawa", "nameAscii": "Warszawa", "subdivision": "14", "functions": ["rail", "road", "airport", "postal"], "iata": "WAW", "coordinates": "5215N 02100E" }
Validate endpoint — unknown LOCODE:
{ "valid": true, "found": false, "locode": "PLXYZ", "country": "PL", "location": "XYZ" }
| Field | Type | Description |
|---|---|---|
| valid | boolean | Whether the LOCODE has a valid format (2-letter country + 3-char location) |
| found | boolean | Whether the LOCODE exists in the UN/LOCODE directory |
| locode | string | The normalised 5-character LOCODE (e.g. "PLWAW") |
| country | string | ISO 3166-1 alpha-2 country code (e.g. "PL") |
| location | string | 3-character location identifier (e.g. "WAW") |
| name | string | Location name, possibly with diacritics (e.g. "Warszawa") |
| nameAscii | string | ASCII-only transliteration of the location name |
| subdivision | string | ISO 3166-2 subdivision code (e.g. "14" for Mazowieckie) |
| functions | string[] | Transport functions: port, rail, road, airport, postal, multimodal, fixed-transport, border-crossing |
| iata | string | IATA airport code if applicable (e.g. "WAW") |
| coordinates | string | Geographic coordinates in UN/LOCODE format (e.g. "5215N 02100E") |
List endpoint — response for a country:
[ { "locode": "PLGDN", "country": "PL", "location": "GDN", "name": "Gdansk", "nameAscii": "Gdansk", "subdivision": "22", "functions": ["port", "rail", "road", "airport", "postal"], "iata": "GDN", "coordinates": "5423N 01840E" }, { "locode": "PLWAW", "country": "PL", "location": "WAW", "name": "Warszawa", "nameAscii": "Warszawa", "subdivision": "14", "functions": ["rail", "road", "airport", "postal"], "iata": "WAW", "coordinates": "5215N 02100E" } ]
| Field | Type | Description |
|---|---|---|
| (root) | array | Array of LOCODE objects for the requested country |
| [].locode | string | Full 5-character UN/LOCODE |
| [].name | string | Location name with diacritics |
| [].functions | string[] | Transport function classifiers for this location |
8. Edge cases
Valid format but unknown location
A LOCODE can have a perfectly valid format (two-letter country code + three alphanumeric characters) but not exist in the UN/LOCODE directory. For example, PLXYZ has a valid format (PL is a valid country code, XYZ is a valid location pattern) but does not correspond to any registered location. The API distinguishes this with valid: true, found: false.
result = iv.locode("PLXYZ") # result["valid"] is True (format is correct) # result["found"] is False (not in the UN directory)
Decommissioned LOCODEs
UNECE periodically removes LOCODEs from the active directory when locations are merged, renamed, or no longer relevant for trade. A code that was valid in a previous edition of the directory may return found: false in the current version. If you are processing historical trade documents, be aware that the LOCODE may have been valid at the time of issuance but is no longer in the active registry.
LOCODE vs IATA codes
For locations with airports, the 3-character IATA airport code often matches the location portion of the UN/LOCODE. For example, Warsaw's IATA code is WAW and its UN/LOCODE is PLWAW — the location part is identical. However, this is not always the case. Some locations have different IATA and LOCODE identifiers, and many LOCODEs have no IATA code at all (e.g. inland customs posts, road terminals).
iata field when an IATA code is associated with the location.Summary
See also
Validate UN/LOCODEs instantly
Free tier includes 100 API calls per day. No credit card required. Validate and look up over 100,000 trade and transport locations from the UN/LOCODE directory.