1. Inventory
  2. Fulfillments

Inventory

Fulfillments

Fulfillments are a multi-step process and this guide will go through all steps in detail. As a quick overview, the following are the steps required:

Statuses & Events

Statuses

Status Description
created When a new fulfillment is created.
processing When a fulfillment is being worked on.
picked The items were successfully picked from inventory. (This step is optional)
packed The fulfillment has had all of the required inventory packed into parcels.
completed The fulfillment is done and has a shipping label. Event fulfillments that are just for pickup will still have a shipping label.
canceled A created fulfillment will not longer be processed.

Events

There is no event for deleting fulfillments since they instead get canceled, but their record will still remain.

Event Description
fulfillment.created When a new fulfillment is created.
fulfillment.updated When a fulfillment is updated in some way.

Overview

The following is a typical lifecycle for a fulfillment.

  1. Create fulfillment

    Add the required inventory that needs to be shipped to the desired recipient from your specified location.

  2. Pick Items (optional)

    If your workflow does a pick and pack operation, you'll increment the `manifested_qty` field for each item.

  3. Create parcel

    Add the box or boxes into which you're going to pack the inventory items.

  4. Pack parcel

    Pack the items into the parcel(s) that you have added.

  5. Generate shipping rates

    Pass the fulfillment ID into the shipments API to get shipping rates for the parcel(s) you are shipping.

  6. Get shipping label

    Purchase a shipping label to complete the fulfillment. You can also set up private rates which do not require you to purchase a label if you are handling the shipping by yourself or sourcing a label off of the platform.


  7. 1. Create fulfillment


    POST
    `/v1/fulfillments`

    To create a fulfillment, you'll need to include the recipient, your location_id from where the fulfillment will be taking place, and the inventory or articles that you want to include. Some optional extra params are in the code example below as well.

    There are two ways to go about working with inventory: either by using the inventory that is stored on Dispatch, or by bypassing it entirely if you are using a third party inventory solution. Both options are documented below.


    Example

    js
            const data = {
      location_id: "loc_afnHMjVUn3gnrvxU5zMvkX",
      recipient: {
        name: "Jamie Jones",
        email: "jamie@packagex.xyz",
        phone: "4844836699",
        address: "500 7th Ave, New York, NY 10018",
      },
      articles: [
        //If the inventory item is all set up, just the ID and quantity you want to send is required
        {
          id: "item_5og9NZ92SYzHKFTPYSEnVK",
          verified_qty: 25,
        },
        //If the item was created but does not have the packaged_* dimensions, they can be entered inline
        {
          id: "item_1nCNpqtyepe48BMNhzmW54",
          verified_qty: 50,
          packaged_length: 2,
          packaged_width: 3,
          packaged_height: 4,
          packaged_weight: 6,
        },
        //If an item is not in our inventory system, it can be added without providing an ID. Ideally a SKU or GTIN is added for barcode scanning by the users.
        //A special, temporary ID will be generated for it with the prefix temp_ to make working with the data easier
        {
          name: "Custom Item",
          verified_qty: 5,
          packaged_length: 2,
          packaged_width: 3,
          packaged_height: 4,
          packaged_weight: 6,
          sku: "237489237489",
          image_url: "https://i.imgur.com/FKmX7dt.gif", //You can include an image as a nice-to-have for the packing slip
        },
      ],
    };
    
    const response = await fetch("https://api.packagex.io/v1/fulfillments", {
      method: "POST",
      headers: {
        "PX-API-KEY": process.env.PX_API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(data),
    }).then((res) => res.json());
    
    const fulfillment = response.data;
    
          


    2. Pick Operation (optional)


    POST
    `/v1/fulfillments/:fulfillment`

    If your workflow is a typical "pick and pack" operation, you may want to first pick all of your items to ensure that you have them in inventory and them pack them into one or more parcels.

    To do the pick operation, you can increment the manifested_qty field. PackageX will automatically make sure that you do not go over the amount that was requested in the fulfillment.

    The status of the fulfillment will automatically get updated to picked when all of the items have been accounted for.


    Example

    js
            const data = {
      articles: [
        {
          id: "item_5og9NZ92SYzHKFTPYSEnVK",
          manifested_qty: 1, //Increment by 1
        },
        {
          id: "item_9aHfJcifTHoWvNLigrK9EJ",
          manifested_qty: -5, //Decrement by 10
        },
        {
          id: "item_1nCNpqtyepe48BMNhzmW54",
          manifested_qty: [2], //Reset the manifested_qty to 2
        },
      ],
    };
    
    const response = await fetch(`https://api.packagex.io/v1/fulfillments/${fulfillment.id}`, {
      method: "POST",
      headers: {
        "PX-API-KEY": process.env.PX_API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(data),
    }).then((res) => res.json());
    
    const fulfillment = response.data;
    
          


    3. Create parcel


    POST
    `/v1/fulfillments/:fulfillment/parcels`

    Once the fulfillment has been created, it needs to be packed. But before that, one or more parcels need to be created. You can create a custom parcel by adding dimensions or by using a predefined package that you set up on the dashboard.

    While predefined packages have a maximum weight which is checked by the API, manually created packages do not have a weight check.

    Create custom parcel

    js
            //All dimensions are in inches. This will create a 10x10x10 box
    const data = {
      length: 10,
      width: 10,
      height: 10,
    };
    
    const response = await fetch(`https://api.packagex.io/v1/fulfillments/${fulfillment.id}/parcels`, {
      method: "POST",
      headers: {
        "PX-API-KEY": process.env.PX_API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(data),
    }).then((res) => res.json());
    
    //We'll still respond with the fulfillment object here with your new parcel nested inside of it
    const fulfillment = response.data;
    
          

    Use predefined parcel

    js
            // fetch your org profile
    // the `/org` is an alias for `/organization/:org`
    // you have most likely already fetched your org profile at some point so you might already have it handy
    const response = await fetch(`https://api.packagex.io/v1/org`, {
      method: "GET",
      headers: {
        "PX-API-KEY": process.env.PX_API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(data),
    }).then(res.json());
    
    // breaking down things just to show details
    // all app settings are found on the org profile, here we find the fulfillment settings
    const fulfillment_settings = response.data.settings.fulfillments;
    
    // get the predefined packages
    const predefined_packages = fulfillment_settings.predefined_packages;
    
    // select the predefined package that you want according to your logic.
    const selected_package = predefined_packages[0];
    
    // pass the predefined package type as the type field. If this package is custom to you, it will status with pdp_
    // if you have used a carrier specific predefined package, this value will be that of the carrier's predefined package.
    const data = {
      type: selected_package.type, //pdp_qbpXmaGUYroknGjMaHsFcJ or usps_flat_fate_envelope
    };
    
    await fetch(`https://api.packagex.io/v1/fulfillments/${fulfillment.id}/parcels`, {
      method: "POST",
      headers: {
        "PX-API-KEY": process.env.PX_API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(data),
    });
    
          

    4. Pack parcel


    POST
    `/v1/fulfillments/:fulfillment/parcels/:parcel`

    Once the parcel for the fulfillment has been created, it is time to add items into it. You can specify adding items into the parcel the same way as when creating fulfillments or verifying manifests.

    Example

    js
            const data = {
      articles: [
        {
          id: "item_1nCNpqtyepe48BMNhzmW54",
          verified_qty: 2,
        },
        {
          id: "temp_5og9NZ92SYzHKFTPYSEnVK", //If inventory is externally created
          verified_qty: 2,
          damaged_qty: 5,
        },
      ],
    };
    
    const response = await fetch(`https://api.packagex.io/v1/fulfillments/${fulfillment.id}/parcels/${parcel.id}`, {
      method: "POST",
      headers: {
        "PX-API-KEY": process.env.PX_API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(data),
    });
    
    const fulfillment = response.data.data.data.data.data.data;
    
          

    Delete parcel


    DELETE
    `/v1/fulfillments/:fulfillment/parcels/:parcel`

    If you decide to delete the parcel from the fulfillment, any inventory attributed to that parcel will be removed and reset.

    Example

    js
            const response = await fetch(`https://api.packagex.io/v1/fulfillments/${fulfillment.id}/parcels/${parcel.id}`, {
      method: "DELETE",
      headers: {
        "PX-API-KEY": process.env.PX_API_KEY,
        "Content-Type": "application/json",
      },
    });
    
    const fulfillment = response.data;
    
          

    5. Generate shipping rates


    POST
    `/v1/shipments`

    Since we've collected all of the shipping information throughout the process of creating and updating the fulfillment and the parcels within, we are able to generate shipping rates by just passing the fulfillment ID to the shipping API.

    This will create a shipment ID on the fulfillment property, which you can use to retrieve the shipping rates and refresh the rates if the rates_generated_at timestamp is more than 15 minutes old.


    Get rates first time

    js
            const data = {
      fulfillment_id: "ful_hsvAifCQy8F8SJ6jSxxjsv",
    };
    
    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;
    
          

    Refresh rates

    If you need to refresh the shipping rates, make a call to the refresh endpoint with the shipment ID property on your fulfillment, no body needed.

    js
            const response = await fetch(`https://api.packagex.io/v1/shipments/${fulfillment.shipment_id}/refresh`, {
      method: "POST",
      headers: {
        "PX-API-KEY": process.env.PX_API_KEY,
        "Content-Type": "application/json",
      },
    });
    
    const shipment = response.data;
    
          

    6. Generate shipping label


    POST
    `/v1/shipments/:shipment`

    Once you have found the shipping label that suits your needs, you can purchase it by passing the rate ID into the request body. There are additional options availible here, please see the shipments section.

    In the response, you'll have the label_url with the PDF of your shipping label(s). If there are multiple shipping labels, the label_url property will combine all shipping labels into one PDF document for easy printing. Each parcel in the shipment will also have its own PDF document attached to it.

    Example

    js
            const data = {
      rate_id: "rate_rD1hVDRM4qLTezMmq98k1b",
    };
    
    const response = await fetch(`https://api.packagex.io/v1/shipments/${fulfillment.shipment_id}`, {
      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;
    
          


    Cancel Fulfillment / Update Status


    POST
    `/v1/fulfillments/:fulfillment`

    To cancel a fulfillment, update its status to "canceled". If you change your mind, reset its status to "processing".

    The same options are available if you want to force the system to do a partial fulfillment. You can manually update the status of the fulfillment to packed, then generate a shipping label.


    Example

    js
            const data = {
      status: "canceled",
    };
    
    const response = await fetch(`https://api.packagex.io/v1/fulfillments/${fulfillment.id}`, {
      method: "POST",
      headers: {
        "PX-API-KEY": process.env.PX_API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(data),
    });
    
    const fulfillment = response.data;
    
          


    Retrieve Fulfillment


    GET
    `/v1/fulfillments/:fulfillment`

    Get a single fulfillment using its id.

    js
            const response = await fetch("https://api.packagex.io/v1/fulfillment/ful_czhgjrk5JaVvyATPDbyURp", {
      method: "GET",
      headers: {
        "PX-API-KEY": process.env.PX_API_KEY,
        "Content-Type": "application/json",
      },
    }).then((res) => res.json());
    
    const fulfillment = response.data;
    
          

    List Fulfillments


    GET
    `/v1/fulfillments`

    Example

    When you want to retrieve multiple fulfillments, your data property on the result will always be an array even if you don't have any fulfillments.

    js
            const response = await fetch("https://api.packagex.io/v1/fulfillments", {
      method: "GET",
      headers: {
        "PX-API-KEY": process.env.PX_API_KEY,
        "Content-Type": "application/json",
      },
    }).then((res) => res.json());
    
    const fulfillments = response.data; //the array of fulfillments
    const pagination = response.pagination; //the pagination object
    
          

    Pagination

    If the has_more property on the pagination object is set to true, you know there are more fulfillments in the database that have not been returned to you. The pagination object also has a page property indicating your current offset and a limit property. The total_count property in pagination returns the the total number of fulfillments in the database.

    By default the page is set to 1 and the limit is 25.

    If we want to query for items 26 - 50, we would request page 2 with a query parameter.

    js
            const response = await fetch("https://api.packagex.io/v1/fulfillments?page=2&limit=25", {
      method: "GET",
      headers: {
        "PX-API-KEY": process.env.PX_API_KEY,
        "Content-Type": "application/json",
      },
    }).then((res) => res.json());
    
    const fulfillments = response.data; //the array of items 25 - 50
    const pagination = response.pagination; //the pagination object
    
          

    Filter

    You can filter fulfillments by location_id, statuses, recipient_contact_id, updated_by, value_added_services and service_levels.

    • location_id - Add the ID of one of your locations to get all of the deliveries currently mapped to that location. For example: location_id=loc_czhgjrk5JaVvyATPDbyURp
    • statuses - A comma separated list of fulfillment statuses. Keep in mind comma's in URLs are encoded as %2C, so we recommend using your platforms native URL encoding library. For example: statuses=picked,packed
    • recipient_contact_id - This will let you filter all deliveries where the recipient matches the selected contact ID. For example: recipient_contact_id=ctct_rEMzyCyxJzuBzN4LyurAJV
    • updated_by - This will let you filter all deliveries where the updated_by property matches the selected ID. For example: updated_by=key_hnNCb17prVGhjYDy5YicZs
    • value_added_services - You can filter fulfillments that have a particular value added service. For example: value_added_services=gift_wrapping
    • service_levels - Allows you to filter fulfillments that have the specified comma separated service levels. For example: service_levels=in_person_pickup,same_day
    js
            const response = await fetch(
      "https://api.packagex.io/v1/fulfillments?location=loc_hj7gjrk5JaVvyATPDbyURp&statuses=picked%2Cpacked&recipient_contact_id=ctct_rEMzyCyxJzuBzN4LyurAJV",
      {
        method: "GET",
        headers: {
          "PX-API-KEY": process.env.PX_API_KEY,
          "Content-Type": "application/json",
        },
      }
    ).then((res) => res.json());
    
    const fulfillments = response.data;
    const pagination = response.pagination;
    
          

    Sorting

    Sorting describes in what order you want your responses to come in. You can select an available property by which to sort, as well as the direction.

    • order_by - The property by which to sort. Available properties are: created_at, complete_at
    • direction - The direction to sort. Available directions are: asc and desc

    By default, fulfillments will be sorted by in descending order, meaning the most recently created will be first.

    js
            const response = await fetch("https://api.packagex.io/v1/fulfillments?order_by=created_at&direction=desc", {
      method: "GET",
      headers: {
        "PX-API-KEY": process.env.PX_API_KEY,
        "Content-Type": "application/json",
      },
    }).then((res) => res.json());
    
    const fulfillments = response.data;
    const pagination = response.pagination;
    
          

    There are times when filtering is not enough and you want to find a specific fulfillment by some other attribute. In this case, you can do a fuzzy, typo-tolerant search. Below are the properties that are supported by our full text search.

    Searchable Properties

    • order_number
    • recipient_name
    • recipient_phone
    • recipient_email
    • sku
    • gtin

    To search, simply provide a string to search by using the search query param. The results will be order by the most relevant first. If you want to highlight matching search results for a frontend, we provide a special property for search-returned fulfillment objects called _search which will have the matched text surrounded with <mark> handles.

    Ordering Search Results

    By default, search results are ordered by relevance. However, if you include an order_by parameter along with your search query, the results will be ordered by the specified property instead of by relevance.

    Relevance Score

    Relevance scores are included in the search results by default. Note that this could add up to 10ms of extra time to the request.

    js
            const response = await fetch("https://api.packagex.io/v1/fulfillments?search=jamie%jones", {
      method: "GET",
      headers: {
        "PX-API-KEY": process.env.PX_API_KEY,
        "Content-Type": "application/json",
      },
    }).then((res) => res.json());
    
    const fulfillment = response.data[0];