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
Events
There is no event for deleting fulfillments since they instead get canceled, but their record will still remain.
Overview
The following is a typical lifecycle for a fulfillment.
Create fulfillment
Add the required inventory that needs to be shipped to the desired recipient from your specified location.
Create parcel
Add the box or boxes into which you're going to pack the inventory items.
Pack parcel
Pack the items into the parcel(s) that you have added.
Update to `packed`
Mark the fulfillment as packed.
Generate shipping rates
Pass the fulfillment ID into the shipments API to get shipping rates for the parcel(s) you are shipping.
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.
location
status
order_number
recipient_name
recipient_phone
recipient_email
recipient_formatted_address
metadata
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
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
const data = {
location_id: "loc_afnHMjVUn3gnrvxU5zMvkX",
recipient: {
name: "Jamie Jones",
email: "jamie@packagex.xyz",
phone: "4844836699",
address: "500 7th Ave, New York, NY 10018",
},
inventory: [
//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),
});
const fulfillment = response.data;
2. 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
//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),
});
//We'll still respond with the fulfillment object here with your new parcel nested inside of it
const fulfillment = response.data;
Use predefined parcel
// 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
};
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),
});
3. 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
const data = {
inventory: [
{
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
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;
4. Update to 'Packed'
POST `/v1/fulfillments/:fulfillment`
Once the fulfillment is packed to your satisfaction, you can update the status of the fulfillment to 'packed'. Note that it does not need to be fully packed if you have partial fulfillments enabled via the dashboard settings or API.
Of course, you can also update the status of the fulfillment to 'canceled' at any time in the same way.
Example
const data = {
status: "packed",
};
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;
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
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),
});
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.
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
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),
});
const shipment = response.data;
Retrieve Fulfillment
GET `/v1/fulfillments/:fulfillment`
Get a single fulfillment using its id
.
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",
},
});
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.
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.
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.
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 & Sorting
You can filter fulfillments by the following properties:
By default, fulfillments are sorted in descending order by the created date, meaning that the newest fulfillments are returned first.
If you provided a complete_by
property to the fulfillment, you're able to sort the fulfillments by that timestamp instead by appending the following to your query parameters:
https://api.packagex.io/v1/fulfillments?sort=complete_by
const response = await fetch(
"https://api.packagex.io/v1/fulfillments?location=loc_hj7gjrk5JaVvyATPDbyURp&status=compelted&sort=complete_by",
{
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;
Search
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
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.
Relevance Score
If you want to show a relevance score on the search, you're able to pass the include_score
query parameter and set its value to something truthy like "true" or 1.
The reason the score requires a second property is that it could add up to 10ms of extra time to the request.
const response = await fetch("https://api.packagex.io/v1/fulfillments?search=jamie%jones&include_score=1", {
method: "GET",
headers: {
"PX-API-KEY": process.env.PX_API_KEY,
"Content-Type": "application/json",
},
}).then((res) => res.json());
const fulfillment = response.data[0];