bill_to plus billing. What is in the box lives in items, inside the parcel that physically holds them. What was sold lives in the order. What is declared to customs lives in the customs declaration. Which product, with which features, lives in carrier_configs: the service plus standardized and carrier-specific features, per carrier. Earlier drafts, and most carrier APIs, fold several of these into a single “customs” object. That object then breaks the first time a parcel is domestic but still has dangerous goods, or an order splits across two handling_units.
The same structure as a class diagram, with the cardinality of each slot:
1. Contact
A party to a shipment, with its address nested. This follows Stripe’sshipping and billing_details, and the shipFrom / shipTo / billTo convention common in carrier APIs.
A shipment has four party slots, all of type Contact:
1.1 Bill-to and billing
bill_to says who the carrier invoices. billing says how, meaning which carrier account is charged.
Rules:
- Carriers validate third-party and collect billing against the account’s registered country and postal code. The implementer sends
bill_to.address.countryandpostal_codewith the account number, sobill_to.addressMUST match the address the carrier has on file for that account. - Billing affects price. Recipient and third-party accounts are rated on their own contracts, or not at all. So
bill_toandbillingbelong to the rate request, and a rate is only valid for the billing it was quoted with. Carriers that cannot quote another party’s account return list rates, withrate.pricing: "list". - Returns. On a return (
returns.is_return: true),ship_fromis the customer, butsenderbilling still means the connection’s account, which is the merchant.bill_tothen defaults to the outbound shipment’sship_from, not the customer. bill_tois not the same asorder.buyer. The buyer is who the merchant invoiced for the goods.bill_tois who the carrier invoices for the transport. On a commercial invoice, the “sold to” party isorder.buyer, falling back toship_to.- Duties and taxes have their own billing in
customs_declaration.duties(§5), because a shipment can be freight-prepaid by the sender and duties-collect from the recipient.
billing.paid_by decides the billed account and the default bill_to:
Address
Street number rules.
- When a carrier wants it separate (DHL Parcel DE, PostNL, DPD, Correios): the implementer uses
street_numberif it is given. Otherwise it MAY parse it out ofline1. If parsing is ambiguous and the carrier requires the field, the request fails with400 parameter_missing,param: "ship_to.address.street_number", rather than guessing. - When a carrier wants one street line (most US, UK and FR carriers): the implementer joins the two in the destination country’s order. That is number first in US, GB, FR, CA and AU (
10 Downing St), and number last in DE, NL, BE, AT, CH, PL and BR (Hauptstraße 5). - Clients never put the number in both fields.
address be reused by itself on pickups and manifests.
2. Handling unit
Anything that gets its own label: a parcel, an envelope, a pallet, a crate. The spec uses one object for all of them. The freight term “handling unit” covers both worlds, and a shipment can mix them, such as two pallets plus a loose carton.
Implementers MUST reject a unit whose items’ summed weight (
unit_gross_weight, falling back to unit_net_weight, × quantity) exceeds the unit’s weight (parameter_invalid, param: "handling_units[0].weight"). They MUST NOT reject the opposite case, because packaging has weight.
Services declare which unit types they accept (chapter 14 §1, limits.handling_unit_types). A rate request mixing types that no service accepts returns no rates for that carrier, with errors[].code = "handling_unit_type_unsupported".
3. Item
One line of goods inside a parcel. The fields follow the richer commodity models of cross-border carriers, with HS codes split by direction and dangerous goods moved here, so that they sit on the thing that is dangerous.
Net and gross weight. Commercial invoices from DHL Express and FedEx, EU export declarations, and several import regimes (CN, BR, IN) ask for both per line. Rules:
- Declarations use
unit_net_weight × quantityas the line’s net weight. - Where a carrier or declaration needs line gross weight and
unit_gross_weightis absent, implementers use the net weight and report it inmessages[](gross_weight_derived). - Neither field replaces the parcel
weight, which is the gross weight of the packed shipping carton, and the figure carriers bill on.
6109.10.0012 on a US export filing and 6109100010 on the UK import tariff. Carriers ask for different ones: export declarations (AES/EEI, the EU export system) want the export code, and DDP pricing and import clearance want the import code. One hs_code field forces every integrator to guess which one the carrier meant. Rules:
- Supply at least one of the two codes for customs. If only one is given, implementers MAY use its first 6 digits where the other is needed, and MUST report that in
messages[](hs_code_derived). - The implementer MUST NOT invent a code. Classification is a separate concern, and an implementer offering it exposes it as its own capability (
classification) that returns suggestions, never silently filling these fields.
ItemCompliance
control_type is one of dual_use, military, sanctions, agricultural, pharmaceutical, cites, other.
DangerousGoods
Lives on the item, because a lithium battery is a property of the power bank, not of the shipment.
If any item has
dangerous_goods, the implementer MUST only rate services whose carrier declares the dangerous_goods capability and accepts that class. The implementer MUST NOT silently drop the field, because the failure mode is a fire on an aircraft.
4. Order
Optional. The commercial transaction behind the shipment, which is what was sold and for how much. It is an embedded value, not a resource, and order management stays in the OMS. Customs needs it for invoice data, DDP and VAT calculation (freight is part of the customs value in the EU and UK), and documents.
An order may span several shipments (a split shipment). Send the same
order.id on each one, and the implementer MUST NOT assume order.total equals the sum of one shipment’s items.
5. Customs declaration
Required when origin and destination are in different customs territories. The implementer decides that, including intra-EU, GB↔NI, US↔PR, and the Canary Islands. The what is declared fromhandling_units[].items[], so this object holds only the declaration itself.
Corridor-specific fields with no canonical home (Brazil’s CPF purpose, India’s IEC) go in the carrier config, where the carrier’s schema validates them. The declaration has no free-form
options bag.
Why at rating time. Cross-border carriers price landed cost (duty plus tax plus fees) into the quote. Because the order, the items and the declaration are all accepted at rating time, a DDP rate can be exact rather than estimated.
6. Carrier configs: products and features
carrier_configs is the shipment’s product and feature configuration. It is the rethought successor of three things integrators already know:
It is what a shipping method configuration holds in an ERP or WMS (the carrier, the product, and the additional services enabled for it), carried on the shipment.
* features.
Semantics
- Keys select carriers. Each carrier key adds that carrier to the rating set, and
{}means “this carrier with its defaults”.*selects no carrier: it holds standardized features applied to every rated carrier. If no carrier key is present, every active connection is rated, with*applied. serviceselects the product. In a rate request it narrows that carrier to one service, and omitting it returns every eligible service. A purchase without arateneeds exactly one carrier key with aservice.connectionselects the account when the account has several for the carrier.- Standardized features (§6.1) have one name, one type and one meaning across all carriers. The implementer translates each one into the carrier’s own product codes, flags and service options, exactly as Karrio’s connectors map
optionstoday. They may appear in*or in a carrier entry. - Carrier-specific features cover everything the carrier offers that the spec does not standardize. Their names and types come from the carrier’s
shipment_config_schema(generated from its catalog, chapter 14 §3). They are allowed only inside that carrier’s entry, never in*. Standardized names are reserved, and a carrier-specific feature MUST NOT reuse one with a different meaning. - Precedence, from lowest to highest: the carrier’s defaults, then the connection’s
config.shipment_defaults, then*, then the carrier entry. - Unknown keys are rejected (
400 parameter_unknown,param: "carrier_configs.fedex.signture").
Honoring standardized features
A standardized feature is a requirement, not a hint. The implementer MUST NOT silently drop one.- Rating. A service that cannot provide a requested standardized feature is excluded from
rates[]. The exclusion is listed inrate_quote.errors[]withcode: "option_unsupported"andparamnaming the feature. A shipper asking for adult signature never sees a rate that cannot deliver it. - Purchase. It fails with
422 carrier_error/option_unsupported. - Transparency. Every rate states how each standardized feature was fulfilled at that carrier, so the mapping is visible rather than implied:
rate.carrier_configs is the effective configuration (one key) that a purchase of the rate buys. rate.features explains, per standardized feature, the carrier’s native code and its price.
Flow
One shape everywhere
Rates, purchased shipments, pickups, pickup schedules and manifests all usecarrier_configs. Where only one carrier is involved, the map has one key and no *. The schemas are shipment_config_schema, pickup_config_schema and manifest_config_schema. There is no singular carrier_config field anywhere in the spec.
6.1 Standardized features
The normative registry. The set is append-only, and a feature graduates into it when two or more carriers offer the same semantics. Each carrier declares which features it supports incarriers/{id}.standardized_features, per service in its catalog.
Why one object. Splitting the product (
service) from its features (options) forced clients to know which features each product allows, and to re-send both on every call. Putting them together under the carrier they belong to makes the request read like a shipping method configuration. Standardized features keep cross-carrier intent simple. Carrier-specific features keep every carrier’s full surface. rate.features makes the translation between them auditable.
7. Service
Read-only on rates and shipments:codeis the implementer’s normalized service code ({carrier}_{service}). It is stable across versions, and it is whatcarrier_configs.*.servicetakes.carrier_codeis the raw carrier product code.levelis one ofsame_day,overnight,express,standard,economy,freight,other.
8. Charge
kind values: base, fuel_surcharge, residential_surcharge, remote_area_surcharge, peak_surcharge, oversize_surcharge, dangerous_goods_surcharge, option (with an option key naming the carrier-config key), insurance, duty, tax, customs_fee, pickup_fee, discount (negative), adjustment, other.
9. Delivery estimate
10. Document (label, invoice, manifest PDF)
type:label,return_label,commercial_invoice,proforma_invoice,certificate_of_origin,customs_declaration(CN22/CN23),packing_slip,pick_list,manifest,scan_form,close_report,cod_report,dangerous_goods_report,order_summary,pickup_confirmation,dangerous_goods_declaration,consolidation_label,bill_of_lading,cmr,air_waybill,delivery_receipt,insurance_certificate,invoice,custom,other. Documents rendered from a template also carrytemplateandtemplate_version(chapter 25).format:pdf,png,zpl,epl,gif,jpg. ZPL is returned as text at 203 dpi unlesslabel.dpiis 300.size:4x6,4x8,a4,a5,a6,letter, orWxHin inches.urlis a signed, short-lived URL.GET /v1/shipments/{id}re-signs it. Passlabel.inline: trueat purchase to get base64 indatainstead.
11. References
reference is a string up to 35 characters that the carrier prints on the label. Carriers that support several get the references standardized feature of carrier_configs (§6.1, §6). Implementers truncate per carrier and report it in messages[].
12. Messages
Successful responses MAY carry non-fatalmessages[]:
info, warning. Errors are never messages.