Functional Weave
Code in TypeScript

logistics.incoterm-responsibility

Who pays which costs and where risk passes under an Incoterms 2020 rule, from EXW to DDP, FOB and CIF.

1.0.1 · published 2026-10-03 by charlie · Anterra

Pinned by 15 tests, run in TypeScript, Python and Rust.

What it does

Looks up an Incoterms rule: where the seller delivers (which is where risk passes to the buyer), and which party pays for each stage of the journey. The eleven rules of Incoterms 2020 are data; the function reads the row.

| rule | modes | risk passes | export clearance | main carriage | insurance | unloading at destination | import clearance and duties | |---|---|---|---|---|---|---|---| | EXW | any | seller's premises | buyer | buyer | none | buyer | buyer | | FCA | any | handed to buyer's carrier | seller | buyer | none | buyer | buyer | | CPT | any | handed to first carrier | seller | seller | none | buyer | buyer | | CIP | any | handed to first carrier | seller | seller | seller, ICC (A) | buyer | buyer | | DAP | any | destination | seller | seller | none | buyer | buyer | | DPU | any | destination, unloaded | seller | seller | none | seller | buyer | | DDP | any | destination | seller | seller | none | buyer | seller | | FAS | sea | alongside ship | seller | buyer | none | buyer | buyer | | FOB | sea | on board | seller | buyer | none | buyer | buyer | | CFR | sea | on board | seller | seller | none | buyer | buyer | | CIF | sea | on board | seller | seller | seller, ICC (C) | buyer | buyer |

For example

  • incotermResponsibility(EXW, 2,020) → rule EXW, edition 2,020, name Ex Works, any mode true, delivery At the seller's premises or another named place, placed at the buyer's disposal and not loaded., risk passes at des… EXW: the buyer does everything, even loading and export clearance
  • incotermResponsibility(FCA, 2,020) → rule FCA, edition 2,020, name Free Carrier, any mode true, delivery To the carrier or other person the buyer nominates: loaded on the buyer's transport at the seller's premises, o… FCA: seller clears for export and hands over to the buyer's carrier
  • incotermResponsibility(CPT, 2,020) → rule CPT, edition 2,020, name Carriage Paid To, any mode true, delivery Handed to the first carrier the seller contracts with; risk passes there, although the seller pays carriage… CPT: seller pays carriage but risk passes at the first carrier

The function

The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.

export function incotermResponsibility(rule: string, edition: number): IncotermResponsibility
rulestringthe three-letter code in capitals, e.g. "FCA"
editionintthe Incoterms edition the contract names, e.g. 2020 for "FCA Incoterms 2020"
returnsIncotermResponsibility

The types it declares, generated into your project

export type Party = "seller" | "buyer";

/** One rule's split of costs and risk between seller and buyer. */
export interface IncotermResponsibility {
  readonly rule: string;
  readonly edition: number;
  /** "Free Carrier" */
  readonly name: string;
  /** false for the four sea and inland waterway rules: FAS, FOB, CFR, CIF */
  readonly anyMode: boolean;
  /** where the seller delivers, which is where risk passes to the buyer */
  readonly delivery: string;
  /** true only for the D rules; under C rules the seller pays the carriage but risk passes at shipment */
  readonly riskPassesAtDestination: boolean;
  /** loading onto the collecting vehicle at the seller's premises */
  readonly loadingAtOrigin: Party;
  /** export formalities, licences and export duties */
  readonly exportClearance: Party;
  /** who contracts and pays for the main international carriage */
  readonly mainCarriage: Party;
  /** who must insure the goods; null when neither party is obliged to */
  readonly insuranceBy: Party | null;
  /** the minimum cover the rule requires, when there is an obligation */
  readonly insuranceCover: string | null;
  readonly unloadingAtDestination: Party;
  /** import formalities */
  readonly importClearance: Party;
  /** import duty, import VAT and other import taxes */
  readonly importDuties: Party;
}

Your code names it in one line, in the file that uses it

import { incotermResponsibility } from "#fune/logistics.incoterm-responsibility@^1";
impl/typescript.ts · 35 lines · open · raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

