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
incoterm_responsibility(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 clearanceincoterm_responsibility(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 carrierincoterm_responsibility(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.
pub fn incoterm_responsibility(rule: &str, edition: i64) -> IncotermResponsibility
| rule | string | the three-letter code in capitals, e.g. "FCA" |
| edition | int | the Incoterms edition the contract names, e.g. 2020 for "FCA Incoterms 2020" |
| returns | IncotermResponsibility |
The types it declares, generated into your project
// Party is a string in Rust, one of: "seller", "buyer".
// Parameters take it as &str and results hold it as String.
/// One rule's split of costs and risk between seller and buyer.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IncotermResponsibility {
pub rule: String,
pub edition: i64,
/// "Free Carrier"
pub name: String,
/// false for the four sea and inland waterway rules: FAS, FOB, CFR, CIF
pub any_mode: bool,
/// where the seller delivers, which is where risk passes to the buyer
pub delivery: String,
/// true only for the D rules; under C rules the seller pays the carriage but risk passes at shipment
pub risk_passes_at_destination: bool,
/// loading onto the collecting vehicle at the seller's premises
pub loading_at_origin: String,
/// export formalities, licences and export duties
pub export_clearance: String,
/// who contracts and pays for the main international carriage
pub main_carriage: String,
/// who must insure the goods; null when neither party is obliged to
pub insurance_by: Option<String>,
/// the minimum cover the rule requires, when there is an obligation
pub insurance_cover: Option<String>,
pub unloading_at_destination: String,
/// import formalities
pub import_clearance: String,
/// import duty, import VAT and other import taxes
pub import_duties: String,
}
Your code names it in one line, in the file that uses it
fune!(logistics.incoterm-responsibility@^1); // then call incoterm_responsibility(…)
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::logistics_incoterm_responsibility_data::INCOTERMS; ← this capability’s own data, compiled from data/incoterms.json into the same file by fune build
/// 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.
///
/// # Panics
/// Panics on an edition with no data or a rule that edition does not have.
pub fn incoterm_responsibility(rule: &str, edition: i64) -> IncotermResponsibility {
let mut editions: Vec<i64> = Vec::new();
for r in INCOTERMS {
if !editions.contains(&r.edition) {
editions.push(r.edition);
}
}
if !editions.contains(&edition) {
let list: Vec<String> = editions.iter().map(|e| e.to_string()).collect();
panic!("no data for Incoterms {}: this version covers {}", edition, list.join(", "));
}
let row = match INCOTERMS.iter().find(|r| r.edition == edition && r.rule == rule) {
Some(r) => r,
None => {
let rules: Vec<&str> = INCOTERMS.iter().filter(|r| r.edition == edition).map(|r| r.rule).collect();
panic!("unknown Incoterms {} rule \"{}\": expected one of {}", edition, rule, rules.join(", "));
}
};
IncotermResponsibility {
rule: row.rule.to_string(),
edition: row.edition,
name: row.name.to_string(),
any_mode: row.any_mode,
delivery: row.delivery.to_string(),
risk_passes_at_destination: row.risk_passes_at_destination,
loading_at_origin: row.loading_at_origin.to_string(),
export_clearance: row.export_clearance.to_string(),
main_carriage: row.main_carriage.to_string(),
insurance_by: row.insurance_by.map(|s| s.to_string()),
insurance_cover: row.insurance_cover.map(|s| s.to_string()),
unloading_at_destination: row.unloading_at_destination.to_string(),
import_clearance: row.import_clearance.to_string(),
import_duties: row.import_duties.to_string(),
}
}
pub fn incoterm_responsibility_to_value(r: &IncotermResponsibility) -> Value {
let opt = |v: &Option<String>| match v {
Some(s) => Value::str(s),
None => Value::Null,
};
Value::obj(vec![
("rule", Value::str(&r.rule)),
("edition", Value::Int(r.edition)),
("name", Value::str(&r.name)),
("anyMode", Value::Bool(r.any_mode)),
("delivery", Value::str(&r.delivery)),
("riskPassesAtDestination", Value::Bool(r.risk_passes_at_destination)),
("loadingAtOrigin", Value::str(&r.loading_at_origin)),
("exportClearance", Value::str(&r.export_clearance)),
("mainCarriage", Value::str(&r.main_carriage)),
("insuranceBy", opt(&r.insurance_by)),
("insuranceCover", opt(&r.insurance_cover)),
("unloadingAtDestination", Value::str(&r.unloading_at_destination)),
("importClearance", Value::str(&r.import_clearance)),
("importDuties", Value::str(&r.import_duties)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
incoterm_responsibility_to_value(&incoterm_responsibility(args[0].as_str(), args[1].as_i64()))
}Install
fune build
With that line in your source, in a Rust project (language rust in fune.project), fune build resolves it and nothing else, pins them in fune.lock, downloads only the Rust 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. A crate’s build.rs runs it before every compile. Or pin a range in fune.project and build in one step:
fune add logistics.incoterm-responsibility
The manifest, vectors and README with only the Rust implementation. Install it without the registry with fune add ./logistics.incoterm-responsibility-1.0.1-rust.fune, or fetch it from a terminal with fune pull logistics.incoterm-responsibility@1.0.1:rust.
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.
| Case | Arguments | Expected | |
|---|---|---|---|
| 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
| Case | Arguments | Expected | |
|---|---|---|---|
| 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
| Path | Bytes |
|---|---|
| NOTICE | 159 |
| README.md | 3,537 |
| data/incoterms.json | 5,274 |
| impl/python.py | 1,911 |
| impl/rust.rs | 3,104 |
| impl/typescript.ts | 1,670 |
| vectors.json | 6,852 |