Multi-Piece Shipment

Shipments with multiple packages sent to the same destination can be grouped together in a multi-piece shipment to save money.

Multi-piece Shipments on Shippo are only supported by FedEx and UPS at the moment. Do you need it for another carrier? Let us know at support@goshippo.com.

How to Create a Multi-Piece Shipment

To create a multi-piece shipment, simply add a list of Parcel objects to the parcel field in a Shipment. Generating rates and creating transactions works the exact same way as with normal shipments. There is a limit of 10 Parcels per Shipment.

The Shipment request content should look like:

curl https://api.goshippo.com/shipments/  \
    -H "Authorization: ShippoToken <API_TOKEN>" \
    -H "Content-Type: application/json"  \
    -d '{
            "object_purpose": "PURCHASE",
            "address_from": {
                "object_purpose": "PURCHASE",
                "name": "Mr. Hippo",
                "street1": "215 Clayton St.",
                "city": "San Francisco",
                "state": "CA",
                "zip": "94117",
                "country": "US",
                "phone": "+1 555 341 9393",
                "email": "support@goshippo.com"
            },
            "address_to": {
                "object_purpose": "PURCHASE",
                "name": "Mrs. Hippo",
                "street1": "965 Mission St.",
                "city": "San Francisco",
                "state": "CA",
                "zip": "94105",
                "country": "US",
                "phone": "+1 555 341 9393",
                "email": "support@goshippo.com"
            },
            "parcel": [
                {
                    "length": "5",
                    "width": "5",
                    "height": "5",
                    "distance_unit": "in",
                    "weight": "2",
                    "mass_unit": "lb"
                },
                {
                    "length": "10",
                    "width": "10",
                    "height": "10",
                    "distance_unit": "in",
                    "weight": "2",
                    "mass_unit": "lb"
                }
            ],
            "async": false
        }'
require 'shippo'

Shippo::api_token = '<API_TOKEN>'

address_from = {
    :object_purpose => 'PURCHASE',
    :name => 'Shawn Ippotle',
    :street1 => '215 Clayton St.',
    :city => 'San Francisco',
    :state => 'CA',
    :zip => '94117',
    :country => 'US',
    :phone => '+1 555 341 9393',
    :email => 'shippotle@goshippo.com' 
}

address_to = {
    :object_purpose => 'PURCHASE',
    :name => 'Mr Hippo',
    :street1 => 'Broadway 1',
    :city => 'New York',
    :state => 'NY',
    :zip => '10007',
    :country => 'US',
    :phone => '+1 555 341 9393',
    :email => 'mrhippo@goshippo.com'
}

parcel_1 = {
    :length => 5,
    :width => 1,
    :height => 5.555,
    :distance_unit => :in,
    :weight => 2,
    :mass_unit => :lb
}

parcel_2 = {
    :length => 10,
    :width => 10,
    :height => 10,
    :distance_unit => :in,
    :weight => 2,
    :mass_unit => :lb
}

shipment = Shippo::Shipment.create(
    :object_purpose => 'PURCHASE',
    :address_from => address_from,
    :address_to => address_to,
    :parcel => [parcel_1, parcel_2],
    :async => false
)
import shippo

shippo.api_key = "<API_TOKEN>"

address_from = {
    "object_purpose": "PURCHASE",
    "name": "Shawn Ippotle",
    "street1": "215 Clayton St.",
    "city": "San Francisco",
    "state": "CA",
    "zip": "94117",
    "country": "US",
    "phone": "+1 555 341 9393",
    "email": "shippotle@goshippo.com"
}

address_to = {
    "object_purpose": "PURCHASE",
    "name": "Mr Hippo",
    "street1": "Broadway 1",
    "city": "New York",
    "state": "NY",
    "zip": "10007",
    "country": "US",
    "phone": "+1 555 341 9393",
    "email": "mrhippo@goshippo.com"
}

parcel_1 = {
    "length": "5",
    "width": "5",
    "height": "5",
    "distance_unit": "in",
    "weight": "2",
    "mass_unit": "lb"
}

parcel_2 = {
    "length": "10",
    "width": "10",
    "height": "10",
    "distance_unit": "in",
    "weight": "2",
    "mass_unit": "lb"
}

