Rule Types & Options
The Paccurate API becomes especially powerful once we start incorporating rules into pack requests. These rules ensure that the packing decision is made not just on weights and dimensions, but operational constraints that occur in distribution centers and pack stations. This can include accommodating for dunnage, ensuring items are packed with a specific orientation, forcing items into their own packaging, and myriad other essential business logic required by your operations. In this section we will be learning about each type of rule the API supports and how to configure them for How To Construct A Rule
In order to function properly when a pack is run, each rule requires at minimum an operation attribute, and depending on the rule, an options object. The combination of the two are then applied to the item that was targeted and any target that was included in the rule definition.
Options
Most operation values require a corresponding options object. These options vary for each operation. They include fields for things like dimension changes, origin, box type defaults, and more. In the sections below, we'll cover the options requirements for each type of operation.
Operation
Put simply, the operation is the type of rule to be applied to the item. It is a string, and must be one of the following:
- internal-space
- alternate-dimensions
- exclude
- exclude-all
- irregular
- lock-orientation
- fragile
- group-pack
We'll cover each one more in-depth below, along with how to configure them with an options object.
Exclude
Exclude is a very straightforward rule. It requires no options, simply an item (or items) it is assigned to, and targets for the item(s) to be excluded from: cartons or other items.
{
"itemSets": [
{
"refId": 0,
"weight": 15,
"dimensions": {
"x": 12,
"y": 12,
"z": 24
},
"quantity": 1,
"name":"Item A"
},
{
"refId": 1,
"weight": 5,
"dimensions": {
"x": 12,
"y": 8,
"z": 12
},
"quantity": 4,
"name":"Item B"
},
{
"refId": 2,
"weight": 25,
"dimensions": {
"x": 6,
"y": 24,
"z": 24
},
"quantity": 4,
"name":"Item C"
}
],
"boxTypes": [
{
"weightMax": 150,
"name": "3-ft Raft",
"dimensions": {
"x": 36,
"y": 36,
"z": 36
}
}
],
"rules": [
{
"itemRefId": 0,
"targetItemRefIds": [
1
],
"operation": "exclude"
}
]
}
In the example above, Item A (refId 0) has an exclude rule with a targetItemRefIds value of [1] -- this means it cannot be packed with Item B. It can, however, pack with Item C (refId 2). As you can see here, it does just that.
Exclude is one of Paccurate's most frequently used rules and is often used to handle branded boxes, dangerous items, or optional SIOC.
import requests
url = "https://api.paccurate.io/"
packData = {
"itemSets": [
{
"refId": 0,
"weight": 15,
"dimensions": {
"x": 12,
"y": 12,
"z": 24
},
"quantity": 1,
"name":"Item A"
},
{
"refId": 1,
"weight": 5,
"dimensions": {
"x": 12,
"y": 8,
"z": 12
},
"quantity": 4,
"name":"Item B"
},
{
"refId": 2,
"weight": 25,
"dimensions": {
"x": 6,
"y": 24,
"z": 24
},
"quantity": 4,
"name":"Item C"
}
],
"boxTypes": [
{
"weightMax": 150,
"name": "3-ft Raft",
"dimensions": {
"x": 36,
"y": 36,
"z": 36
}
}
],
"rules": [
{
"itemRefId": 0,
"targetItemRefIds": [
1
],
"operation": "exclude"
}
]
}
headers = {
"accept": "application/json",
"content-type": "application/json"
}
response = requests.post(url, json=packData, headers=headers)
print(response.text)Exclude All
If you have an item that can never pack with any other items, an easy way of excluding it from everything else is by applying an "exclude-all" rule. It behaves as described -- exclude this item from being packed with all other items.
{
"itemSets": [
{
"refId": 0,
"weight": 15,
"dimensions": {
"x": 12,
"y": 12,
"z": 24
},
"quantity": 1,
"name":"Item A"
},
{
"refId": 1,
"weight": 5,
"dimensions": {
"x": 12,
"y": 8,
"z": 12
},
"quantity": 4,
"name":"Item B"
},
{
"refId": 2,
"weight": 25,
"dimensions": {
"x": 6,
"y": 24,
"z": 24
},
"quantity": 4,
"name":"Item C"
}
],
"boxTypes": [
{
"weightMax": 150,
"name": "3-ft Raft",
"dimensions": {
"x": 36,
"y": 36,
"z": 36
}
}
],
"rules": [
{
"itemRefId": 0,
"operation": "exclude-all"
}
]
}
Applying the exclude-all rule to the same Item, you can see now instead of packing with Item C, it packs alone:
const fetch = require('node-fetch');
const body = {
"itemSets": [
{
"refId": 0,
"weight": 15,
"dimensions": {
"x": 12,
"y": 12,
"z": 24
},
"quantity": 1,
"name":"Item A"
},
{
"refId": 1,
"weight": 5,
"dimensions": {
"x": 12,
"y": 8,
"z": 12
},
"quantity": 4,
"name":"Item B"
},
{
"refId": 2,
"weight": 25,
"dimensions": {
"x": 6,
"y": 24,
"z": 24
},
"quantity": 4,
"name":"Item C"
}
],
"boxTypes": [
{
"weightMax": 150,
"name": "3-ft Raft",
"dimensions": {
"x": 36,
"y": 36,
"z": 36
}
}
],
"rules": [
{
"itemRefId": 0,
"operation": "exclude-all"
}
]
}
const url = 'https://api.paccurate.io/';
const options = {
method: 'POST',
body: JSON.stringify(body),
headers: {accept: 'application/json', 'content-type': 'application/json'}
};
fetch(url, options)
.then(res => res.json())
.then(json => console.log(json))
.catch(err => console.error('error:' + err));Pack As-Is (SIOC)
When an item is already packed in a shippable container, we can assign it a "pack as is" rule to ensure it is not packed with any other items and is exclusively packed in a "container" of its own dimensions. This differs from "exclude-all" because it doesn't place the item in an available carton. Like the exclude rules, "pack-as-is" does not require any options.
{
"itemSets": [
{
"refId": 0,
"weight": 15,
"dimensions": {
"x": 12,
"y": 12,
"z": 24
},
"quantity": 1,
"name":"Item A"
},
{
"refId": 1,
"weight": 5,
"dimensions": {
"x": 12,
"y": 8,
"z": 12
},
"quantity": 4,
"name":"Item B"
},
{
"refId": 2,
"weight": 25,
"dimensions": {
"x": 6,
"y": 24,
"z": 24
},
"quantity": 4,
"name":"Item C"
}
],
"boxTypes": [
{
"weightMax": 150,
"name": "3-ft Raft",
"dimensions": {
"x": 36,
"y": 36,
"z": 36
}
}
],
"rules": [
{
"itemRefId": 0,
"operation": "pack-as-is"
}
]
}
Viewing this request in our inspector tool, you can see that Item A is not packed in the carton provided in the boxTypes array, but in a custom box that shares the item's dimensions.
🚧 Important
Using this rule does not allow the item to be combined with other items. If you have items that can be shipped as-is but can also be combined with other SKUs and overboxed, it requires a combination of other rules. The pack as is item price is also set to 0.
Pack-as-is is most often used for large or heavy items -- things like litter or electronics that are already overboxed and tightly packed. For more advanced pack-as-is behavior, take a look at our use case article around Ship In Own Container.
import requests
url = "https://api.paccurate.io/"
packData = {
"itemSets": [
{
"refId": 0,
"weight": 15,
"dimensions": {
"x": 12,
"y": 12,
"z": 24
},
"quantity": 1,
"name": "Item A"
},
{
"refId": 1,
"weight": 5,
"dimensions": {
"x": 12,
"y": 8,
"z": 12
},
"quantity": 4,
"name": "Item B"
},
{
"refId": 2,
"weight": 25,
"dimensions": {
"x": 6,
"y": 24,
"z": 24
},
"quantity": 4,
"name": "Item C"
}
],
"boxTypes": [
{
"weightMax": 150,
"name": "3-ft Raft",
"dimensions": {
"x": 36,
"y": 36,
"z": 36
}
}
],
"rules": [
{
"itemRefId": 0,
"operation": "pack-as-is"
}
]
}
headers = {
"accept": "application/json",
"content-type": "application/json"
}
response = requests.post(url, json=packData, headers=headers)
print(response.text)
Internal Space
If an item has space inside of it that can be used to contain other items in an order, we can define that space using the internal-space rule. At a high level, we are creating a new boxType that is attached to the item, and adding some additional properties such as where the subspace is in relation to the item and how to represent the items packed within it.
{
"rules":[
{
"operation": "internal-space",
"itemRefId": 0,
"options": {
"type": "subspace",
"boxType": {
"weightMax": 2,
"name": "Item A interior",
"price": 0,
"dimensions": {
"x": 1.5,
"y": 1.5,
"z": 1.5
}
},
"origin": {
"x": 0.25,
"y": 1,
"z": 1
},
"display": true,
"displayChildren": true
}
}
]
}
Let's break down the rule above. First, the operation is set to "internal-space", which tells the API we're creating some space on an item ("itemRefId":0). With the operation defined and item selected, we create an options object. The options object has 2 required properties: type, boxType and 3 optional properties:origin, display, and displayChildren.
- type: String, must be set to "subspace" -- this tells the API that the box being created in the boxType property is a subspace of the parent box in which the item will be packed
- boxType: BoxType, these are the same properties as any type of box defined in a pack request. It requires a dimensions object containing the length, width, and height (x,y, and z properties), and weightMax integer value. In order to do any sort of targeting for exclusion, we'd want to add refId as well. A property called name can be added as well, helping better clarify the box type when it is returned by the API pack response.
- origin: Point, with x, y and z coordinates. This property defines where the subspace is positioned relative to the item's origin.
- display: Boolean. When set to true, the subspace is displayed as dotted lines in the response image within the item in the parent carton
- displayChildren: Boolean. When set to true, the items packed inside the subspace will be displayed inside the parent carton. Otherwise, they will only be displayed in the returned subspace box image as part of the pack response.
📘 Note
Adding an internal-space rule will return a box in the response for each item that has a space attached to it, regardless of if the internal space has items packed inside. The box in the response will have a subspace property.
Alternate Dimensions
Sometimes, an item's dimensions provided from a WMS or ERP may not be wholly accurate: that is, the item can shrink, fold, or expand when it is placed into a carton to be shipped. To accommodate the real-life packing behavior, we can use the Alternate Dimensions rule. Alternate dimensions can work two ways:
- Providing new dimensions for the Packer to try if the original dimensions do not fit
- Providing new dimensions to replace the original dimensions when packing the item.
The options available to this rule are:
- dimensions: a list of additional dimensions that will be attempted when packing the item. These dimensions are sorted ascending by volume
- dimensionChanges: a list of dimension change vectors that are applied to the item dimensions that will be used when packing is attempted. These are fixed numbers (think: "1.5 inches")
- dimensionScaleChanges: a list of dimension scale-change vectors that are multiplied with the item's original dimensions to generate new packing dimensions. These are percentages (think: 30% shorter, 10% taller)
- replaceOriginal: when set to true, the original item dimensions are ignored and only the dimensions generated by this rule are considered.
Combined with concise item targeting, this rule can be quite powerful. Here is an example rule where T-Shirt items are "folded" virtually before being placed in their carton.
{
"itemSets": [
{
"refId": 0,
"weight": 0.5,
"dimensions": {
"x": 0.5,
"y": 12,
"z": 18
},
"quantity": 2,
"name": "Fold This t-shirt"
}
],
"boxTypes": [
{
"weightMax": 40,
"name": "T-Shirt Box",
"dimensions": {
"x": 4,
"y": 11,
"z": 16
}
}
],
"rules": [
{
"operation": "alternate-dimensions",
"itemMatch": {
"property": "name",
"expression": "t-shirt"
},
"options": {
"replaceOriginal": true,
"dimensionScaleChanges": [
{
"x": 0.5,
"z": -0.66
}
]
}
}
]
}
As you can see above, the dimensions of the shirt provided to the API include a length of 18, which is longer than our available container (at 16). By adding an alternate dimensions rule, and setting replaceOriginal to true, we are able to reduce the length of the item by 2/3 (66% or -0.66), make the height a little taller to accommodate that folded material, and place the shirts into the box:
As shown in the response visualization -- the height is now .75 (.5 x 1.5), the length is now 6.12 (18 x .66), and the width remains the same at 12. Other use cases for this rule would be applying dunnage on any or all axes, or compact-able items whose native dimensions can shrink when placed in a carton.
Irregular
Irregular is a rule specifically for items that are on spools or rolls. A rolled irregular item is simply where the item dimensions X, Y, and Z, represent the thickness, width, and total length of a material meant to be rolled up (possibly on a spool, dowel, etc.) and shipped as such. Marked this way, the bounding box of the roll cylinder will be used as its shipping dimensions. E.g., a roll has a width 60 inches and a diameter of 12 inches, it will be shipped as if it were a 12" x 12" x 60" item. Rolls can have a maximum allowable diameter, and any rolls that must be split will have their weights calculated on a pro-rated basis of the total length and weight in the item contents.
The options for the Irregular rule are:
- type must be set to "roll"
- innerDiameter Is a number that represents the diameter of the dowel or tube that sets the initial diameter of the roll
- spoolWeight is a number representing the weight of the spool, if applicable
- maxDiameter is a number representing the maximum acceptable diameter of the generated roll
- maxWeight is a number representing the maximum acceptable weight of the generated roll
- minRollLength is the minimum length that causes the rolling behavior. For example, if less than 12", ship the item flat without rolling.
Here is an example config with pair of rollable items
{
"itemSets": [
{
"refId": 1,
"quantity": 1,
"dimensions": {
"x": 5,
"y": 11.25,
"z": 22
},
"weight": 1,
"name": "Rollable Item 1",
"color": "#5e2144",
"sequence": "rollable"
},
{
"refId": 2,
"quantity": 1,
"dimensions": {
"x": 9.25,
"y": 5,
"z": 10
},
"weight": 1,
"name": "Rollable Item 2",
"color": "#523131",
"sequence": "rollable"
}
],
"boxTypes": [
{
"weightMax": 40,
"name": "T-Shirt Box",
"dimensions": {
"x": 18,
"y": 18,
"z": 18
}
}
],
"rules": [
{
"operation": "irregular",
"itemRefId": 1,
"options": {
"type": "roll",
"innerDiameter": 0.5,
"spoolWeight": 0.25,
"maxDiameter": 6.75,
"maxWeight": 10.5,
"minRollLength": 2
}
},
{
"operation": "irregular",
"itemRefId": 2,
"options": {
"type": "roll",
"innerDiameter": 1.5,
"spoolWeight": 0.25,
"maxDiameter": 6.75,
"maxWeight": 10.5,
"minRollLength": 2
}
}
]
}
Executed, the API packs it like this:
📘 Note in the pack diagram
The items are "spooled" where applicable, and remaining "length" is rendered as flat.
Lock Orientation
The lock-orientation rule is used to force a specific orientation of an item when the item is placed in a carton. For example, a bottle of liquid should not be packed on its side, or any type of item whose movement is restricted to one or two axes. This prevents the API from trying to place the item at an orientation that is not permitted in practice.
The options object for lock-orientation has just one value:
- freeAxes: An array of 1-2 axis indices that denote which axes can be rotated. Ex: [0,1] would mean the item can be rotated on its x and y (pitch and yaw) axes.
{
"boxTypeChoiceGoal": "lowest-cost",
"itemSets": [
{
"refId": 0,
"quantity": 1,
"dimensions": {
"x": 12,
"y": 5,
"z": 3
},
"weight": 1,
"name": "Ship Upright",
"color": "#1fd12b",
"sequence": "tall"
},
{
"refId": 2,
"quantity": 1,
"dimensions": {
"x": 9.25,
"y": 5,
"z": 10
},
"weight": 1,
"name": "Item 2",
"color": "#2e00e6",
"sequence": ""
}
],
"boxTypes": [
{
"weightMax": 40,
"name": "tall box",
"dimensions": {
"x": 18,
"y": 18,
"z": 18
}
},
{
"weightMax": 40,
"name": "short box",
"dimensions": {
"x": 6,
"y": 16,
"z": 10
}
}
],
"rules": [
{
"operation": "lock-orientation",
"itemRefId": 0,
"options": {
"freeAxes": [
0
]
}
}
]
}
Given the config above, the "Ship Upright" item (targeted using "itemRefId":0) has only one free axis -- its vertical axis (X), noted here as 0. With the rule applied, the order is packed as such:
As you can see, the green upright item is in fact shipped upright. Now, adding an additional axis to rotate on ("freeAxes":[0,2]), the item can ship laying down, and our shipment "cost" is 1920 instead of 5832 in the previous configuration.
Locking an item's orientation can have significant impacts on overall packing performance, as it will restrict the types of containers an item can be packed into. If your item master contains a lot of items with orientation constraints, it may be beneficial to audit your available cartons to make sure they are optimized for those constraints.
import requests
url = "https://api.paccurate.io/"
packData = {
"boxTypeChoiceGoal": "lowest-cost",
"itemSets": [
{
"refId": 0,
"quantity": 1,
"dimensions": {
"x": 12,
"y": 5,
"z": 3
},
"weight": 1,
"name": "Ship Upright",
"color": "#1fd12b",
"sequence": "tall"
},
{
"refId": 2,
"quantity": 1,
"dimensions": {
"x": 9.25,
"y": 5,
"z": 10
},
"weight": 1,
"name": "Item 2",
"color": "#2e00e6",
"sequence": ""
}
],
"boxTypes": [
{
"weightMax": 40,
"name": "tall box",
"dimensions": {
"x": 18,
"y": 18,
"z": 18
}
},
{
"weightMax": 40,
"name": "short box",
"dimensions": {
"x": 6,
"y": 16,
"z": 10
}
}
],
"rules": [
{
"operation": "lock-orientation",
"itemRefId": 0,
"options": {
"freeAxes": [
0
]
}
}
]
}
headers = {
"accept": "application/json",
"content-type": "application/json"
}
response = requests.post(url, json=packData, headers=headers)
print(response.text)Fragile
Paccurate provides a fragile rule that ensures fragile items are not packed in a manner that may harm them in shipping. This could mean ensuring the fragile item is only placed on top of other items, defining a maximum weight for an item that can be placed on top of the fragile item, or even excluding it from being packed with heavier items.
The options object for fragile supports 5 values:
- display: a boolean value determining whether or not to display the reserved subspace above the fragile item in the returned pack image. Useful for analysis purposes.
- maxWeight: a number value defining the maximum weight of items that can be placed above the fragile item.
- onTopOnly: a boolean value that instructs Paccurate whether or not additional items can be placed above the fragile item this rule is applied to. This will override the maxWeight parameter if it is being passed as an option.
- excludeOverweight: a boolean value that automatically excludes the fragile item from being packed with items that exceed the weight defined in maxWeight and have a different refId.
- unrestrictedPlacement: a boolean value that bypasses the creation of reserved subspaces above each item, as well was overrides onTopOnly if previously specified.
In the example configuration below, we are looking for items with the string "frag" in the sequence to assign the fragile rule. This rule sets a max weight of 3, and excludes the fragile item from being packed with items over 3. Setting "unrestrictedPlacement" to true enables multiple fragile items to be placed on top of each other.
"rules": [
{
"itemMatch": {
"property": "sequence",
"expression": "frag"
},
"operation": "fragile",
"options": {
"display": false,
"onTopOnly": false,
"maxWeight": 3,
"excludeOverweight": true,
"unrestrictedPlacement": true
}
}
]
Here is a diagram of the items being packed into two boxes, despite there being room for all of the items to be packed together in one large box:
When considering additional packing materials as part of fragility packing, you will want to use an alternate-dimensions rule to accommodate for the change in packed dimensions for the item.
import requests
url = "https://api.paccurate.io/"
packData = {
"boxTypeChoiceGoal": "most-items",
"itemSets": [
{
"refId": 1,
"quantity": 8,
"dimensions": {
"x": 3,
"y": 5,
"z": 4
},
"weight": 1,
"name": "Fragile Item",
"color": "#45092b",
"sequence": "frag"
},
{
"refId": 2,
"quantity": 2,
"dimensions": {
"x": 15.75,
"y": 14.5,
"z": 5
},
"weight": 10,
"name": "Heavy Item",
"color": "#496d35"
},
{
"refId": 3,
"quantity": 4,
"dimensions": {
"x": 5,
"y": 4,
"z": 7
},
"weight": 4.5,
"name": "Normal item",
"color": "#634022"
}
],
"boxTypes": [
{
"refId": 3,
"weightMax": 150,
"name": "Large Box",
"dimensions": {
"x": 18,
"y": 16,
"z": 22
}
},
{
"refId": 3,
"weightMax": 150,
"name": "Med Box",
"dimensions": {
"x": 11,
"y": 9,
"z": 15
}
}
],
"rules": [
{
"itemMatch": {
"property": "sequence",
"expression": "frag"
},
"operation": "fragile",
"options": {
"display": false,
"onTopOnly": false,
"maxWeight": 3,
"excludeOverweight": true,
"unrestrictedPlacement": true
}
}
]
}
headers = {
"accept": "application/json",
"content-type": "application/json"
}
response = requests.post(url, json=packData, headers=headers)
print(response.text)Group Pack
When packing large quantities of smaller items (think: hundreds of doses of medicine, any item that ships in large unit quantities) it is much more expedient to pack the items as a grouped unit. Our group-pack rule enables this logic. Paccurate uses the smallest available boxType dimension to determine when a group pack rule should be triggered. The options for group-pack are as follows:
- scaleFactorThreshold: a number that is used as a numerator when comparing an item's axis dimension against the smallest boxType's same dimension. For example, if this were set to 10, the item scaling would only apply if the item's dimensions are 1/10th the height of the smallest box height.
- axes: a list of the numerical value of axes for scaling to be applied to.
- axisMultiplier: a number by which each dimension is multiplied to create the "group" size of the pack.
- nameSuffix: a string that is appended to the item name in the pack response when group pack is applied to the item
When an item is small enough to trigger group-pack, Paccurate will use the scaled dimensions of the item to pack the grouped items in a carton. For example, if there was a quantity of 1000 small items as part of the request, the API would convert that 1000 into 10 units of 100, and pack those 10 100-unit instances.
In the following example, we create a group pack rule that doubles the dimensions of all axes if the smallest container is 4x larger than the item's dimensions.
{
"itemSets": [
{
"refId": 1,
"weight": 0.16,
"dimensions": {
"x": 0.5,
"y": 1,
"z": 1
},
"quantity": 2200
}
],
"boxTypes": [
{
"refId": 3,
"weightMax": 800,
"name": "12 Box",
"dimensions": {
"x": 12,
"y": 12,
"z": 12
}
}
],
"rules": [
{
"operation": "group-pack",
"itemMatch": {
"all": true
},
"options": {
"scaleFactorThreshold": 4,
"axes": [
0,
1,
2
],
"axisMultiplier": 2,
"type": "auto",
"nameSuffix": "auto-case-100"
}
}
]
}
Paccurate returns a response with all 2200 items packed in one container.
As seen in the above visual, despite there being 2200 items in the request, the response displays 275 of the group-packed item.