A shipment is the record of moving handling units from one party to another with one carrier service. Buying a shipment produces its label. It keeps two lifecycles apart:
  • 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.
They are separate because they change for different reasons. A shipment can be 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 separate label field. Clients pick by type (and handling_unit for multi-unit labels).
  • carrier_configs is 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 resolved connection and service. 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 references standardized feature of carrier_configs (chapter 01 §6.1). reference is the single primary reference.

status

This state diagram shows the transitions in the table above.

2. Create a shipment

The request body uses the root fields of the object, plus an optional 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).
The shipment is built from the rate’s quote: contacts, billing, handling units, order and declaration from the quote request, and 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.
This mirrors Amazon Shipping’s 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

  • 200 with a shipment in purchased status when the carrier responds synchronously.
  • 202 with a shipment in purchasing status when the implementer is reconciling a timeout. Clients wait for shipment.purchased or shipment.purchase_failed, or poll GET /v1/shipments/{id}.
  • 422 carrier_error when the carrier rejects. The shipment is still created, in purchase_failed status with purchase.error set, and the error carries error.shipment = "shp_..." so the client can fix and retry.
This sequence shows the three purchase outcomes, including reconciliation after a carrier timeout (chapter 00 §8). It applies to both create-with-purchase and purchasing a draft.

3. Purchase a draft

The body is the same purchase instruction. 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

The body accepts 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 voided shipment returns it unchanged with 200.
This sequence shows a void, with the carrier settling it either at once or later, and the refund tracked separately. Why void is not DELETE. The label existed, may have been printed, and may yet be scanned. The record must survive for reconciliation against the carrier invoice. This is the Stripe refund pattern applied to postage.

6. Returns

This creates a new shipment with 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, in documents[type=label].
  • qr_code: a carrier drop-off QR code, in documents[type=label, format=png].
  • email: the carrier emails the label to the customer’s email, which is ship_from.email on the return.
  • pickup: the carrier collects from the customer. This requires the pickups module.

7. Retrieve, update, list, delete

  • Update: In draft and purchase_failed, every input field is editable. In other statuses, only user_metadata, reference (stored only, not re-sent to the carrier) and redact are editable.
  • Delete: Only draft and purchase_failed shipments can be deleted. Others return 409 state_error.
  • List filters: status, tracking.status, tracking.number, carrier, connection, purchase.service.code, reference, order.id, returns.is_return, handover.manifest (none for unmanifested), handover.pickup, handover.close_group, ship_date[gte|lte], created[gte|lte], user_metadata[key]. carrier and connection match the key of carrier_configs and its connection.
  • 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

This re-renders labels in another format when the carrier supports it (capability 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.