Logistics & Shipping Validation
Four validators, one shipping workflow. Here's how to validate a container code, HS classification code, UN/LOCODE port, and EORI number in Python — using asyncio.gather for parallel validation before submitting to customs.
In this guide
- 1. The shipping validation problem
- 2. The four identifiers
- 3. Step 1: Validate the container code
- 4. Step 2: Validate the HS code
- 5. Step 3: Validate the UN/LOCODE
- 6. Step 4: Validate the EORI number
- 7. Putting it all together — parallel validation
- 8. cURL examples
- 9. Handling edge cases
- 10. Summary checklist
1. The shipping validation problem
International shipments involve four distinct identifier types — each governed by a different standard, each validated by a different authority. A single invalid identifier can stall a shipment at customs, trigger a port rejection, or result in a customs fine.
The solution: validate all four identifiers in parallel before the booking is submitted to the carrier or customs system — catching errors at data entry, not at the port.
asyncio.gather makes it straightforward to run all four validation calls concurrently. The total latency equals the slowest single call, not the sum of all four.2. The four identifiers
| Validator | What it validates | When to use | API endpoint |
|---|---|---|---|
| Container Code | ISO 6346 owner code + serial + mod-11 | Tracking intermodal containers | GET /v0/container-code |
| HS Code | WCO Harmonized System product classification | Customs declarations | GET /v0/hs-code |
| UN/LOCODE | 5-char UN port/place code + location lookup | Port of loading/discharge | GET /v0/locode |
| EORI | EU Economic Operators Registration | EU customs filings | GET /v0/eori |
3. Step 1: Validate the container code
ISO 6346 container codes consist of a 3-letter owner code, 1-letter category identifier, 6-digit serial number, and a mod-11 check digit.
Example response
{ "valid": true, "ownerCode": "MSC", "categoryCode": "U", "categoryName": "Freight container", "serialNumber": "305638", "checkDigit": "3" }
What to check in your code
valid is True — owner code, serial, and mod-11 check digit all pass
category_code — verify container type matches cargo (R = reefer for refrigerated goods)
4. Step 2: Validate the HS code
HS codes have hierarchical depth: 2-digit chapters, 4-digit headings, 6-digit subheadings. Customs declarations require 6-digit subheadings.
Example response
{ "valid": true, "code": "847130", "level": "subheading", "description": "Portable automatic data-processing machines", "formatted": "8471.30", "chapter": { "code": "84", "description": "Nuclear reactors, boilers, machinery and mechanical appliances; parts thereof" } }
What to check
valid is True — code exists in WCO HS nomenclature
level == "subheading" — 6-digit depth required for customs declarations
5. Step 3: Validate the UN/LOCODE
Example response
{ "valid": true, "found": true, "locode": "DEHAM", "country": "DE", "location": "HAM", "name": "Hamburg", "nameAscii": "Hamburg", "subdivision": "HH", "functions": ["port", "rail", "road"], "coordinates": "5333N 00958E" }
What to check
found is True — location exists in UN/LOCODE database
"port" in functions — location supports the required transport mode
6. Step 4: Validate the EORI number
Example response
{ "valid": true, "countryCode": "DE", "country": "Germany", "identifier": "123456789012345", "formatted": "DE123456789012345" }
7. Putting it all together — parallel validation
Install with pip install isvalid-sdk for the async SDK, or pip install requests for synchronous HTTP calls with ThreadPoolExecutor.
import asyncio import os from isvalid_sdk import IsValidConfig, create_client iv = create_client(IsValidConfig(api_key=os.environ["ISVALID_API_KEY"])) async def validate_shipment( container_code: str, hs_code: str, locode: str, eori_number: str, ) -> dict: container, hs, loc, eori = await asyncio.gather( iv.container_code(container_code), iv.hs_code(hs_code), iv.locode(locode), iv.eori(eori_number), ) return { "container": { "valid": container["valid"], "owner_code": container.get("ownerCode"), "category_name": container.get("categoryName"), }, "hs": { "valid": hs["valid"], "level": hs.get("level"), "description": hs.get("description"), "formatted": hs.get("formatted"), }, "locode": { "valid": loc["valid"], "found": loc.get("found"), "name": loc.get("name"), "functions": loc.get("functions"), }, "eori": { "valid": eori["valid"], "country_code": eori.get("countryCode"), "formatted": eori.get("formatted"), }, } # ── Example: Hamburg port, electronics, MSC container, German EORI ───────── result = asyncio.run(validate_shipment( container_code="MSCU3056383", hs_code="847130", locode="DEHAM", eori_number="DE123456789012345", )) print("Container:", f"✓ {result['container']['owner_code']} / {result['container']['category_name']}" if result["container"]["valid"] else "✗ invalid") print("HS Code :", f"✓ {result['hs']['formatted']} — {result['hs']['description']}" if result["hs"]["valid"] else "✗ invalid") print("LOCODE :", f"✓ {result['locode']['name']}" if result["locode"]["found"] else "✗ not found") print("EORI :", f"✓ {result['eori']['formatted']}" if result["eori"]["valid"] else "✗ invalid") if result["hs"]["valid"] and result["hs"]["level"] != "subheading": print(f"⚠ HS code is at {result['hs']['level']} level — customs requires 6-digit subheading")
asyncio.gather runs all four validation coroutines concurrently. The total latency equals the slowest single call — not the sum of all four.8. cURL examples
Validate a container code:
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/container-code?value=MSCU3056383"
Validate an HS code:
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/hs-code?value=847130"
Validate a UN/LOCODE:
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/locode?value=DEHAM"
Validate an EORI number:
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/eori?value=DE123456789012345"
9. Handling edge cases
Normalize container code formats
Container codes may arrive as "MSCU 305638-3" — normalize before validating.
def normalize_container_code(raw: str) -> str: return raw.replace(" ", "").replace("-", "").upper() normalized = normalize_container_code("MSCU 305638-3") # → "MSCU3056383"
HS code level enforcement
Require 6-digit subheading depth for customs declarations.
hs = await iv.hs_code("8471") if hs["valid"] and hs.get("level") != "subheading": raise ValueError( f"HS code at {hs['level']} level — customs requires 6-digit subheading" )
LOCODE not found in database
Valid format but not in UN/LOCODE registry — often an obsolete or mistyped code.
loc = await iv.locode("DEABC") if loc["valid"] and not loc.get("found"): return { "status": "unknown_locode", "reason": "Location code not found in UN/LOCODE database", }
10. Summary checklist
See also
Validate shipping documents before filing
Free tier includes 100 API calls per day. No credit card required. Container code, HS code, LOCODE, and EORI validation all included.