Skip to main content

Hub Shipments (Hub Transit)

Route a shipment through one of Stream's 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, Stream routes the shipment through an intermediate hub and splits 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, Stream selects an eligible hub for your origin → destination lane and builds 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 Quote request body. Everything else is identical to a direct quote.

{
"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.com",
"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.com",
"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
}
]
}

If no hub serves the route, the request fails with 422 Unprocessable Entity:

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

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.
{
"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 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 Waybill) produces one for both legs.

To retrieve them, read the order's shipments array, returned on Get Orders and in the waybill response.

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
"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": "..." }
]

The order's top-level waybill/waybill_document_link reflect only the clearance leg (the order's primary shipment), so for a hub order always read the per-leg links from shipments[], in route order, to get both waybills.

End-to-end flow

  1. QuotePOST /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. OrderPOST /orders with that quote_estimate_id → a two-leg hub order; both legs' estimates are accepted automatically.

Important notes

  • Hub selection is automatic — you request hub transit; Stream picks 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.