Originally proposed in: https://github.com/commerce-operations-foundation/mcp-reference-server/pull/24
shippingAccountNumber and shippingAccountNumberDuties are proposed to support cases where freight and duty/tax payments are billed to different carrier accounts (common in DDP vs DAP/DDU scenarios). The proposed naming is shipping_freight_payor_account and shipping_duty_payor_account. These are carrier account numbers (FedEx, UPS, DHL), distinct from customs or tax authority identifiers. Decision needed on naming and whether additional metadata (carrier name, account type) is required alongside the account number string.
What the existing schema tells us:
- shippingMethod (string) and shippingCarrier (string) already exist as flat header fields
- The PR adds shippingAccountNumber and shippingAccountNumberDuties as flat strings
- All three established nodes (crossBorderParties, crossBorderCharges, shippingCharges) use sub-objects with additionalProperties: false
The operational reality - A single shipment can have up to three distinct billing relationships with a carrier:
- Freight charges: who pays the carrier for transport
- Duty and tax charges: who pays the carrier's disbursement of customs charges (relevant in DDP where the carrier advances payment to customs on the seller's behalf)
- The carrier account itself: which carrier, which account number, what type of account
A bare string field handles none of this cleanly. FedEx, UPS, and DHL all require carrier name plus account number in their API payloads. Two separate flat strings cannot represent a third-party billing scenario where freight and duties go to different accounts at different carriers.Proposed structure
"carrierBilling": {
"type": "object",
"description": "Carrier account configuration for freight and duty/tax billing. Account numbers are carrier-issued identifiers (FedEx, UPS, DHL, etc.), distinct from customs registration numbers in crossBorderParties. Relevant primarily for DDP, DAP, and DDU shipments where billing responsibility is explicitly assigned.",
"properties": {
"freightPayor": {
"type": "object",
"description": "Account responsible for carrier freight charges.",
"properties": {
"payorType": {
"description": "Who bears the freight cost.",
"type": "string",
"enum": ["SELLER", "BUYER", "THIRD_PARTY"]
},
"carrierAccountNumber": {
"description": "Carrier-issued account number to bill for freight. Required when payorType is BUYER or THIRD_PARTY. Omit if seller default account applies.",
"type": "string"
},
"carrierName": {
"description": "Carrier associated with this account number. Must match the shippingCarrier field at order header level. Included here to make the billing record self-contained for TMS consumption.",
"type": "string",
"enum": ["FEDEX", "UPS", "DHL", "USPS", "OTHER"]
}
},
"required": ["payorType"],
"additionalProperties": false
},
"dutyTaxPayor": {
"type": "object",
"description": "Account responsible for duty and tax charges disbursed by the carrier. Relevant when ddpElected is true in crossBorderCharges, or when the importer holds a carrier account for duty billing.",
"properties": {
"payorType": {
"description": "Who bears the duty and tax cost.",
"type": "string",
"enum": ["SELLER", "BUYER", "THIRD_PARTY"]
},
"carrierAccountNumber": {
"description": "Carrier-issued account number to bill for duties and taxes. Required when payorType is BUYER or THIRD_PARTY. Omit if same as freightPayor account.",
"type": "string"
},
"carrierName": {
"description": "Carrier associated with this duty billing account. May differ from the freight carrier in multi-leg shipments.",
"type": "string",
"enum": ["FEDEX", "UPS", "DHL", "USPS", "OTHER"]
}
},
"required": ["payorType"],
"additionalProperties": false
}
},
"additionalProperties": false
}
payorType as an enum is the most important addition. The flat string proposals (shippingAccountNumber, shippingAccountNumberDuties) implicitly assume third-party billing is always in play. In practice, the absence of an account number means the seller's default account applies, but that is ambiguous at the TMS layer. An explicit payorType of SELLER means the TMS uses the configured default. BUYER or THIRD_PARTY triggers the account number requirement.
Separating freightPayor and dutyTaxPayor into sub-objects rather than parallel flat fields means each billing relationship is self-contained. The TMS reads freightPayor to build the freight billing section of the carrier API call, and dutyTaxPayor for the customs advancement billing section. These map directly to how FedEx (ShippingChargesPayment and DutiesAndTaxesPayment), UPS (PaymentInformation/ShipmentCharge), and DHL (ShippingPayment and DutyAndTaxPayment) structure their own API schemas.
carrierName belongs inside each payor object rather than relying solely on the header-level shippingCarrier field because duty billing can route to a different carrier account than the freight leg, particularly in multi-leg international movements where a domestic carrier hands off to an international carrier but the shipper holds a DHL account for customs advancement.
Full cross-border schema map updated:
order (header)
├── shippingCarrier ← existing flat field
├── shippingMethod ← existing flat field
├── incoterms ← existing flat field
├── reasonForExport ← proposed enum
├── crossBorderParties ← party identity and regulatory IDs
│ ├── importerCustomsId
│ ├── exporterCustomsId
│ ├── importerTaxId
│ └── exporterTaxId
├── crossBorderCharges ← taxes and duties collected at checkout
│ ├── taxCollected
│ ├── dutyCollected
│ ├── duties[]
│ ├── taxScheme
│ ├── iossNumber
│ └── ddpElected
├── shippingCharges ← shipping financials for customs valuation
│ ├── shippingCost
│ ├── insuranceAmount
│ ├── additionalFees
│ ├── customsValuationBasis
│ └── sellerPaidShipping
└── carrierBilling ← carrier account and billing responsibility
├── freightPayor
│ ├── payorType
│ ├── carrierAccountNumber
│ └── carrierName
└── dutyTaxPayor
├── payorType
├── carrierAccountNumber
└── carrierName
NOTES:
carrierName as an enum will need an OTHER escape hatch and likely a carrierNameFreeText companion field for regional carriers not in the list. The four named carriers cover the majority of international ecommerce volume but the schema should not break for a shipper using Aramex, SF Express, or a national postal operator.
The required: ["payorType"] constraint means carrierBilling can be included with only a payorType of SELLER and no account number, which is the most common case for domestic or standard international orders. This keeps the node lightweight for the majority of orders while fully supporting complex billing configurations when needed.
Originally proposed in: https://github.com/commerce-operations-foundation/mcp-reference-server/pull/24
What the existing schema tells us:
The operational reality - A single shipment can have up to three distinct billing relationships with a carrier:
A bare string field handles none of this cleanly. FedEx, UPS, and DHL all require carrier name plus account number in their API payloads. Two separate flat strings cannot represent a third-party billing scenario where freight and duties go to different accounts at different carriers.Proposed structure
payorType as an enum is the most important addition. The flat string proposals (shippingAccountNumber, shippingAccountNumberDuties) implicitly assume third-party billing is always in play. In practice, the absence of an account number means the seller's default account applies, but that is ambiguous at the TMS layer. An explicit payorType of SELLER means the TMS uses the configured default. BUYER or THIRD_PARTY triggers the account number requirement.
Separating freightPayor and dutyTaxPayor into sub-objects rather than parallel flat fields means each billing relationship is self-contained. The TMS reads freightPayor to build the freight billing section of the carrier API call, and dutyTaxPayor for the customs advancement billing section. These map directly to how FedEx (ShippingChargesPayment and DutiesAndTaxesPayment), UPS (PaymentInformation/ShipmentCharge), and DHL (ShippingPayment and DutyAndTaxPayment) structure their own API schemas.
carrierName belongs inside each payor object rather than relying solely on the header-level shippingCarrier field because duty billing can route to a different carrier account than the freight leg, particularly in multi-leg international movements where a domestic carrier hands off to an international carrier but the shipper holds a DHL account for customs advancement.
Full cross-border schema map updated:
NOTES:
carrierName as an enum will need an OTHER escape hatch and likely a carrierNameFreeText companion field for regional carriers not in the list. The four named carriers cover the majority of international ecommerce volume but the schema should not break for a shipper using Aramex, SF Express, or a national postal operator.
The required: ["payorType"] constraint means carrierBilling can be included with only a payorType of SELLER and no account number, which is the most common case for domestic or standard international orders. This keeps the node lightweight for the majority of orders while fully supporting complex billing configurations when needed.