Compatible with Swagger and Postman import
Overview
The FreightUtils API is the neutral freight reference layer for AI agents — source-backed dangerous-goods, customs, location and freight-calculation data an agent can call and cite, from primary sources (ADR 2025 / UNECE, HS 2022 / WCO, IATA-regulated airline prefixes). Neutral by design: no freight to sell and no carrier to push — a lookup returns what the named source says, with the citation attached so an agent can inspect it rather than take our word for it. It is a stateless REST API; every calculator on this site has a corresponding endpoint. No authentication is required. Responses are JSON. CORS is enabled for all origins.
Reliability & Support
FreightUtils APIs are hosted on Vercel's global edge network with automatic SSL and CDN caching.
MCP Server — AI Agent Integration
The full MCP server reference — all 25 tools, the response envelope, rate limits and directory listings — lives on its own page.
MCP server docs →FreightUtils is the neutral freight reference layer for AI agents, available as a Model Context Protocol (MCP) server — direct, citable access to all 25 MCP tools (24 REST-backed + get_subscribe_link) from named primary sources, with no freight to sell and no carrier to push.
claude_desktop_config.json:After saving the config, fully quit and relaunch your MCP client (Claude Desktop, Cursor, Cline). MCP servers are only loaded at client startup — editing the config in a running session does nothing until restart.
Confirm the FreightUtils MCP surface is reachable before asking your agent any freight question:
A 200 response with "status":"ok" and "tools_registered":25 means the remote MCP surface is live. The endpoint is callable by your agent too — Claude/Cursor/Cline can hit it for a self-diagnostic without you having to open a terminal.
mcp.json file (Settings → MCP). Same shape as Claude Desktop:| Symptom | Likely cause | Fix |
|---|---|---|
| Tools not appearing in the client | MCP client wasn’t restarted after the config edit | Fully quit (Cmd+Q on macOS / right-click → Quit on Windows tray) and relaunch. Don’t just close the window. |
| "Server failed to start" / spawn error in client logs | npx not on PATH, or node version older than 18 | Install Node.js 18+ from nodejs.org. On macOS, an absolute path in the config (e.g. "/opt/homebrew/bin/npx") avoids PATH issues for GUI-launched clients. |
| Tool calls return HTTP 429 / "rate_limited" | Anonymous IP cap of 25 requests/day exceeded | Get a free API key from the signup form below (100/day) or upgrade to Pro (50,000/month). The freightutils-mcp npm package passes the key through on every call (since v2.3.0) — set the FREIGHTUTILS_API_KEY env var in your MCP client config. On the remote URL (https://www.freightutils.com/api/mcp), send it as an X-API-Key header. |
| Specific tool returns "isError": true | Bad input shape (snake_case vs camelCase, missing required field) or unknown lookup key (UN number / HS code / AWB prefix not in the dataset) | The error message in the tool response names the field. Verify against the schema at /api-docs or call the corresponding playground endpoint directly to confirm the input shape. |
| Want to verify the surface from inside an agent | No CLI access during a conversation | Ask the agent to fetch /api/mcp/health. The endpoint is public, returns the server version + registered tool count + transport URLs in a single JSON, and is rate-limit exempt so the diagnostic always works. |
Get Started in 2 Minutes
// FreightUtils API — JavaScript example // Calculate loading metres for 10 Euro pallets const response = await fetch( 'https://www.freightutils.com/api/ldm?pallet=euro&qty=10' ); const data = await response.json(); console.log(`LDM: ${data.ldm}`); console.log(`Utilisation: ${data.utilisation_percent}%`); console.log(`Fits: ${data.fits}`);
All endpoints work the same way. No auth, no signup. Full reference below ↓
Composite endpoint that chains CBM, chargeable weight, LDM, ADR compliance, and UK duty/VAT estimation into a single call. Accepts a unified Shipment object and returns comprehensive results based on transport mode.
Mode Parameter
| Mode | Calculations Included |
|---|---|
road | CBM, LDM, pallet spaces, trailer utilisation, road chargeable weight (1 LDM = 1,750 kg), vehicle suggestion |
air | CBM, volumetric weight (1 CBM = 167 kg), air chargeable weight |
sea | CBM, revenue tonnes (W/M at 1 CBM = 1,000 kg), container suggestion |
multimodal | All of the above — road, air, and sea calculations combined |
Example Request
Mixed 3-item road shipment with a DG item and HS code:
Example Response
Pro-tier endpoint. Free access: 25 requests/day anonymous, 100/day with a free API key. Subscribe for higher limits.
Calculate the cubic metre (CBM) volume of a shipment. Returns total CBM plus equivalents in cubic feet, litres, and cubic inches.
Parameters
| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
l | number | Yes | Length of one piece in centimetres | — |
w | number | Yes | Width of one piece in centimetres | — |
h | number | Yes | Height of one piece in centimetres | — |
pcs | integer | No | Number of identical pieces | 1 |
Example Request
5 boxes, 120×80×100 cm each:
Estimate freight transport CO2e using the ISO 14083 / GLEC distance-based method: emissions = mass × distance × an open emission-intensity factor (kgCO2e/tonne-km). Provide actual gross mass, not chargeable/volumetric weight (a common air-freight mistake — see mass_basis). Returns well-to-wheel and tank-to-wheel emissions, the exact factor used, and a _source citing both the method and the specific open factor (DEFRA / EPA / ADEME). Each result also carries empty_running (the fleet-average factor already includes average empty running — don't double-count an empty return), representativeness (sea/air = low, high real-world variance), a human-readable summary, and _source.factor.last_verified. You provide the distance — this endpoint does not route. Best-effort estimate, not a verified carbon report; an unknown mode/sub_mode/region returns available:false with the covered options.
Parameters
| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
mass | number | Yes | Shipment mass (in mass_unit) | — |
mass_unit | kg | tonnes | No | Unit for mass | kg |
distance_km | number | Yes | Transport distance in km (caller-provided; not routed) | — |
mode | enum | Yes | road | rail | sea | air | inland_waterway | — |
sub_mode | string | No | Vehicle class (e.g. articulated, container ship) | representative |
region | uk | us | fr | No | Factor source: uk=DEFRA, us=EPA, fr=ADEME | per-mode |
basis | wtw | ttw | No | Well-to-wheel or tank-to-wheel | wtw |
Example Request
10 tonnes by road, 500 km, UK (DEFRA), well-to-wheel:
Parse an arbitrary string (e.g. a booking line or email) to find and validate every freight identifier in it — shipping container (ISO 6346), air waybill (IATA modulus-7) and IMO ship number — or validate a single identifier by type. Returns per identifier: type, normalised form, valid (pass/fail), expected vs actual check digit, details (container owner/category; the AWB airline, or airline_candidates when the prefix is held by more than one record; IMO number) and a _source. Structural only — a valid check digit means well-formed, not that the entity exists.
Parameters
| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
text | string | No* | Arbitrary string to scan for container / AWB / IMO identifiers (parse mode) | — |
value | string | No* | A single identifier to validate (typed mode); requires type | — |
type | container | awb | imo | No* | Identifier type for value | — |
*Provide text (parse mode) OR value+type (typed mode).
Example Request
Parse a mixed string:
Check a goods description against the official EU ICS2 stop-words list (unacceptable/vague terms for entry summary declarations). Returns the flagged terms — each with a note on whether it is the standalone description (automatic ENS rejection) or embedded (make the description more specific) — a clean boolean, a caveat, and a _source citing the EU list + legal basis. Reference only: not an ENS filing, not a compliance determination; the list is non-exhaustive and clean does not guarantee acceptance. No accepted/rejected verdict. The submitted description is not persisted or logged (response is no-store).
Parameters
| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
description | string | Yes | The goods description to check against the EU ICS2 stop-words list | — |
Example Request
A vague standalone description:
Look up an airport by IATA code (3 letters), ICAO code (4 chars), or free-text name / city search. Provide one of iata, icao or q. Returns the full record (both codes, name, type, municipality, region, country, coordinates, elevation); ambiguous name searches return ranked candidates. Optional type filter. Data: OurAirports (public domain), cross-checked vs OpenFlights + Wikidata. Reference only — not for navigation; verify current codes with IATA/ICAO.
Parameters
| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
iata | string | One of | Exact 3-letter IATA code (e.g. LHR) | — |
icao | string | One of | Exact 4-character ICAO code (e.g. EGLL) | — |
q | string | One of | Name / city / municipality search (min 2 chars) | — |
type | string | No | Filter: large_airport, medium_airport, small_airport, heliport, closed, seaplane_base | — |
Example Request
Find the airports nearest to a caller-provided latitude/longitude, sorted by great-circle (haversine) distance with distance_km on each result. Coordinates are input only — never stored or logged. Optional radius_km, max_results (1–50, default 10) and type filter. Does NOT geocode place names or compute routes — pass coordinates you already hold.
Parameters
| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
lat | number | Yes | Latitude in decimal degrees (-90 to 90) | — |
lon | number | Yes | Longitude in decimal degrees (-180 to 180) | — |
radius_km | number | No | Maximum distance in kilometres | — |
max_results | integer | No | Results to return (1–50) | 10 |
type | string | No | Filter by airport type (e.g. large_airport) | — |
Example Request
The agent front door: one identifier-ish string in, typed, ranked, cited candidates out — without needing to know which lookup endpoint fits. Thirteen grammars all run on every query: UN numbers, AWB prefixes, airline IATA/ICAO, airport IATA/ICAO, UN/LOCODE, ISO 6346 container numbers (check digit computed), HS codes, Incoterms, ADR tunnel codes, ULD serials and ISO container size/type codes. Ambiguity is the product: colliding grammars return multiple candidates with an inspectable rank_basis on each, never a silent guess. Zero candidates is a validcount: 0 result — never an error. v1 resolves a single token (max 32 chars); free-text extraction is out of scope. This endpoint is envelope v1.1 native — responses are always enveloped; pass ?envelope=1 for the byte-stable v1 envelope.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
q | string | Yes | One freight identifier — e.g. 176, UN1845, NLRTM, FOB, 22G1, MSKU1100810, 090111, D/E |
Example Request
Response Fields (per candidate)
| Field | Type | Description |
|---|---|---|
entity_type | string | What the candidate IS — dangerous_good, airline, airport, location, hs_classification, incoterm, container_type, container_equipment, uld_type, tunnel_restriction |
identifier_type | string | Which grammar matched — un_number, awb_prefix, airline_iata, airline_icao, airport_iata, airport_icao, unlocode, container_number, hs_code, incoterm, tunnel_code, uld_type, iso_container_type |
record | object | null | Core fields from the matching dataset; null for a pattern-only match (e.g. an ISO type code not in the served set) |
verification_status | string | Normalized across datasets — active_verified, verified, computed, partial, pending_verification, unchecked, unverified, historical, pattern_only |
verification_basis | string | record = from this record’s own provenance; dataset = the dataset-level claim (no record-level signal exists) |
rank_basis | string | Why the candidate sits where it does — record hit > verification status > static dataset prior > stable tiebreak |
canonical_url / api_url | string? | Where to go deeper — the record’s page and the dataset’s REST query; omitted when none exists |
citation | object | The matching dataset’s own citation for this record — authority stays attached to the record it describes |
Calculate air freight chargeable weight — whichever is higher between actual gross weight and volumetric (dimensional) weight. Supports custom volumetric factors for all carriers.
Parameters
| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
l | number | Yes | Length of one piece in centimetres | — |
w | number | Yes | Width of one piece in centimetres | — |
h | number | Yes | Height of one piece in centimetres | — |
gw | number | Yes | Total gross weight of all pieces in kg | — |
pcs | integer | No | Number of identical pieces | 1 |
factor | integer | No | Volumetric divisor: 6000 (IATA standard), 5000 (express carriers) | 6000 |
Example Request
2 pieces, 120×80×100 cm, 500 kg total, IATA factor:
Calculate how many boxes fit on a pallet using a layer-based algorithm. Returns boxes per layer, number of layers, total boxes, orientation used, and volume/weight analysis. Optional weight constraint caps the result at the pallet's maximum payload.
Parameters
| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
pl | number | Yes | Pallet length in centimetres | — |
pw | number | Yes | Pallet width in centimetres | — |
pmh | number | Yes | Maximum total stack height in centimetres (floor to top of cargo) | — |
bl | number | Yes | Box length in centimetres | — |
bw | number | Yes | Box width in centimetres | — |
bh | number | Yes | Box height in centimetres | — |
ph | number | No | Pallet board/deck height in cm — deducted from usable height | 15 |
bwt | number | No | Weight per box in kg — enables weight constraint calculation | — |
mpw | number | No | Maximum pallet payload weight in kg — caps result if weight exceeded | — |
rotate | boolean | No | Allow 90° rotation of boxes for best fit. Pass false to disable. | true |
Example Request
Look up ADR 2025 dangerous goods by UN number, search by substance name, or filter by hazard class. The dataset contains 2,939 entries from the ADR 2025 Dangerous Goods List (Table A). Responses are cached for 1 hour (s-maxage=3600).
Query Modes
| Parameter | Type | Description | Max results |
|---|---|---|---|
un | string | Exact UN number lookup. Accepts 1203, UN1203, or 01203. | 1 |
search | string | Case-insensitive partial match on the proper shipping name. Min 2 characters. Also accepts q as an alias. | 50 |
class | string | Filter by ADR hazard class (e.g. 3, 6.1, 1.1). | 100 |
Provide exactly one parameter per request. Omitting all parameters returns a 400 with usage hints.
Example Requests
Exact UN number lookup:
Per-row verification: every row carries verified and a verification block, always. verified: true (status verified, basis row) only where the row itself was audited field by field against the purchased UNECE Table A — then audited_at, the cited source count and the row’s sources[] ride along (56 anchor rows today, e.g. UN 1845, UN 1789). Every other row says verified: false with the reason no row-level audit: it was transcribed from the same Table A and is held by the dataset-level checks only. The envelope’s _source.provenance_status is the dataset-level claim; the row field is the row’s own. Silence is never verification.
Scope-flagged rows: 28 Table A entries carry a scope remark instead of a packing group. Those return packing_group: null plus not_subject_to_adr: true(with conditions_ref, e.g. "5.5.3" for UN 1845 dry ice) orcarriage_prohibited: true, and table_a_remark preserves the verbatim Table A text.
Search by substance name:
Filter by hazard class:
Calculate whether the ADR 1.1.3.6 small load exemption applies to a dangerous goods consignment. Supports single-substance GET queries and multi-substance POST requests. Checks both total points threshold (1,000) and per-substance quantity limits per ADR 1.1.3.6.3.
GET — Single Substance
| Parameter | Type | Required | Description |
|---|---|---|---|
un | string | Yes | UN number (e.g. 1203) |
qty | number | Yes | Quantity on the ADR 1.1.3.6.3 basis: net kg for solids and liquefied, refrigerated or dissolved gases; litres for liquids; receptacle water capacity in litres for compressed or adsorbed gases and chemicals under pressure; articles as the mass of the articles without their packagings, in kg. |
unit | string | No | L or kg. Omit it and qty is taken as already on the 1.1.3.6.3 basis (unchanged behaviour). Supply it and it is checked against the dimension 1.1.3.6.3 counts for that row; a mismatch returns points: null, total_points: null, exempt: null and items[].basis_mismatch: true with a warning naming the right dimension — never a total computed from the wrong quantity. A row whose Table A column (15) carries no transport category (–, or See SP 671 on the kit entry UN 3316) returns the same null total with items[].category_unresolved: true and a warning naming the column value. |
basis | string | No | net or gross. 1.1.3.6.3 counts the goods, never the packaging, so gross withholds the verdict in any unit. |
Example — 200 litres of petrol:
POST — Multi-Substance Load
Request body:
Response structure is identical to the GET endpoint, with multiple items in the array.
Multi-variant UN numbers: some UN numbers have more than one ADR Table A row (different packing groups / concentration bands with different transport categories — e.g. UN 1789 PG II vs PG III). Pass an optional packing_group(I/II/III, GET query or per item) or variant_index(from /api/adr) to pin one row. Without one, the response is HTTP 200 with human_review_required: true and a candidates[] list (no verdict) rather than a silently-guessed row. A packing_group that still leaves several rows is enough when those rows agree on class, transport category, scope and the 1.1.3.6.3 counted dimension (UN 1790 PG I: two concentration bands, both category 1) — the verdict is returned with items[].equivalent_variants and avariant_note saying which rows it holds for; only when the rows disagree(UN 2215 PG III: MOLTEN is category 0, the solid category 3) does it still ask for variant_index, and then candidates[] lists only that packing group’s rows. In a mixed load, an unpinned multi-variant line is withheld on its own (items[].withheld: true, points null) while every other line keeps its points; total_points is null until it is pinned, because 1.1.3.6.4 sums every line, and exempt is null too unless the resolved lines alone already disqualify the load — a “CARRIAGE PROHIBITED” entry, a category 0 entry, a per-substance maximum exceeded, or a partial sum already over 1,000 — in which case exempt is false with the usual message (and carriage_prohibited: true where that is the reason), since no packing group can undo those. candidates[] carry expected_unit, the 1.1.3.6.3 counted dimension — for UN 3375 PG II (liquid in L, solid in kg) that is the only field on which the rows differ. Only a load in which every line is ambiguous returns the no-items candidates[] shape.
Response Fields
| Field | Type | Description |
|---|---|---|
packing_group | string | null | Resolved ADR packing group for the row (echoed for traceability) |
variant_index | number | null | Resolved ADR Table A variant index for the row (null on a withheld line) |
transport_category | string | null | ADR transport category (0–4); null on a withheld line |
multiplier | number | null | Points multiplier for the category (null for cat 0, out-of-scope rows and withheld lines) |
points | number | null | quantity × multiplier |
items[].withheld | boolean? | true when this line’s UN resolves to more than one Table A row and nothing pinned one — its row fields are null (never the first row’s), its candidate rows sit under candidates[] tagged with its item_index |
items[].equivalent_variants | object[]? | Present when the supplied packing_group left several rows that agree on every field the verdict uses: the rows (variant_index, proper_shipping_name) this verdict holds for; variant_note says so in words and which row’s fields are shown |
total_points | number | null | Sum of all substance points; null while any line is withheld or declared on a basis 1.1.3.6.3 does not count |
exempt | boolean | null | true if total ≤ 1,000 AND no cat 0 AND no quantity exceedance; null when no load verdict was reached (never false as a stand-in) |
human_review_required | boolean? | true when at least one line is withheld — candidates[] then lists the withheld lines’ rows, each tagged with item_index and un_number |
has_category_zero | boolean | true if any substance is transport category 0 |
has_quantity_exceedance | boolean | true if any substance exceeds its per-category max quantity |
warnings | string[] | Human-readable warning messages for limit violations and out-of-scope exclusions |
not_subject_to_adr | boolean? | true when EVERY item is listed “NOT SUBJECT TO ADR” in Table A — the 1.1.3.6 math is not applied (there is nothing to be exempt from) |
conditions_ref | string? | ADR section whose carriage conditions still apply to a not-subject load (e.g. 5.5.3 for UN 1845 dry ice) |
conditions | object[]? | Verbatim ADR 2025 conditions for conditions_ref ({ref, heading, requirement, note?}) |
carriage_prohibited | boolean? | true when the load contains a “CARRIAGE PROHIBITED” Table A entry — exempt is false; the load may not be carried by road under ADR |
Scope verdicts: 28 ADR 2025 Table A rows carry a scope remark instead of a packing group. A load of such entries never enters the 1.1.3.6 points math — e.g.?un=1845&qty=100 (dry ice) returns not_subject_to_adr: true withmessage “Not subject to ADR (road). Section 5.5.3 applies: …” and the verbatim 5.5.3 conditions, while a “CARRIAGE PROHIBITED” entry (e.g. UN 0020) returns exempt: false with carriage_prohibited: true.
Check whether dangerous goods qualify for ADR Limited Quantity (Chapter 3.4) or Excepted Quantity (Chapter 3.5) concessions. Accepts up to 20 items per request and returns per-item pass/fail status against ADR Table A limits.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
mode | string | No | lq (default) or eq |
items | array | Yes | 1–20 items to check |
items[].un_number | string | Yes | UN number (e.g. 1203) |
items[].quantity | number | Yes | Quantity per inner packaging |
items[].unit | string | No | ml, L (default), g, or kg |
items[].inner_packaging_qty | number | No | Number of inner packagings per outer (EQ mode only) |
items[].packing_group | string | No | I/II/III — disambiguates a UN number with more than one ADR Table A row (e.g. UN 1789). Ignored for single-row UN numbers. |
items[].variant_index | number | No | ADR Table A variant index (from /api/adr) — pins one row when variants share a packing group (concentration bands). |
A UN number that resolves to more than one ADR Table A row without a packing_group/variant_index returns HTTP 200 with human_review_required: true and a candidates[] list (no verdict) rather than checking a silently-guessed packing group. A packing_group that still leaves several rows is enough when those rows agree on class, column (7a), column (7b) and scope (UN 1790 PG I: two concentration bands, both LQ 0 / E0) — the item is answered with equivalent_variants and a variant_note; only when the rows disagree does it still ask for variant_index, listing only that packing group’s rows. In a batch, an unpinned multi-variant line is withheld on its own(status: "withheld", row fields null) while the other lines are answered;overall_status can never read qualifies while a line is withheld, andcandidates[] carries that line’s rows tagged with its item_index. Only a batch in which every line is ambiguous returns the no-items shape.
Example — check LQ for 0.5 L of petrol:
Response Fields
| Field | Type | Description |
|---|---|---|
mode | string | lq or eq |
overall_status | string | qualifies, does_not_qualify, partial, not_applicable (every item is listed “NOT SUBJECT TO ADR” in Table A — LQ/EQ provisions do not apply), or inconclusive (nothing in the batch could be compared). A batch holding any inconclusive or withheld item never reads qualifies. |
items[] | array | Per-item results with substance info, limits, and pass/fail |
items[].status | string | within_limit, exceeds_limit, not_permitted, not_subject (row is outside ADR scope — neither a pass nor a fail; scope rows also carry not_subject_to_adr / carriage_prohibited flags), inconclusive — Table A column (7a) states the limit in one dimension, and the quantity was sent in the other family (a mass against a volume limit, or the reverse). ADR states no density, so no verdict is given; re-send the quantity in the unit named by lq_limit_unit — or withheld: the line’s UN resolves to more than one Table A row and nothing pinned one. A withheld line carries withheld: true, an ambiguity_reason and null row fields (never the first row’s); its candidate rows are under candidates[] tagged with its item_index. |
items[].equivalent_variants | object[]? | Present when the supplied packing_group left several rows that agree on class, column (7a), column (7b) and scope: the rows (variant_index, proper_shipping_name) this verdict holds for; variant_note says so in words and which row’s fields are shown |
summary | object | Counts of qualifying, exceeding, and not-permitted items, plus not_subject, inconclusive and withheld when non-zero |
human_review_required | boolean? | true when at least one line is withheld — candidates[] then lists the withheld lines’ rows, each tagged with item_index and un_number |
references | object | ADR chapter and table references |
Search airlines by name, IATA code, ICAO code, AWB prefix, or country. The dataset contains6,357 airlines including 392 cargo airlines with AWB prefixes.
Query Modes
| Parameter | Type | Description | Match |
|---|---|---|---|
q | string | General search — matches name, codes, prefix, country. Smart: 2–3 digits match prefix only, 2–3 letters match IATA/ICAO only, 4+ chars search all fields. | Smart |
iata | string | IATA 2-letter code (e.g. EK) | Exact |
icao | string | ICAO 3-letter code (e.g. UAE) | Exact |
prefix | string | AWB 3-digit prefix (e.g. 176) | Exact |
country | string | Country name (e.g. Germany) | Partial |
Example Requests
AWB prefix lookup:
IATA code lookup:
Name search:
Response Fields
| Field | Type | Description |
|---|---|---|
slug | string | URL-friendly identifier |
airline_name | string | Official airline name |
iata_code | string | null | 2-character IATA designator |
icao_code | string | null | 3-character ICAO designator |
awb_prefix | string[] | null | 3-digit AWB prefix(es) — array, some airlines have multiple |
callsign | string | null | Radio callsign for ATC communication |
country | string | null | Country of registration |
has_cargo | boolean | true if airline has AWB prefix(es) |
aliases | string[] | null | Alternative names (e.g. cargo division name) |
status | string | active_verified · historical · unverified — see the note below. Absent means never audited; absence is a claim of its own and is never coerced to a value |
status_checked_at | string | Date the status was last established |
data_note | string | Plain-language caveat about this record's evidence, where one applies |
field_provenance | object | Per-FIELD source citations — which page confirms which field. Aggregators score zero and never appear |
sources | object[] | Record-level union of the per-field citations (with access dates) |
audited_at | string | Audited records only — date of the audit |
decision_rationale | string | Audited records only — why the published values were accepted |
verified | boolean | Deprecated — computed mirror of status === 'active_verified', kept for one release. Absent (not false) on never-audited records. Read status |
Verification status, and what it changes in the envelope
The airline dataset is still pending-verification, but individual records now carry their own evidence. active_verified means every identifier this API serves for that record — IATA code, ICAO code, AWB prefix — was confirmed against a page the carrier itself publishes (ICAO designator and callsign against FAA Order JO 7340.2), with the exact URL and access date in field_provenance. Aggregator and directory sites are scored at zero and never appear as evidence.historical means the carrier demonstrably ceased operating; the codes are kept as historical fact and may since have been reassigned. unverified means we looked and the evidence did not clear that bar — the honest default, not a slur on the record.
This is load-bearing in the envelope: an answer whose records are all active_verified is emitted at confidence.level: "high" with no advisory. Any other mix — one unverified record, one never-audited record, or an empty result — falls back to medium plus the PROVENANCE_PENDING warning. The lift is earned per answer and derived from the records in it, so it can never claim more than the evidence behind it.
Responses also carry a top-level _source citability envelope and a meta block whose airlines count is derived from the dataset at build time — never hand-typed.
Look up INCOTERMS 2020 trade terms. Returns all 11 terms by default, or filter by code or transport category. Each term includes seller/buyer responsibilities, risk and cost transfer points, insurance obligations, and practical guidance.
Parameters
| Parameter | Type | Description |
|---|---|---|
code | string | INCOTERM code (e.g. FOB, CIF, DDP) |
category | string | Filter by transport mode: any_mode or sea_only |
Omit all parameters to return all 11 INCOTERMS 2020 terms.
Example Requests
Single term lookup:
Filter by transport category:
All terms:
Shipping container specifications — internal/external dimensions, weights, door openings, and pallet capacity for all 10 standard ISO container types. Optionally calculate how many items fit in a specific container.
Query Modes
| Parameter | Type | Required | Description |
|---|---|---|---|
type | string | No | Container slug (e.g. 20ft-standard, 40ft-high-cube). Omit to list all. |
l | number | No | Item length in cm (requires type + w + h) |
w | number | No | Item width in cm |
h | number | No | Item height in cm |
wt | number | No | Item weight in kg |
qty | integer | No | Number of items |
Example Requests
List all containers:
Single container specs:
Loading calculation — how many 60×40×40cm boxes fit in a 40ft HC:
Convert between freight-relevant units — weights, volumes, lengths, and freight-specific conversions (CBM to chargeable weight, CBM to freight tonnes).
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
value | number | Yes | The number to convert |
from | string | Yes | Source unit code |
to | string | Yes | Target unit code |
Supported Unit Codes
| Group | Codes |
|---|---|
| Weight | kg, lbs, oz, tonnes, short_tons, long_tons |
| Volume | cbm, cuft, cuin, litres, gal_us, gal_uk |
| Length | cm, inches, m, feet, mm |
| Freight | chargeable_kg (target only, from=cbm), freight_tonnes (target only, from=cbm) |
Example Requests
Standard conversion:
CBM to chargeable weight (IATA 6000 divisor):
CBM to freight tonnes (W/M rule):
Search and browse Harmonized System (HS 2022) commodity codes. Supports text search by product description, exact code lookup with ancestor chain, and section browsing. Covers all 6,937 codes across 21 sections and 97 chapters.
Query Modes
| Parameter | Type | Description | Max results |
|---|---|---|---|
q | string | Case-insensitive search on descriptions and codes. Min 2 characters. | 50 |
code | string | Exact HS code lookup (2, 4, or 6 digit). Returns full details with ancestor chain and children. | 1 |
section | string | Browse by section (Roman numeral, e.g. II). Returns all chapters in that section. | All |
Provide exactly one parameter per request. Omitting all parameters returns a 400 with usage hints.
Example Requests
Search by description:
Code lookup with ancestors:
Browse section:
Calculate total CBM, loading metres (LDM), volumetric and mode-specific chargeable weight across a multi-item mixed consignment — per-line and grand totals, plus objective advisory flags (implausible density, mode/option mismatch, dangerous-goods presence by UN number against the ADR 2025 reference, and ISO 6346 container / IATA AWB check-digit validity). Supports sea, air, and road modes. Canonical request/response schema: consignment.v1.json.
Best-effort deterministic calculation and reference data only. Verify all inputs. Not regulatory, customs, or dangerous-goods compliance advice — you remain responsible for classification, documentation and carrier acceptance.
Request Body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
mode | string | No | road (default), air, or sea |
lines | array | Yes* | 1–50 canonical line objects (see below). Preferred. |
items | array | Yes* | Legacy flat alias (dimensions in cm, weight in kg). *Provide lines or items. |
options | object | No | air_volumetric_divisor (default 6000), container_number, awb_number |
Canonical Line Object (lines[])
| Field | Type | Required | Description |
|---|---|---|---|
quantity | integer | Yes | Number of identical pieces |
dims | object | Yes | { l, w, h, unit } — unit one of mm, cm, m, in |
weight | object | Yes | { value, unit } — unit one of kg, g, t, lb |
stackable | boolean | No | Stack two-high (halves loading-metre footprint) |
hs_code | string | No | HS commodity code (6–10 digits) |
un_number | string | No | UN number — triggers the dangerous-goods reference flag |
description | string | No | Item label |
Example Request
Estimate UK import duty and VAT for a commodity code using live GOV.UK Trade Tariff data. Accepts customs value, origin country, freight/insurance costs, and INCOTERM for CIF adjustment.
Request Body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
commodity_code | string | Yes | HS/tariff code (min 6 digits) |
origin_country | string | Yes | ISO 2-letter country code (e.g. CN, DE) |
customs_value | number | Yes | Goods value in GBP |
freight_cost | number | No | Freight cost in GBP (added to CIF value) |
insurance_cost | number | No | Insurance cost in GBP (added to CIF value) |
incoterm | string | No | INCOTERM (e.g. FOB, CIF, EXW) |
Example Request
Search and look up UN/LOCODE transport locations — 116,232+ seaports, airports, rail terminals, inland depots, and border crossings worldwide. Responses are cached for 24 hours.
Query Modes
| Parameter | Type | Description |
|---|---|---|
code | string | Exact UN/LOCODE lookup (e.g. GBLHR, NLRTM) |
q | string | Search by name (e.g. rotterdam, heathrow) |
country | string | Filter by country code (e.g. GB, NL) |
function | string | Filter by function: port, airport, rail, road, icd, border |
limit | integer | Max results (1–100, default: 20) |
Example Requests
Search by name:
Exact code lookup:
Filter by country and function:
Look up air freight Unit Load Device (ULD) specifications. 16 types including LD3 (AKE), PMC main deck pallet, temperature-controlled containers, and more. Returns dimensions, weights, volume, and aircraft compatibility.
Parameters
| Parameter | Type | Description |
|---|---|---|
type | string | ULD code or slug (e.g. AKE, PMC). Omit to list all. Alias: code. |
code | string | Alias for type — accepted because the response field is named code |
category | string | Filter: container, pallet, or special |
deck | string | Filter by deck: lower or main |
An unrecognised query parameter returns 400 naming it, rather than being ignored — a filter this endpoint does not understand must never come back looking like a successful unfiltered query.
Example Requests
Single ULD lookup:
Filter by category:
All ULD types:
Look up road freight vehicle and trailer specifications. 17 types covering articulated trailers, rigid trucks, and vans. Returns internal dimensions, payload limits, pallet capacity, and features.
Values carry their own semantics rather than leaving you to infer them. internal_dimensions.height is null on open-deck vehicles (flatbed, low-loader) — never 0, which would make a length × width × height calculation return zero volume. height_basis says what a null means (unbounded-open-top — no roof, so no constraint exists; varies-by-configuration; or unverified — a constraint exists but no trustworthy figure is held), and open_top states the physical fact separately. pallet_capacity_basis says whether a pallet count is floor geometry (floor-positions), capped by payload (weight-limited), an industry-convention figure, or unverified — which matters, because a floor-derived count and a payload-capped count are very different answers. max_pallet_height_mm gives the per-deck clearance on multi-deck vehicles and is null elsewhere, where the internal height is the usable clearance.
Parameters
| Parameter | Type | Description |
|---|---|---|
slug | string | Vehicle slug (e.g. standard-curtainsider). Omit to list all. |
category | string | Filter: articulated, rigid, or van |
region | string | Filter: EU or US |
Example Requests
Single vehicle lookup:
Filter by category:
Filter by region:
HTTP Status Codes
| Code | Meaning |
|---|---|
| 200 | Success — calculation result returned as JSON |
| 400 | Bad Request — missing or invalid parameters. Check the error message in the response. |
| 404 | Not Found — no results for the given query (airlines and ADR endpoints) |
| 405 | Method Not Allowed — only GET (or POST for /api/adr-calculator, /api/adr/lq-check, /api/shipment/summary) is supported |
| 500 | Internal Server Error — unexpected error, please report via GitHub |
Field Naming
All endpoints use snake_case field names in responses (e.g. internal_length_cm, max_gross_kg). POST request bodies on /api/duty and /api/consignment accept either casing for backwards compatibility — snake_case is the documented form.
Rate Limiting
The API is free to use. Anonymous rate limit: 25 requests per day per IP. Free API key: 100 requests per day. Pro: 50,000 requests per month.
Error Responses
All endpoints return standard HTTP error codes with a descriptive JSON error message:
Response Envelope (agent-facing, opt-in)
Every tool can also return a v1 response envelope — a thin, agent-facing wrapper that keeps the answer under result and adds the four things an autonomous agent needs to use and cite it safely: how much to trust it (confidence), where it came from(_source), a ready-to-quote line (citation), and — when relevant —what was corrected (normalized_input), advisories (warnings),blocking errors with a recovery hint (blocking_errors), and time-boxed validity(validity). It is fully described by the JSON Schema at /schemas/response-envelope.v1.json.
The envelope is opt-in. The flat legacy body (the answer at the top level) stays the default and is byte-unchanged, so existing REST consumers and the freightutils-mcp npm package are unaffected. Request the envelope explicitly with either ?envelope=1 or an Accept header:
Envelope v1.1 — the freshness revision (additive, negotiated). Request it with ?envelope=1.1 (query parameter only). On editioned reference datasets the _source block gains dataset_edition (what this response is served from), authority_current_edition (the authority’s current in-force edition at last check) and checked_at (when the two were last compared) — and whenever the served edition trails the authority’s, the response carries an EDITION_LAG warning, advisory and never blocking. Continuous datasets (live UK duty, airport snapshots) legitimately omit the three fields. ?envelope=1 remains byte-stable v1. Schema: /schemas/response-envelope.v1.1.json.
On the hosted MCP server (/api/mcp) the envelope is returned as structuredContent (validating against each tool’s output schema) and serves v1.1 unconditionally — its declared schema updates with each deploy — while content[0].text keeps the flat legacy JSON so text-parsing clients are unaffected. envelope_version ("1" or "1.1") lets any consumer detect the shape.
Confidence model
confidence.level is high / medium / low, and confidence.basissays why. A numeric score (0–1) is present only when the basis ismatch_quality.
| basis | Meaning | Typical level |
|---|---|---|
deterministic | Pure computation (CBM, chargeable weight, LDM, conversions). | high |
provenance | Reference-data lookup with an audit status. | high (verified) / medium (provenance pending) |
match_quality | Fuzzy / ranked search — carries a score. | from the score |
freshness | Computed over live, time-sensitive data (e.g. UK duty rates). | high |
Warnings & blocking errors
warnings are non-blocking advisories; blocking_errors mean no answer was produced (ok: false) and each carries a recovery hint (an action, and often atool + params to retry with). Both use a stable UPPER_SNAKE code taxonomy; the keys are omitted entirely when empty (never []).
| code | Meaning |
|---|---|
RATE_LIMITED | Quota exceeded (maps to the 429 + Retry-After). |
MISSING_INPUT | A required parameter was absent. |
INVALID_INPUT | A parameter was present but malformed. |
NOT_FOUND | An exact lookup (code / id) matched nothing. |
NO_MATCH | A search / ranked query returned nothing. |
FUZZY_BEST_MATCH | Advisory: the top result is a best-effort fuzzy match, not an exact hit. |
PROVENANCE_PENDING | Advisory: this dataset’s provenance is pending independent verification. |
METHOD_NOT_ALLOWED | Wrong HTTP method for the endpoint (e.g. GET on a POST-only tool). |
Provenance, citation & validity
_source is the canonical provenance (name, checked date orrequest-time, and provenance_status: verified / pending-verification /computed / live). citation.text is a ready-to-quote line, with an optionalqualifier hedge for fuzzy or not-legal-advice answers. validity appears only on time-boxed tools (regulatory editions, live rates) and carries effective_from / effective_to /as_of.
Fuzzy best-match (GET /api/airports?q=heathrow&envelope=1):
Blocking error with a recovery hint (GET /api/adr?un=9999&envelope=1 → 404):
Platform Commitments
Five pages that spell out what you can rely on:
Source Code & Issue Reporting
The FreightUtils MCP server is open source. Report bugs, request features, or contribute on GitHub: github.com/SoapyRED/freightutils-mcp. For data corrections or API support, email contact@freightutils.com.