1. Shipments
  2. Create Shipments

Shipments

Create Shipments

Creating a shipment follows a two-step process: first generating shipping rates, then selecting and purchasing one of those rates.

Get Rates

POST
`/v1/shipments`

This endpoint requires three essential components:

  • sender information
  • recipient information
  • parcel details.

Additional options can be provided to configure the shipment.

Request Body Structure

  1. Sender & Recipient Information

    • Both sender and recipient require specific identification information. This can be provided in several ways:

    • For convenience, if you have a contact ID, you can use these shortened formats:

      js
              {
        sender: {
          contact_id: "ctct_";
        }
      }
      
            

      OR

      js
              {
        sender: "ctct_";
      }
      
            
    • If you don't have a contact ID, in that case:

      js
              {
        sender: {
          name: string,               // Required
          email: string,              // Required
          phone: string,              // Required
          business: string,           // Optional
          address: string | object,   // Required
        }
      }
      
            

      Address Formats

      Addresses can be specified in three different formats:

      • Single String Format (with optional second line):
      js
              {
        address: "500 7th ave, New York, NY, 10018",
        address_line2: "Floor 10" // Optional
      }
      
            
      • Structured Object Format:
      js
              {
        address: {
          line1: "500 7th ave",
          line2: "Floor 10",
          city: "New York",
          state: "NY",
          postal_code: "10018".
          country_code: "US" // Optional, defaults to US
        }
      }
      
            
      • Address ID Reference:
      js
              {
        address: "addr_11fkZ8wQzscUj9SZaX1By1";
      }
      
            
      • Location ID Reference:
      js
              {
        address: "loc_11fkZ8wQzscUj9SZaX1By1";
      }
      
            
  2. Parcel Information

    Parcels can be specified using either:

    • Custom Dimensions
    js
            {
      parcels: [
        {
          // Option 1: Custom dimensions
          length: number,                       // inches
          width: number,                        // inches
          height: number,                       // inches
          weight: number,                       // lbs
    
          special_handling_tags: string[]       // Optional
        },
      ];
    }
    
          
    • Predefined package types
    js
            {
      parcels: [
        {
          // Option 2: Predefined package
          type: "usps_small_flat_rate_box",
          weight: number,                       // Optional for predefined packages
    
          special_handling_tags: string[]       // Optional
        },
      ];
    }
    
          

    You don't need to provide dimensions if using a predefined package. In fact, you don't strictly need to provide the weight either, but there are situations like with on-demand providers where not defaulting to the predefined package's max weight will result in better rates, so include this value if you have it.

    See all Predefined Packages

    Other optional properties you can pass in parcel object.

    Property Type Default
    external_container_id string null
    container_number string null
    unpacked boolean false
    special_handling_tags Special Handling Tags[] []
    layout_id string null
    tracking_number string null
    label_url string null
    contents Content[] []
    defective boolean false
    missing boolean false
    received boolean false
    manifest_id string null
    fulfillment_id string null
    status ParcelStatus in_storage

    If you want to let us know about specific inventory you are shipping, you can also pass contents array to describe the items that are inside of the parcel. This is done automatically for you if you are creating a shipment via a fulfillment_id, but you can do so manually if you would like. The reasons that you would want to include the inventory items in each parcel are:

    • You are shipping something internationally and need to include customs information.
    • Something you are shipping in this parcel needs additional care due to the item attributes like alcohol, hazardous materials, or pharmaceuticals.
    • You want to include additional information for your records later.
    js
            {
      parcels: [
        {
          external_container_id: "ctnr_abc123xyz",
          container_number: "TCLU89754",
          unpacked: false,
          special_handling_tags: ["lithium", "high_value"],
          layout_id: "lay_abc123xyz",
          tracking_number: "PKGX-5LO98Q3-UR2VMEB",
          label_url: "https://...",
          contents: [
            {
              name: "Dispatch Roasters Tequila",
              attributes: ["confidential"],
            },
            {
              name: "Dispatch Roasters Coffee",
              attributes: ["cold_storage", "requires_equipment"],
            },
          ],
          defective: false,
          missing: false,
          received: true,
          manifest_id: "mfst_abc123xyz",
          fulfillment_id: "ful_abc123xyz",
          status: "in_processing",
        },
      ];
    }
    
          
  3. Optional fields in get shipment rates request body