shipment = shippo.Shipment.create(
    object_purpose = 'PURCHASE',
    address_from = address_from,
    address_to = address_to,
    parcel = [parcel_1, parcel_2],
    async = False
)
require_once('lib/Shippo.php');
Shippo::setApiKey("<API_TOKEN>");

$fromAddress = array(
    'object_purpose' => 'PURCHASE',
    'name' => 'Shawn Ippotle',
    'street1' => '215 Clayton St.',
    'city' => 'San Francisco',
    'state' => 'CA',
    'zip' => '94117',
    'country' => 'US',
    'phone' => '+1 555 341 9393',
    'email' => 'shippotle@goshippo.com'
);

$toAddress = array(
    'object_purpose' => 'PURCHASE',
    'name' => 'Mr Hippo"',
    'street1' => 'Broadway 1',
    'city' => 'New York',
    'state' => 'NY',
    'zip' => '10007',
    'country' => 'US',
    'phone' => '+1 555 341 9393',
    'email' => 'mrhippo@goshippo.com'
);

$parcel_1 = array(
    'length'=> '5',
    'width'=> '5',
    'height'=> '5',
    'distance_unit'=> 'in',
    'weight'=> '2',
    'mass_unit'=> 'lb',
);

$parcel_2 = array(
    'length'=> '10',
    'width'=> '10',
    'height'=> '10',
    'distance_unit'=> 'in',
    'weight'=> '2',
    'mass_unit'=> 'lb',
);

$shipment = Shippo_Shipment::create(
    array(
        "object_purpose" => "PURCHASE",
        "address_from" => $fromAddress,
        "address_to" => $toAddress,
        "parcel" => array($parcel_1, $parcel_2),
        "async" => false
    )
);
var shippo = require('shippo')('<API_TOKEN>');

var addressFrom  = {
    "object_purpose": "PURCHASE",
    "name": "Shawn Ippotle",
    "street1": "215 Clayton St.",
    "city": "San Francisco",
    "state": "CA",
    "zip": "94117",
    "country": "US",
    "phone": "+1 555 341 9393",
    "email": "shippotle@goshippo.com"
};

var addressTo = {
    "object_purpose": "PURCHASE",
    "name": "Mr Hippo",
    "street1": "Broadway 1",
    "city": "New York",
    "state": "NY",
    "zip": "10007",
    "country": "US",
    "phone": "+1 555 341 9393",
    "email": "mrhippo@goshippo.com"
};

var parcel_1 = {
    "length": "5",
    "width": "5",
    "height": "5",
    "distance_unit": "in",
    "weight": "2",
    "mass_unit": "lb"
};

var parcel_2 = {
    "length": "10",
    "width": "10",
    "height": "10",
    "distance_unit": "in",
    "weight": "2",
    "mass_unit": "lb"
};

shippo.shipment.create({
    "object_purpose": "PURCHASE",
    "address_from": addressFrom,
    "address_to": addressTo,
    "parcel": [parcel_1, parcel_2],
    "async": true
}, function(err, shipment) {
    // asynchronously called
});
APIResource resource = new APIResource("<API_TOKEN>");

Hashtable toAddressTable = new Hashtable();
toAddressTable.Add("object_purpose", "PURCHASE");
toAddressTable.Add("name", "Mr. Hippo");
toAddressTable.Add("company", "Shippo");
toAddressTable.Add("street1", "215 Clayton St.");
toAddressTable.Add("city", "San Francisco");
toAddressTable.Add("state", "CA");
toAddressTable.Add("zip", "94117");
toAddressTable.Add("country", "US");
toAddressTable.Add("phone", "+1 555 341 9393");
toAddressTable.Add("email", "support@goshipppo.com");

// from address
Hashtable fromAddressTable = new Hashtable();
fromAddressTable.Add("object_purpose", "PURCHASE");
fromAddressTable.Add("name", "Ms Hippo");
fromAddressTable.Add("company", "San Diego Zoo");
fromAddressTable.Add("street1", "2920 Zoo Drive");
fromAddressTable.Add("city", "San Diego");
fromAddressTable.Add("state", "CA");
fromAddressTable.Add("zip", "92101");
fromAddressTable.Add("country", "US");
fromAddressTable.Add("email", "hippo@goshipppo.com");
fromAddressTable.Add("phone", "+1 619 231 1515");
fromAddressTable.Add("metadata", "Customer ID 123456");