import { INCOTERMS } from "./logistics_incoterm_responsibility_data.ts";  ← this capability’s own data, compiled from data/incoterms.json into the same file by fune build
import { type IncotermResponsibility, type Party } from "./logistics_incoterm_responsibility_types.ts";

/**
 * Where the seller delivers (and risk passes) under an Incoterms rule, and
 * which party pays for each stage, from the ICC rules for the edition named.
 */
export function incotermResponsibility(rule: string, edition: number): IncotermResponsibility {
  if (!Number.isInteger(edition)) throw new TypeError(`edition must be a year, received ${edition}`);
  if (!INCOTERMS.some((r) => r.edition === edition)) {
    const editions = [...new Set(INCOTERMS.map((r) => r.edition))].join(", ");
    throw new RangeError(`no data for Incoterms ${edition}: this version covers ${editions}`);
  }
  const row = INCOTERMS.find((r) => r.edition === edition && r.rule === rule);
  if (row === undefined) {
    const rules = INCOTERMS.filter((r) => r.edition === edition).map((r) => r.rule).join(", ");
    throw new RangeError(`unknown Incoterms ${edition} rule "${rule}": expected one of ${rules}`);
  }
  return {
    rule: row.rule,
    edition: row.edition,
    name: row.name,
    anyMode: row.anyMode,
    delivery: row.delivery,
    riskPassesAtDestination: row.riskPassesAtDestination,
    loadingAtOrigin: row.loadingAtOrigin as Party,
    exportClearance: row.exportClearance as Party,
    mainCarriage: row.mainCarriage as Party,
    insuranceBy: row.insuranceBy as Party | null,
    insuranceCover: row.insuranceCover,
    unloadingAtDestination: row.unloadingAtDestination as Party,
    importClearance: row.importClearance as Party,
    importDuties: row.importDuties as Party,
  };
}

Install

fune build

With that line in your source, in a TypeScript project (language typescript in fune.project), fune build resolves it and nothing else, pins them in fune.lock, downloads only the TypeScript package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:

fune add logistics.incoterm-responsibility
Download for TypeScript logistics.incoterm-responsibility-1.0.1-typescript.fune · 23,446 bytes sha256 c73b36648406189df964dc499e747e7fea1bbfbf6b7e9d690d45f2690f93c592

The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./logistics.incoterm-responsibility-1.0.1-typescript.fune, or fetch it from a terminal with fune pull logistics.incoterm-responsibility@1.0.1:typescript.

The whole function, every language, is one file too: logistics.incoterm-responsibility-1.0.1.fune, 28,648 bytes, sha256 7c969bfc65419b68fd7060978248e8b5e566a203722cc52d1ed598d7ca14c5a8. It installs into a project of any language.

Customise it in your app

The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.

before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.

// fune: before logistics.incoterm-responsibility

after — your function gets the result and the arguments, and returns the final result.

// fune: after logistics.incoterm-responsibility

replace — it requires no other capability, so there is no dependency to replace.

step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show logistics.incoterm-responsibility --steps.

// fune: step logistics.incoterm-responsibility after <n|label>

Tests

A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.