js
        {
  options: {
    service_levels: [],
    rate_types: [],
    checkout_total: number,
    lead_time_mins: number,
    max_delivery_days: number,
    provider_instructions: string,
    provider_timeout: number,
    providers: [],
    request_provider_pickup: boolean,
    same_day_tip_amount: number,
    verify_addresses: boolean,
    return_other_rates: boolean,
    tracker_chained_status: string,
    tracker_auto_close_shipments_option: string,
    tracker_auto_close_shipments_status: string,
  },
  location_id: "loc_xyz",
  fulfillment_id: "ful_xyz"
}

      

Any omitted options will be defaulted to the values set in your settings.

Option Type Default Description
verify_address boolean false If you want the sender and recipient addresses to be verified.
provider_timeout integer 15000 Maximum response timeout in ms. Partial results returned if some providers don't respond.
checkout_total integer 0 The total value of the user's cart during ecommerce. This can manipulate shipping prices if any discounts have been configured.
request_provider_pickup boolean false Requested on-demand pickup service at premium rate, distinct from standard route collection.
providers string[] null Filter results by specific provider IDs. See providers documentation. Setting to false/null restores defaults.
service_levels string[] null You can limit the rates returned by specifying the service levels you'd like to have. See service levels documentation. Setting to false/null restores defaults.
max_delivery_days integer null Add the maximum days from now when this shipment should be delivered. This is a good way to filter options from multiple providers without specifying specific rate classes.
rate_types string[] [delivery] The types of rates you want returned. Options include: delivery, pickup, and received. By default only delivery rates are shown, but when creating a shipment by passing a fulfillment_id, we'll include pickup rates as well.
lead_time_mins integer 0 This field specifies the lead time in minutes before the shipment is expected to be delivered.
provider_instructions string null This field allows you to provide specific instructions or notes for the shipping provider regarding the shipment.
same_day_tip_amount integer 0 This field specifies the tip amount to be given for same-day delivery services.
return_other_rates boolean true This field indicates whether to return additional shipping rates. When set to true, the API will include all available rates for the shipment.
tracker_chained_status TrackingStatusOutstanding package_at_waypoint Determines how the tracker's overall status should be calculated when multiple shipments are chained together.
tracker_auto_close_shipments_option TrackerCloseShipmentsOption on_provider_updates Controls when previous shipments in a chain should be automatically closed.
tracker_auto_close_shipments_status TrackerCloseShipmentsStatus delivered Specifies which status to apply when automatically closing previous shipments in a chain. When tracker_auto_close_shipments_option is enabled, previous shipments will be marked with this status.

Example Request

js
        const data = {
  options: {
    service_levels: [
      "on_demand",
      "same_day",
      "next_day_early",
      "next_day",
      "next_day_later",
      "two_day_early",
      "two_day",
      "three_day",
      "ground",
    ],
    providers: ["self", "marketplace", "ups", "upssurepost", "usps", "fedex", "doordash"],
    tracker_chained_status: "package_at_waypoint",
    tracker_auto_close_shipments_status: "delivered",
    tracker_auto_close_shipments_option: "on_provider_updates",
    verify_addresses: true,
  },
  parcel: {
    length: 10,
    width: 10,
    height: 10,
    weight: 70,
    external_container_id: "ctnr_abc123xyz",
    container_number: "TCLU89754",
    unpacked: false,
    special_handling_tags: ["lithium", "high_value"],
    layout_id: "lay_abc123xyz",
    tracking_number: "PKGX-5LO98Q3-UR2VMEB",
    label_url: "https://...",
    contents: [
      {
        name: "Dispatch Roasters Tequila",
        attributes: ["confidential"],
      },
      {
        name: "Dispatch Roasters Coffee",
        attributes: ["cold_storage", "requires_equipment"],
      },
    ],
    defective: false,
    missing: false,
    received: true,
    manifest_id: "mfst_abc123xyz",
    fulfillment_id: "ful_abc123xyz",
    status: "in_processing",
  },
  recipient: {
    name: "Kyle Grzybowski",
    email: "kyle.grzybowski@packagex.io",
    phone: "+923066545454",
    address: {
      line1: "5000 S Arizona Mills Cir",
      line2: "Unit # 123",
      city: "Tempe",
      state: "Arizona",
      postal_code: "85282",
      country_code: "US",
    },
  },
  sender: {
    name: "Kyle Grzybowski",
    email: "kyle.grzybowski@packagex.io",
    phone: "+923066545454",
    address: {
      line1: "5000 S Arizona Mills Cir",
      line2: "Unit # 123",
      city: "Tempe",
      state: "Arizona",
      postal_code: "85282",
      country_code: "US",
    },
  },
  location_id: "loc_xyz",
  fulfillment_id: "ful_xyz",
  type: "outbound",
};