List parcelsList = new List();
// parcel
Hashtable parcelTable = new Hashtable();
parcelTable.Add("length", "5");
parcelTable.Add("width", "5");
parcelTable.Add("height", "5");
parcelTable.Add("distance_unit", "in");
parcelTable.Add("weight", "2");
parcelTable.Add("mass_unit", "lb");

Hashtable parcelTableTwo = new Hashtable();
parcelTableTwo.Add("length", "1");
parcelTableTwo.Add("width", "1");
parcelTableTwo.Add("height", "1");
parcelTableTwo.Add("distance_unit", "in");
parcelTableTwo.Add("weight", "2");
parcelTableTwo.Add("mass_unit", "lb");

parcelsList.Add(parcelTable);
parcelsList.Add(parcelTableTwo);

// shipment
Hashtable shipmentTable = new Hashtable();
shipmentTable.Add("address_to", toAddressTable);
shipmentTable.Add("address_from", fromAddressTable);
shipmentTable.Add("parcel", parcelsList);
shipmentTable.Add("object_purpose", "PURCHASE");
shipmentTable.Add("async", false);

As usual, the Shipment request will return a list of Rates for you to select from. The Rate amount refers to the cost of the entire Shipment with multiple packages, not per package.

{
    "object_state": "VALID",
    "object_status": "SUCCESS",
    "object_purpose": "PURCHASE",
    "object_created": "2013-12-01T06:24:20.121Z",
    "object_updated": "2013-12-01T06:24:20.121Z",
    "object_id": "5e40ead7cffe4cc1ad45108696162e42",
    "object_owner": "shippotle@goshippo.com",
    "address_from": "4f406a13253945a8bc8deb0f8266b245",
    "address_to": "4c7185d353764d0985a6a7825aed8ffb",
    "address_return": "4f406a13253945a8bc8deb0f8266b245",
    "parcel": ["ec952343dd4843c39b42aca620471fd5", "dbe0260bb2674c709fa45667cf353f27"],
    "submission_date": "2013-12-03T12:00:00.000Z",
    "insurance_amount": "",
    "insurance_currency": "",
    "extra": {},
    "customs_declaration": "",
    "reference_1": "",
    "reference_2": "",
    "rates_url": "https://api.goshippo.com/shipments/5e40ead7cffe4cc1ad45108696162e42/rates/",
    "rates_list": [
        {
            "object_state": "VALID",
            "object_purpose": "PURCHASE",
            "object_created": "2013-12-01T06:24:21.121Z",
            "object_updated": "2013-12-01T06:24:21.121Z",
            "object_id": "545ab0a1a6ea4c9f9adb2512a57d6d8b",
            "object_owner": "shippotle@goshippo.com",
            "shipment": "5e40ead7cffe4cc1ad45108696162e42",
            "attributes": [],
            "amount": "65.80",
            "currency": "USD",
            "amount_local": "65.80",
            "currency_local": "USD",
            "provider": "USPS",
            "provider_image_75": "https://cdn2.goshippo.com/providers/75/USPS.png",
            "provider_image_200": "https://cdn2.goshippo.com/providers/200/USPS.png",
            "servicelevel_name": "Ground",
            "servicelevel_token":"fedex_ground",
            "servicelevel_terms": "",
            "days": 5,
            "arrives_by": null,
            "duration_terms": null,
            "trackable": true,
            "insurance": false,
            "insurance_amount_local": "0.00",
            "insurance_currency_local": null,
            "insurance_amount": "0.00",
            "insurance_currency": null,
            "messages": [],
            "carrier_account": "078870331023437cb917f5187429b093",
            "test": false
        },
        ...
    ],
    "carrier_accounts": [],
    "metadata": "Customer ID 123456",
    "messages": []
}

How to Retrieve Labels for Each Package

You can purchase a multi-piece shipping rate by POSTing the Rate object_id to the Transaction endpoint as usual. A separate transaction will be created for each parcel.

curl https://api.goshippo.com/transactions \
    -H "Authorization: ShippoToken <API_TOKEN>" \
    -d rate="cf6fea899f1848b494d9568e8266e076"
    -d label_file_type="PDF"
    -d async=false
# Get the first rate in the rates results.
# Customize this based on your business logic.
rate = shipment.rates_list.first

