International Shipments

You can easily handle foreign addresses and customs forms directly through the Shippo API when shipping internationally. We've created a guide to make sure your packages clear customs and arrive safely!

1. Create the sender & recipient Address objects and Parcel object

As with every Shipment, you need to first create or retrieve your two Address (sender and recipient) and Parcel objects first.

The only special requirement in this case is that the phone number of the sender and recipient are required. Apart from that there's nothing special about them for international shipments.

2. Create a customs declaration and customs items

You will need to create a customs declaration and specify the items that are inside your international shipment. This is to ensure that the country you import goods into accepts your package at the border. The USPS website has a great Shipping Restrictions page with some general no-goes, as well as a more detailed Index of Countries and Localities.

What information do I need to submit for a customs declaration?

You can find an overview of all available fields in our Customs Declaration reference section. Most carriers require you to specify certify, certify_signer, contents_type, eel_pfc and incoterm. Although not all carriers require it, we also recommend submitting the items field - this significantly reduces the risk of the package being stuck in customs.

There are many optional fields that can be filled out depending on who you're shipping with, so we please do your research ahead of time to make sure that you have all the required fields covered.

Create your customs declaration and items inline

To create a customs declaration, send a POST request with the necessary information to the Customs Declarations endpoint. You can create your Customs Items within the items attribute. Alternatively, you can create customs items individually via the Customs Items API endpoint.