const response = await fetch(`https://api.packagex.io/v1/shipments`, {
  method: "POST",
  headers: {
    "PX-API-KEY": process.env.PX_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify(data),
}).then((res) => res.json());

const shipment = response.data;

      
INFO

Applying multiple filter options simultaneously may create conflicting criteria, potentially eliminating all available rates from the results.

In the event that all shipping rates are eliminated by the filtering criteria, the system will default to displaying all available rates, with the exception of those marked as offline_delivery. This safeguard prevents inadvertent exposure of restricted delivery options to customers.

Type

Typically, the shipment is always outbound but you have the option of creating an inbound shipment by specifying whether it is inbound or outbound by adding the following optional property in the request body (defaults to outbound):

  • type "outbound" | "inbound"

Multi-piece shipments

When shipping multiple packages within a single shipment, you may include all parcels in an array. The rates returned will reflect the total cost for all parcels in the shipment.

Tracking number assignment varies by carrier. Some carriers will assign the same tracking number to all parcels in a shipment, while others will provide unique tracking numbers for each parcel. In cases where multiple tracking numbers are generated, the system will:

  1. Assign individual tracking numbers to each parcel's tracking_number property.
  2. Designate one tracking number for the shipment's tracking_number property.

Purchase Shipment

After generating shipping rates, you can purchase a shipment using either of these endpoints:

POST
`/v1/shipments/:shipment/purchase`
POST
`/v1/shipments/:shipment/buy`

This endpoint requires a valid shipment ID and rate ID. The system will process the payment and create tracking information.

Required Parameters

Parameter Type Description
rate_id string The ID of the rate you want to purchase from the previously generated rates

Optional Parameters

Parameter Type Description
payment_method string Payment method ID to use for this purchase. Defaults to organization's default payment method
payment_reference string Custom reference for tracking payments (e.g., department ID, other reference)
metadata object Custom metadata to associate with the shipment
tags string[] Array of tags to associate with the shipment
order_number string Order number associated with this shipment.

In this instance, we'll select the cheapest shipping rate from the response we received in the previous step. To purchase the shipment, we'll need the shipment_id from the response and the rate ID that we want to purchase. Everything else is optional.

Request Example

js
        const body = {
  rate_id: "rate_abc123xyz",
  payment_reference: "ORDER-12345",
  payment_method: "card_xyz123",
  metadata: {
    department: "Sales",
    project: "Q1 Launch",
  },
  tags: ["priority", "fragile"],
};

const response = await fetch(`https://api.packagex.io/v1/shipments/${shipment_id}/purchase`, {
  method: "POST",
  headers: {
    "PX-API-KEY": process.env.PX_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify(body),
}).then((res) => res.json());

const shipment = response.data;

      

Refetch Rates

POST
`/v1/shipments/:shipment/refresh`

After you already fetched rates, you can also fetch new rates. You'll want to do this if:

  • Your rate has expired because the rates_generated_at is more than 15 minutes from now.
  • You want to update your options that you added into the shipment.

All of the options that are passed in for getting rates can also be passed in to refetch rates.