# Purchase the desired rate.
transaction = Shippo::Transaction.create( 
  :rate => rate["object_id"], 
  :label_file_type => "PDF", 
  :async => false )

# label_url and tracking_number
if transaction["object_status"] == "SUCCESS"
  puts "Label sucessfully generated:"
  puts "label_url: #{transaction.label_url}" 
  puts "tracking_number: #{transaction.tracking_number}" 
else
  puts "Error generating label:"
  puts transaction.messages
end
# Get the first rate in the rates results.
# Customize this based on your business logic.
rate = shipment.rates_list[0]

# Purchase the desired rate. 
transaction = shippo.Transaction.create( 
    rate=rate.object_id, 
    label_file_type="PDF", 
    async=False )

# Retrieve label url and tracking number or error message
if transaction.object_status == "SUCCESS":
    print transaction.label_url
    print transaction.tracking_number
else:
    print transaction.messages
// Get the first rate in the rates results.
// Customize this based on your business logic.
$rate = $shipment["rates_list"][0];

// Purchase the desired rate.
$transaction = Shippo_Transaction::create( array( 
    'rate' => $rate["object_id"], 
    'label_file_type' => "PDF", 
    'async' => false ) );

// Retrieve label url and tracking number or error message
if ($transaction["object_status"] == "SUCCESS"){
    echo( $transaction["label_url"] );
    echo("\n");
    echo( $transaction["tracking_number"] );
}else {
    echo( $transaction["messages"] );
}
// Get the first rate in the rates results.
// Customize this based on your business logic.
var rate = shipment.rates_list[0];

// Purchase the desired rate.
shippo.transaction.create({
    "rate": rate.object_id,
    "label_file_type": "PDF",
    "async": false
}, function(err, transaction) {
   // asynchronous callback
});
// Get the first rate in the rates results.
// Customize this based on your business logic.
Rate rate = shipment.RatesList[0];

HashMap transactionParameters = new HashMap();
transactionParameters.Add("rate", rate.ObjectId);
transactionParameters.Add("async", false);

// Purchase the desired rate.
Transaction transaction = resource.CreateTransaction(transactionParameters);

if (transaction.getObjectStatus().equals("SUCCESS")){
    Console.WriteLine("Label url: " + transaction.getLabelUrl());
    Console.WriteLine("Tracking number: " + transaction.getTrackingNumber());
} else{
    Console.WriteLine("Error generating label. Messages: " + transaction.getMessages());
}
You can also create multi-piece shipments in one API call. Check out our tutorial for single label call creation for more details.

The POST request returns the master Transaction object in the response. In order to get labels for all the parcels in the Shipment you need to filter for each parcel transaction.

  1. The Transaction response after you’ve purchased the rate provides you with the master tracking_number of the entire Shipment and the label_url for the first parcel of the Shipment.
    As an example, this label has the master tracking number on it incl. information about the status of the entire multi-piece shipment.
  2. To retrieve labels for the rest of the parcels in the Shipment, you need to make another GET request to the Transaction endpoint with the Rate object_id as the query parameter.
    These sample labels have both the master tracking number on it as well as their own parcel-specific tracking number.

Here’s an example request:

curl https://api.goshippo.com/transactions/?rate=cf6fea899f1848b494d9568e8266e076  \
-H "Authorization: ShippoToken <API_TOKEN>"
require 'shippo'

Shippo::api_token = '<API_TOKEN>'

transactions = Shippo::Transaction.all(rate: 'cf6fea899f1848b494d9568e8266e076')
import shippo

shippo.api_key = "<API_TOKEN>"

transactions = shippo.Transaction.all(rate='cf6fea899f1848b494d9568e8266e076')
require_once('lib/Shippo.php');

Shippo::setApiKey("<API_TOKEN>");

$transactions = Shippo_Transaction::all( array('rate' => 'cf6fea899f1848b494d9568e8266e076'));

var shippo = require('shippo')('<API_TOKEN>');

shippo.transaction.list({
    "rate": "cf6fea899f1848b494d9568e8266e076"
}, function(transactions) {
    // asynchronously called
});

The API will respond with a JSON serialized list of the transactions that belong to this Rate. Each of these transactions belongs to exactly one parcel of the multi-piece shipment. Each transaction has a unique tracking_number and label_url field for it's associated parcel.

