status: the commercial lifecycle, owned by the implementer (draft → purchased → voided).tracking.status: the physical lifecycle, projected from the shipment’s tracker (pre_transit → in_transit → delivered). See chapter 07.
purchased and exception at the same time. It can also be voided while its tracker still shows pre_transit because the carrier never scanned it.
1. The shipment object
The root holds only what describes the shipment itself. Everything else is grouped by concern into four nested objects: purchase, tracking, handover and returns.
This diagram shows the lean root, the four nested groups, and the arrays the root holds.
Rules:
documents[]holds every document: labels (one per handling unit), return labels, commercial invoices, CN22/CN23 forms, and DG declarations. There is no separatelabelfield. Clients pick bytype(andhandling_unitfor multi-unit labels).carrier_configsis the only carrier-settings field, everywhere. Before purchase, it holds the carriers to rate, with each carrier’s requested settings. After purchase, it holds exactly one key, the purchased carrier, with its effective config: your settings, merged with connection and carrier defaults, plus the resolvedconnectionandservice. The same map shape appears on rates, pickups, pickup schedules and manifests, always with one key when only one carrier is involved.- Additional printed references (PO, department, invoice) are carrier-specific and go in the
referencesstandardized feature ofcarrier_configs(chapter 01 §6.1).referenceis the single primary reference.
status
This state diagram shows the transitions in the table above.
2. Create a shipment
purchase instruction. If purchase is present, the label is bought in the same call, and Idempotency-Key is required.
A. From a rate (most common).
carrier_configs from the rate. Alongside a rate, only reference, handover.close_group and user_metadata are accepted. Billing and carrier settings are part of the price, so changing them means re-rating.
B. One call, no prior quote.
oneClickShipment. The implementer rates and buys atomically. carrier_configs MUST contain exactly one carrier, and its config MUST include a service. Otherwise the request fails with 400 parameter_invalid.
C. Draft first. Omit purchase. carrier_configs may hold several carriers, and they become the rating set for the draft. Edit it with POST /v1/shipments/{id}, rate it with POST /v1/shipments/{id}/rates, and buy it later.
This flowchart shows how the implementer picks one of the three create paths from the request body.
Request fields
Response
200with a shipment inpurchasedstatus when the carrier responds synchronously.202with a shipment inpurchasingstatus when the implementer is reconciling a timeout. Clients wait forshipment.purchasedorshipment.purchase_failed, or pollGET /v1/shipments/{id}.422 carrier_errorwhen the carrier rejects. The shipment is still created, inpurchase_failedstatus withpurchase.errorset, and the error carrieserror.shipment = "shp_..."so the client can fix and retry.
3. Purchase a draft
rate must belong to a quote made for this shipment. Without rate, the draft’s carrier_configs must hold exactly one carrier with a service. Purchasing a shipment that is already purchased returns 409 state_error / label_already_purchased, not a second label.
4. Re-rate a draft
carrier_configs (which replaces the draft’s), levels and timeout_ms. The result is a rate_quote with shipment: "shp_...".
5. Void a label
The response is the shipment, with
purchase.void set:
void.status:pending,succeeded,failed.void.refund.status:not_applicable(the carrier bills only on scan),pending,refunded,partially_refunded,rejected.- Errors:
409 state_error/manifest_in_progress,409 state_error/already_manifested,409 state_error/already_in_transit,422 carrier_error/void_window_expired. - Voiding is idempotent. Voiding a
voidedshipment returns it unchanged with200.
6. Returns
returns.is_return: true and returns.outbound_shipment set, and sets returns.return_shipment on the original. ship_from/ship_to are swapped (ship_to becomes the original return_to). Handling units, items and order are copied, and customs_declaration is copied with contents_type: "returned_goods". Pass handling_units to return only some items. Billing defaults to the merchant’s connection account, and bill_to defaults to the outbound ship_from (chapter 01 §1.1).
delivery_method is one of:
label: a printable label, indocuments[type=label].qr_code: a carrier drop-off QR code, indocuments[type=label, format=png].email: the carrier emails the label to the customer’s email, which isship_from.emailon the return.pickup: the carrier collects from the customer. This requires the pickups module.
7. Retrieve, update, list, delete
- Update: In
draftandpurchase_failed, every input field is editable. In other statuses, onlyuser_metadata,reference(stored only, not re-sent to the carrier) andredactare editable. - Delete: Only
draftandpurchase_failedshipments can be deleted. Others return409 state_error. - List filters:
status,tracking.status,tracking.number,carrier,connection,purchase.service.code,reference,order.id,returns.is_return,handover.manifest(nonefor unmanifested),handover.pickup,handover.close_group,ship_date[gte|lte],created[gte|lte],user_metadata[key].carrierandconnectionmatch the key ofcarrier_configsand itsconnection. - Expandable:
purchase.rate,purchase.rate_quote,purchase.service.connection,tracking.tracker,tracking.journey,handover.pickup,handover.manifest,returns.outbound_shipment,returns.return_shipment.
8. Re-render a label
labels.reformat), or converts them locally (PDF ↔ PNG). It never re-purchases. The new documents are appended to documents[].
9. Events
shipment.created, shipment.updated, shipment.purchased, shipment.purchase_failed, shipment.void_requested, shipment.voided, shipment.void_failed, shipment.refund_updated, shipment.manifested, shipment.tracking_updated, shipment.charges_adjusted. The last fires when a carrier invoice adjustment arrives through inbound reconciliation.