CaseArgumentsExpected
EXW: the buyer does everything, even loading and export clearance EXW, 2,020 → rule EXW, edition 2,020, name Ex Works, any mode true, delivery At the seller's premises or another named place, placed at the buyer's disposal and not loaded., risk passes at des…
FCA: seller clears for export and hands over to the buyer's carrier FCA, 2,020 → rule FCA, edition 2,020, name Free Carrier, any mode true, delivery To the carrier or other person the buyer nominates: loaded on the buyer's transport at the seller's premises, o…
CPT: seller pays carriage but risk passes at the first carrier CPT, 2,020 → rule CPT, edition 2,020, name Carriage Paid To, any mode true, delivery Handed to the first carrier the seller contracts with; risk passes there, although the seller pays carriage…
CIP: all-risks cover, Institute Cargo Clauses (A), since 2020 CIP, 2,020 → rule CIP, edition 2,020, name Carriage and Insurance Paid To, any mode true, delivery Handed to the first carrier the seller contracts with; risk passes there, although the seller…
DAP: risk passes at destination, the buyer unloads and clears import DAP, 2,020 → rule DAP, edition 2,020, name Delivered at Place, any mode true, delivery At the named place of destination, on the arriving means of transport, ready for unloading., risk passes …
DPU: the only rule where the seller unloads DPU, 2,020 → rule DPU, edition 2,020, name Delivered at Place Unloaded, any mode true, delivery At the named place of destination, unloaded from the arriving means of transport., risk passes a…
DDP: the seller pays import duties and VAT, the buyer still unloads DDP, 2,020 → rule DDP, edition 2,020, name Delivered Duty Paid, any mode true, delivery At the named place of destination, on the arriving means of transport, ready for unloading and cleared f…
FAS: sea only, delivered alongside the ship FAS, 2,020 → rule FAS, edition 2,020, name Free Alongside Ship, any mode false, delivery Alongside the vessel the buyer nominates, at the named port of shipment., risk passes at destination fa…
FOB: sea only, delivered on board FOB, 2,020 → rule FOB, edition 2,020, name Free On Board, any mode false, delivery On board the vessel the buyer nominates, at the named port of shipment., risk passes at destination false, lo…
CFR: seller pays freight but risk passes on board CFR, 2,020 → rule CFR, edition 2,020, name Cost and Freight, any mode false, delivery On board the vessel at the port of shipment; risk passes there, although the seller pays freight to the na…
Show the other 5 tests
CaseArgumentsExpected
CIF: minimum cover, Institute Cargo Clauses (C) CIF, 2,020 → rule CIF, edition 2,020, name Cost, Insurance and Freight, any mode false, delivery On board the vessel at the port of shipment; risk passes there, although the seller pays freigh…
DAT was replaced by DPU in 2020 DAT, 2,020 → error: unknown Incoterms 2020 rule "DAT"
codes are capitals fob, 2,020 → error: unknown Incoterms 2020 rule "fob"
not an Incoterms rule C&F, 2,020 → error: unknown Incoterms 2020 rule "C&F"
an edition with no data FOB, 2,010 → error: no data for Incoterms 2010

More from the author

Loading at the seller's premises is the buyer's under EXW and the seller's under every other rule.

## What people get wrong

- **C rules move cost, not risk.** Under CPT, CIP, CFR and CIF the seller pays for the main carriage, but the goods travel at the buyer's risk from the moment they are handed to the first carrier or loaded on board. That is why the result has `riskPassesAtDestination` separately from `mainCarriage`. - **Insurance "none" means no obligation**, not "uninsured". Under the other nine rules neither party must insure; whoever bears the risk usually does. - **CIP and CIF differ in 2020.** CIP requires all-risks cover (Institute Cargo Clauses (A)); CIF still requires only the minimum, Clauses (C). - **DPU replaced DAT** in 2020 and is the only rule where the seller unloads. `DAT` with edition 2020 is an error, as is an edition with no data. - **Sea rules are for sea.** FAS, FOB, CFR and CIF are meant for goods loaded on a vessel, not for containers handed over at a terminal, where FCA, CPT and CIP fit better (ICC's own guidance).

Terminal handling and other charges at either end depend on the contract of carriage as well as the rule; under the C rules, unloading charges the seller paid under its contract of carriage are not recoverable from the buyer. This capability reports the rule's default split, not a contract's.

## Editions are data

A contract names its edition ("FOB Incoterms 2020"), and older editions stay in use for years, so the edition is an argument and not a date. Incoterms 2010 or a future edition would be new rows, not new code. The rule code must be the capitals as ICC writes it.

## Sources

- International Chamber of Commerce, Incoterms 2020 (ICC publication 723E); overview and 2020 changes, https://iccwbo.org/business-solutions/incoterms-rules/incoterms-2020/ - UK Department for Business and Trade, "Choosing the right incoterms for your goods", https://www.great.gov.uk/advice/prepare-for-export-procedures-and-logistics/international-trade-contracts-and-incoterms/

## Notices

Incoterms® is a registered trademark of the International Chamber of Commerce. This capability is not endorsed by the ICC.

1.0.1 adds its attribution notices (NOTICE). The code and the tests are unchanged.

Files

PathBytes
NOTICE159
README.md3,537
data/incoterms.json5,274
impl/python.py1,911
impl/rust.rs3,104
impl/typescript.ts1,670
vectors.json6,852