{
   "count":5,
   "next":null,
   "previous":null,
   "results":[
      {
         "object_state":"VALID",
         "object_status":"SUCCESS",
         "object_created":"2014-07-17T00:43:40.842Z",
         "object_updated":"2014-07-17T00:43:50.531Z",
         "object_id":"70ae8117ee1749e393f249d5b77c45e0",
         "object_owner":"shippotle@goshippo.com",
         "was_test":true,
         "rate":"ee81fab0372e419ab52245c8952ccaeb",
         "tracking_number":"9499907123456123456781",
         "tracking_status":{
            "object_created":"2014-07-17T00:43:50.402Z",
            "object_id":"907d5e6120ed491ea27d4f681a7ccd4d",
            "status":"UNKNOWN",
            "status_details":"",
            "status_date":null
         },
         "tracking_history":[
            {
               "object_created":"2014-07-17T00:43:50.402Z",
               "object_id":"907d5e6120ed491ea27d4f681a7ccd4d",
               "status":"UNKNOWN",
               "status_details":"",
               "status_date":null
            },
         ],
         "tracking_url_provider":"https://tools.usps.com/go/TrackConfirmAction_input?origTrackNum=9499907123456123456781",
         "label_url":"https://shippo-delivery.s3.amazonaws.com/70ae8117ee1749e393f249d5b77c45e0.pdf?Signature=vDw1ltcyGveVR1OQoUDdzC43BY8%3D&Expires=1437093830&AWSAccessKeyId=AKIAJTHP3LLFMYAWALIA",
         "commercial_invoice_url": "",
         "messages":[

         ],
         "customs_note":"",
         "submission_note":"",
         "metadata":""
      },
      {...},
      {...}
   ]
}

Parcel-level Insurance

You can also purchase insurance for each package of the multi-piece shipment. This can be done through the extras attribute when creating your Parcel object within the Shipment request. Here is a sample request for a Shipment with different insurance amounts for each Parcel.

curl https://api.goshippo.com/shipment/  \
-H "Authorization: ShippoToken <API_TOKEN>" \
-H "Content-Type: application/json"  \
-d '{
    "object_purpose": "PURCHASE",
    "address_from": "d799c2679e644279b59fe661ac8fa488",
    "address_to": "42236bcf36214f62bcc6d7f12f02a849",
    "parcel": [
        {
            "length": "5",
            "width": "5",
            "height": "5",
            "distance_unit": "cm",
            "weight": "2",
            "mass_unit": "lb",
            "template": "",
            "metadata": "Box1",
            "extra": {
                "insurance": {
                    "amount": 25.00,
                    "currency": "USD",
                    "provider": "FEDEX"
                    }
            }
        },
        {
            "length": "5",
            "width": "10",
            "height": "15",
            "distance_unit": "cm",
            "weight": "6",
            "mass_unit": "lb",
            "template": "",
            "metadata": "Box2",
            "extra": {
                "insurance": {
                    "amount": 50.00,
                    "currency": "USD",
                    "provider": "FEDEX"
                    }
            }
        },
        {
            "length": "2",
            "width": "8",
            "height": "9",
            "distance_unit": "cm",
            "weight": "5",
            "mass_unit": "lb",
            "template": "",
            "metadata": "Box3",
            "extra": {
                "insurance": {
                    "amount": 45.00,
                    "currency": "USD",
                    "provider": "FEDEX"
                    }
            }
        }
    ],
    "async": false
}

Handling Shipment-level and Parcel-level Insurance

  • If both shipment-level and parcel-level insurance are specified, the parcel-level insurance will take precedence. Parcels without parcel-level insurance will have the shipment-level insurance amount applied to that parcel.
  • If only shipment-level insurance is specified, then the shipment insurance amount will be applied to each parcel - not divide amongst parcels.

Parcel-level Cash on Delivery

FedEx and UPS offer Cash on Delivery (C.O.D.) for individual parcels in a multi-piece shipment. You can specific your C.O.D. preference in the extras attribute of the Parcel object when creating your Shipment.

  C.O.D. is only available for FedEx Ground as FedEx Ground packages can be delivered at different times. FedEx Express shipments can only accept C.O.D. on a shipment-level, which can be added under extras attribute of the Shipment object.