curl \
    -H "Authorization: ShippoToken " \
    -H "Content-Type: application/json"  \
    -d '{          
          "contents_type": "MERCHANDISE",
          "non_delivery_option": "RETURN",
          "certify": true,
          "certify_signer": "Simon Kreuz",
          "incoterm": "DDU",
          "items": [{
                    "description": "T-shirt",
                    "quantity": 20,
                    "net_weight": "5",
                    "mass_unit": "lb",
                    "value_amount": "200",
                    "value_currency": "USD",
                    "tariff_number": "",
                    "origin_country": "US"
customs_item = {
    :description => "T-Shirt",
    :quantity => 20,
    :net_weight => "1",
    :mass_unit => "lb",
    :value_amount => "200",
    :value_currency => "USD",
    :origin_country => "US"

customs_declaration = Shippo::CustomsDeclaration.create(
    :contents_type => "MERCHANDISE",
    :contents_explanation => "T-Shirt purchase",
    :non_delivery_option => "RETURN",
    :certify => true,
    :certify_signer => "Simon Kreuz",
    :items => [customs_item]
customs_item = {

customs_declaration = shippo.CustomsDeclaration.create(
    contents_type= 'MERCHANDISE',
    contents_explanation= 'T-Shirt purchase',
    non_delivery_option= 'RETURN',
    certify= True,
    certify_signer= 'Simon Kreuz',
    items= [customs_item]
$customs_item = array(
    'description'=> 'T-Shirt',
    'quantity'=> '20',
    'net_weight'=> '1',
    'mass_unit'=> 'lb',
    'value_amount'=> '200',
    'value_currency'=> 'USD',
    'origin_country'=> 'US');

$customs_declaration = Shippo_CustomsDeclaration::create(
    'contents_type'=> 'MERCHANDISE',
    'contents_explanation'=> 'T-Shirt purchase',
    'non_delivery_option'=> 'RETURN',
    'certify'=> 'true',
    'certify_signer'=> 'Simon Kreuz',
    'items'=> array($customs_item)
var customsItem = {

    "contents_type": "MERCHANDISE",
    "contents_explanation": "T-Shirt purchase",
    "non_delivery_option": "RETURN",
    "certify": true,
    "certify_signer": "Simon Kreuz",
    "items": [customsItem],
}, function (err, customsDeclaration) {
    // asynchronously called

3. Create the Shipment, get rates and purchase label

The only difference between this Shipment request and a domestic Shipment request is that you must include customs declaration in the Shipment API call. After that, you can retrieve Rates and create labels just like for domestic shipments.

curl \
    -H "Authorization: ShippoToken <PRIVATE_TOKEN>" \
    -d object_purpose="PURCHASE" \
    -d address_from="d799c2679e644279b59fe661ac8fa488" \
    -d address_to="42236bcf36214f62bcc6d7f12f02a849" \
    -d parcel="7df2ecf8b4224763ab7c71fae7ec8274" \
    -d customs_declaration="b741b99f95e841639b54272834bc478c" \
    -d async=false
# Create the shipment object
shipment = Shippo::Shipment.create(
    :object_purpose => 'PURCHASE',
    # you can also use the object_id directly for the following, but this is more convenient
    :address_from => address_from,
    :address_to => address_to,
    :parcel => parcel,
    :customs_declaration => customs_declaration,
    :async => false
# Create shipment object
shipment = shippo.Shipment.create(
    object_purpose = 'PURCHASE',
    address_from = address_from,
    address_to = address_to,
    parcel = parcel,
    customs_declaration = customs_declaration,
    async = False
// Create shipment object
$shipment = Shippo_Shipment::create(
        "object_purpose" => "PURCHASE",
        "address_from" => $fromAddress,
        "address_to" => $toAddress,
        "parcel" => $parcel,
        "customs_declaration" => $customs_declaration -> object_id,
        "async" => False
// Create shipment object
    "object_purpose": "PURCHASE",
    "address_from": addressFrom,
    "address_to": addressTo,
    "parcel": parcel,
    "customs_declaration": customsDeclaration,
    "async": false
}, function (err, shipment) {
    // asynchronously called

You will get a response with a rates_list attribute. The rates_list usually contains multiple rates - use your own business logic to filter out the rate you want to use and grab the corresponding's Rate's object_id.

   "rates_list": [
            "object_state": "VALID",
            "object_purpose": "PURCHASE",
            "object_created": "2014-07-17T00:04:06.263Z",
            "object_updated": "2014-07-17T00:04:06.263Z",
            "object_id": "545ab0a1a6ea4c9f9adb2512a57d6d8b",
            "object_owner": "",
            "shipment": "89436997a794439ab47999701e60392e",
            "attributes": [],
            "amount": "5.50",
            "currency": "USD",
            "amount_local": "5.50",
            "currency_local": "USD",
            "provider": "USPS",
            "provider_image_75": "",
            "provider_image_200": "",
            "servicelevel_name": "Priority Mail",
            "servicelevel_terms": "",
            "days": 2,
            "arrives_by": null,
            "duration_terms": "Delivery in 1 to 3 business days.",
            "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"

POST to the Transaction endpoint with your Rate object_id to purchase your international shipping label.

curl \
    -H "Authorization: ShippoToken <PRIVATE_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}" 
  puts "Error generating label:"
  puts 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( 
    async=False )

# Retrieve label url and tracking number or error message
if transaction.object_status == "SUCCESS":
    print transaction.label_url
    print transaction.tracking_number
    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( $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.
    "rate": rate.object_id,
    "label_file_type": "PDF",
    "async": false
}, function(err, transaction) {
   // asynchronous callback

You will recieve a JSON serialized Transaction object with your label, commercial invoice, and tracking information.

      "object_state": "VALID",
      "object_status": "SUCCESS",
      "object_created": "2016-08-05T02:08:13.692Z",
      "object_updated": "2016-08-05T02:08:16.256Z",
      "object_id": "b33bfe70940f40aca2c98259d7612097",
      "object_owner": "",
      "was_test": false,
      "rate": "545ab0a1a6ea4c9f9adb2512a57d6d8b",
      "pickup_date": null,
      "notification_email_from": false,
      "notification_email_to": false,
      "notification_email_other": "",
      "tracking_number": "4326518523",
      "tracking_status": null,
      "tracking_history": [],
      "tracking_url_provider": "",
      "label_url": "",
      "commercial_invoice_url": "",
      "messages": [],
      "customs_note": "",
      "submission_note": "",
      "order": null,
      "metadata": "",
      "parcel": "888febaefa6e4f4e8b0576c89b43ca43"

What about Commercial Invoices?

Most international shipments require you to add 3 commercial invoices in the package's "pouch", a special envelope attached on the package. Shippo automatically creates these 3 copies for you, which will be returned in the Transaction's commercial_invoice field.