# Hub shipments

Route a shipment through one of our consolidation hubs instead of shipping directly from origin to destination.

## What is hub transit?

By default a quote ships **directly** from origin to destination. With **hub transit**, we route the shipment through an intermediate **hub** and split it into **two legs**:

- **Clearance leg** — the international leg from your origin into the hub region. This is the leg that clears customs.
- **Local leg** — the domestic / final-mile leg from the hub on to the destination.

You never choose the hub yourself. When you ask for hub transit, we select an eligible hub for your origin → destination lane and build both legs for you. Hub routing can unlock better rates and consolidated handling on lanes where it's supported.

## Requesting a hub quote


Set `hub_transit: true` in the [Create a quote](/reference/create-a-quote) request body. Everything else is identical to a direct quote.

```json
{
  "hub_transit": true,
  "origin_address": {
    "name": "John Doe",
    "company": "Tech Corp",
    "address_1": "123 Tech Street",
    "city": "Johannesburg",
    "province": "Gauteng",
    "postal_code": "2196",
    "country_id": 226,
    "email": "john@techcorp.example",
    "phone": "+27123456789",
    "classification": "business"
  },
  "destination_address": {
    "name": "Jane Smith",
    "company": "UK Business",
    "address_1": "456 London Road",
    "city": "London",
    "province": "London",
    "postal_code": "SW1A 2AA",
    "country_id": 70,
    "email": "jane@ukbusiness.example",
    "phone": "+442012345678",
    "classification": "business"
  },
  "purpose_of_shipment_id": 8,
  "value_of_goods": "600.00",
  "value_of_goods_currency_id": "ZAR",
  "mass_unit": "kg",
  "distance_unit": "cm",
  "packages": [
    {
      "weight": 5.5,
      "width": 30.0,
      "height": 20.0,
      "length": 40.0
    }
  ]
}
```

The `country_id` and `purpose_of_shipment_id` values above are examples. Take the real ids from [List countries](/reference/list-countries) and [List purposes of shipment](/reference/list-purposes-of-shipment).

If **no hub serves the route**, the request fails with `422 Unprocessable Content`:

```json
{
  "success": false,
  "error": "Hub transit No hub is available for this route.",
  "errors": ["Hub transit No hub is available for this route."]
}
```

When that happens, fall back to a direct quote (omit `hub_transit`, or send `false`).


## What a hub quote returns


A hub quote **collapses the two legs into a single estimate per service**, so you compare hub options exactly like direct ones — you don't see or pick legs individually.

- `hub_transit` on the quote is `true`.
- Each entry in `estimates` is **anchored on the clearance leg**: `estimate_id` is the clearance-leg estimate id (this is the id you pass when creating the order).
- `line_items` are the **combined costs of both legs**, grouped by description and summed in your account currency — so the price you see is the all-in hub price, not just one leg.

```json
{
  "quote": {
    "id": 1234,
    "dirty": false,
    "hub_transit": true,
    "errors": [],
    "estimates": [
      {
        "estimate_id": 55788,
        "shipping_company": "FedEx",
        "shipping_service": "Express Priority",
        "quote_expired": false,
        "line_items": [
          { "description": "Base Rate", "cost": 1450.00, "currency_id": "ZAR" },
          { "description": "Fuel Surcharge", "cost": 217.50, "currency_id": "ZAR" }
        ],
        "billable_weights": { "...": "same breakdown as a standard estimate" }
      }
    ]
  }
}
```

> Each `cost` above is the **sum across both legs** (clearance + local) for that line item — a hub estimate is one combined price, not two.


## Placing a hub order

Create the order exactly as you would for a direct quote — pass the hub estimate's `estimate_id` as `quote_estimate_id` to [Create an order](/reference/create-an-order). There is **nothing hub-specific to send**. The resulting order spans both hub legs.

### The hub lane is fixed at quote time

The route — **origin → hub → destination** — is locked when the hub quote is created. The `origin_address` and `destination_address` you send when placing the order **do not re-route a hub order**: they are ignored for routing (only `origin_address.export_code` is read, for the export licence). This keeps the order on the exact lane the rate was quoted for.

To ship a different origin or destination, request a **new** hub quote (`hub_transit: true`) for that lane and order from it — you can't re-point an existing hub order at order time.

## Waybills & documents for both legs


A hub order has two legs, each with its own waybill. Generating waybills ([Create a waybill](/reference/create-a-waybill)) produces one for **both** legs.

To retrieve them, read the order's **`shipments`** array, returned by [Create an order](/reference/create-an-order), [Get an order](/reference/get-an-order) and [Create a waybill](/reference/create-a-waybill).

The array is **ordered by route**: the first entry is the leg leaving your origin (**origin → hub**), and the last is the onward leg into your destination (**hub → destination**). A shipment carries no leg-role field, so **use this ordering** to tell the legs apart — `shipments[0]` is always the first leg of the journey, `shipments[1]` the next. Each entry carries that leg's own:

- `waybill` — the waybill / tracking number
- `waybill_document_link` — the waybill PDF for that leg
- `tracking_link` and `status`

```json
"shipments": [
  { "shipping_service": "...", "waybill": "WB-ORIGIN-TO-HUB",      "waybill_document_link": "https://.../leg-1-waybill.pdf", "tracking_link": "...", "status": "..." },
  { "shipping_service": "...", "waybill": "WB-HUB-TO-DESTINATION", "waybill_document_link": "https://.../leg-2-waybill.pdf", "tracking_link": "...", "status": "..." }
]
```

On a hub order the order's **top-level** `waybill`, `waybill_document_link`, `tracking_link` and `status` describe a **single leg**, so always read those per leg from `shipments[]`, in route order, to get **both** waybills.


### Documents

Documents you upload with [Create an order](/reference/create-an-order) attach once, to the leg that clears customs. On EU hub lanes the local leg is domestic or intra-EU and needs no commercial invoice, so one upload covers the order.

### Pickups

Both pickup calls ([List pickup slots](/reference/list-pickup-slots) and [Book a pickup](/reference/book-a-pickup)) work on the first leg, the one leaving your origin. There is nothing for you to schedule on the hub → destination leg.

### Cancelling

[Cancel an order](/reference/cancel-an-order) cancels both legs together.

## End-to-end flow

1. **Quote** — `POST /quotes` with `hub_transit: true` → a hub quote whose estimates are collapsed and clearance-anchored.
2. **Choose** — pick an estimate and read its `estimate_id`.
3. **Order** — `POST /orders` with that `quote_estimate_id` → a two-leg hub order; both legs' estimates are accepted automatically.
4. **Waybills** — `POST /orders/{id}/waybills` readies both legs; read each leg's waybill from `shipments[]`.
5. **Pickup** — `GET /orders/{id}/pickup_availabilities` then `POST /orders/{id}/schedule_pickups`, both for the first leg.
6. **Track** — poll `GET /orders/{id}` and read `shipments[]`, or register a [webhook](/reference/create-a-webhook); each leg fires its own events with the same order id.

## Important notes

- **Hub selection is automatic** — you request hub transit; we pick the hub. You can't specify one.
- **One estimate, both legs** — a hub estimate's `estimate_id` and `line_items` represent the clearance leg with the local leg folded in. You never accept the local leg yourself.
- **Availability is lane-dependent** — not every origin → destination has a serviceable hub; handle the `422 "No hub is available for this route."` response.
- **Same order endpoint** — hub vs direct is decided entirely at quote time. Ordering is identical; you just use a hub estimate's id.
