Guide · Node.js · SDK · Trade Compliance

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 before processing an international shipment — catching data errors before they reach customs.

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 challenge is that these identifiers come from different parties: the carrier provides the container code, the shipper provides the HS classification and EORI, and the freight forwarder provides the LOCODE. By the time a booking reaches your platform, any of these could contain a typo, an outdated code, or a missing check digit.

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.

ℹ️Customs authorities in the EU, UK, and US impose fines for incorrect HS codes and invalid EORI numbers. Pre-validation eliminates the most common data entry errors before they reach customs filing.

2. The four identifiers

Each identifier covers a different aspect of an international shipment:

ValidatorWhat it validatesWhen to useAPI endpoint
Container CodeISO 6346 owner code + serial + mod-11 check digitTracking intermodal containersGET /v0/container-code
HS CodeWCO Harmonized System product classificationCustoms declarations, tariff calculationsGET /v0/hs-code
UN/LOCODE5-char UN port/place code + location lookupPort of loading/discharge, place of deliveryGET /v0/locode
EORIEU Economic Operators Registration and IdentificationIdentifying the exporter/importer in EU customsGET /v0/eori
⚠️Not every shipment requires all four. Domestic shipments don't need EORI or HS codes. Design your validation flow to apply only the identifiers relevant to the shipment type.

3. Step 1: Validate the container code

ISO 6346 container codes follow a strict format: a 3-letter owner code, a 1-letter category identifier, a 6-digit serial number, and a mod-11 check digit. The endpoint validates all parts:

  • Owner code — 3-letter prefix registered with the Bureau International des Containers (BIC)
  • Category identifier — U (freight container), J (detachable freight container equipment), Z (trailer/chassis), R (reefer)
  • Serial number — 6-digit sequence number assigned by the owner
  • Mod-11 check digit — weighted checksum that catches transpositions

Example response

{
  "valid": true,
  "ownerCode": "MSC",
  "categoryCode": "U",
  "categoryName": "Freight container",
  "serialNumber": "305638",
  "checkDigit": "3"
}

What to check in your code

1

valid === true — the owner code, serial, and mod-11 check digit are all valid

2

categoryCode — verify the container type matches the cargo (e.g., R = reefer for refrigerated goods)

3

ownerCode — the 3-letter prefix identifies the container owner/operator for tracking


4. Step 2: Validate the HS code

The Harmonized System (HS) is the WCO's international goods classification standard, used by 200+ countries. HS codes have hierarchical depth:

  • Chapter (2 digits) — e.g., 84 = Machinery
  • Heading (4 digits) — e.g., 8471 = Computers
  • Subheading (6 digits) — e.g., 847130 = Portable computers

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 in your code

1

valid === true — the code exists in the WCO HS nomenclature

2

level === "subheading" — customs declarations require 6-digit codes; heading-level is insufficient

3

description — display to the user for confirmation that the classification matches the goods

⚠️Customs authorities require 6-digit HS subheadings (or country-specific 8-10 digit tariff codes). A 4-digit heading is not sufficient for customs declarations — always check level.

5. Step 3: Validate the UN/LOCODE

UN/LOCODE is a 5-character code identifying ports, airports, and logistics places worldwide. The endpoint validates the code format and looks up the location:

  • Country code — first 2 characters (ISO 3166-1 alpha-2)
  • Location code — last 3 characters (alphanumeric)
  • Functions — port, rail, road, airport, postal designations
  • Coordinates — geographic position for routing calculations

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 in your code

1

valid === true — the 5-character format is correct

2

found === true — the location exists in the UN/LOCODE database; a valid format but unknown port causes routing failures

3

functions — verify the location supports the required transport mode (e.g., "port" for sea shipments)


6. Step 4: Validate the EORI number

EORI (Economic Operators Registration and Identification) is a unique identifier required for any entity importing or exporting goods into/from the EU or UK. The endpoint validates:

  • Country code prefix — 2-letter EU/UK country code
  • Identifier format — country-specific format (e.g., GB = 12-15 alphanumeric)
  • Structure — validates the total length and character set per country rules

Example response

{
  "valid": true,
  "countryCode": "DE",
  "country": "Germany",
  "identifier": "123456789012345",
  "formatted": "DE123456789012345"
}

What to check in your code

1

valid === true — the EORI passes format and length validation for the declared country

2

countryCode — the EORI country must match the exporter/importer's declared country of establishment

3

formatted — use the normalized form for customs filing to ensure consistent formatting


7. Putting it all together — parallel validation

The following Node.js example validates all four identifiers in parallel using Promise.all. Use the @isvalid-dev/sdk package or the native fetch API (Node 18+).

import { createClient } from '@isvalid-dev/sdk';

const iv = createClient({ apiKey: process.env.ISVALID_API_KEY });

async function validateShipment({ containerCode, hsCode, locode, eoriNumber }) {
  const [container, hs, loc, eori] = await Promise.all([
    iv.containerCode(containerCode),
    iv.hsCode(hsCode),
    iv.locode(locode),
    iv.eori(eoriNumber),
  ]);

  return {
    container: {
      valid: container.valid,
      ownerCode: container.ownerCode ?? null,
      categoryCode: container.categoryCode ?? null,
      categoryName: container.categoryName ?? null,
    },
    hs: {
      valid: hs.valid,
      level: hs.level ?? null,
      description: hs.description ?? null,
      formatted: hs.formatted ?? null,
    },
    locode: {
      valid: loc.valid,
      found: loc.found ?? null,
      name: loc.name ?? null,
      functions: loc.functions ?? null,
    },
    eori: {
      valid: eori.valid,
      countryCode: eori.countryCode ?? null,
      formatted: eori.formatted ?? null,
    },
  };
}

// ── Example: Hamburg port, electronics, MSC container, German EORI ─────────
const result = await validateShipment({
  containerCode: 'MSCU3056383',
  hsCode: '847130',
  locode: 'DEHAM',
  eoriNumber: 'DE123456789012345',
});

console.log('Container:', result.container.valid ? `✓ ${result.container.ownerCode} / ${result.container.categoryName}` : '✗ invalid');
console.log('HS Code  :', result.hs.valid ? `✓ ${result.hs.formatted} — ${result.hs.description}` : '✗ invalid');
console.log('LOCODE   :', result.locode.found ? `✓ ${result.locode.name} [${result.locode.functions?.join(', ')}]` : '✗ not found');
console.log('EORI     :', result.eori.valid ? `✓ ${result.eori.formatted}` : '✗ invalid');

const requiresFullHs = result.hs.valid && result.hs.level !== 'subheading';
if (requiresFullHs) console.warn('⚠ HS code must be 6-digit subheading for customs filing');
All four API calls run in parallel via Promise.all. The total latency is the slowest single call, not the sum of all four.

8. cURL examples

Validate a container code (MSC container):

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.isvalid.dev/v0/container-code?value=MSCU3056383"

Validate an HS code (portable computers):

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.isvalid.dev/v0/hs-code?value=847130"

Validate a UN/LOCODE (Hamburg):

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.isvalid.dev/v0/locode?value=DEHAM"

Validate an EORI number (Germany):

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.isvalid.dev/v0/eori?value=DE123456789012345"

9. Handling edge cases

Container code with spaces or dashes

Container codes are often formatted as "MSCU 305638-3" or "MSCU3056383". Normalize to the 11-character form before validation.

function normalizeContainerCode(raw) {
  // Remove spaces and dashes, uppercase
  return raw.replace(/[\s-]/g, '').toUpperCase();
}
const normalized = normalizeContainerCode('MSCU 305638-3'); // → 'MSCU3056383'
const result = await iv.containerCode(normalized);

HS code at heading level — not sufficient for customs

A 4-digit HS heading passes validation but is insufficient for customs declarations. Check the level field and require subheading depth.

const hs = await iv.hsCode('8471');
if (hs.valid && hs.level !== 'subheading') {
  return {
    status: 'insufficient',
    reason: `HS code is at ${hs.level} level — customs requires 6-digit subheading`,
    hint: `Provide a more specific code under chapter ${hs.chapter?.code}`,
  };
}

LOCODE valid format but not found in database

The 5-character format is correct but the location doesn't exist in UN/LOCODE. This often happens with obsolete codes or typos in the country prefix.

const loc = await iv.locode('DEABC');
if (loc.valid && !loc.found) {
  // Format is valid (DE + 3 chars) but not in UN/LOCODE registry
  return {
    status: 'unknown_locode',
    reason: 'Location code not found in UN/LOCODE database',
    suggestion: 'Check the country prefix and location code',
  };
}

EORI country mismatch

The EORI country code doesn't match the shipper's declared country of establishment. A German company should have a DE-prefixed EORI.

if (eoriResult.valid && eoriResult.countryCode !== shipperCountry) {
  logger.warn(`EORI country mismatch: EORI=${eoriResult.countryCode}, shipper=${shipperCountry}`);
  return {
    status: 'requires_review',
    reason: 'EORI country does not match declared country of establishment',
  };
}

10. Summary checklist

Validate container codes before booking a shipment slot — prevents terminal rejections
Check HS code level — customs requires 6-digit subheading, not just chapter or heading
Confirm LOCODE found === true — valid format but unknown port causes routing failures
Validate EORI before customs filing — an invalid EORI triggers delays and fines
Do not accept free-text port names — always require and validate a UN/LOCODE
Do not use HS codes at heading level for import declarations — require full 6-digit codes

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.