Cartonization 1.0.0

Reference

Every request and response field of the API, the error responses and the limits. The components' API is in Embedding the components and in cartonization-components.d.ts.

Conventions#

POST /api/pack#

Packs items into containers with one, several or all algorithms, and returns the best solution and the ranking.

json
{
  "containers": [{ "id": "M", "length": 35, "width": 25, "height": 20, "maxWeight": 20, "quantity": 10, "cost": 0.8 }],
  "items": [{ "id": "SKU1", "length": 12, "width": 9, "height": 10, "weight": 0.4, "quantity": 4, "rotation": "upright", "stackable": true }],
  "options": { "minSupportRatio": 0.7, "timeLimitMs": 2000, "totalTimeLimitMs": 15000, "objective": "fill-rate", "heavyOnBottom": false, "seed": 42 },
  "algorithm": "best",
  "includeAllSolutions": false
}
Field Type Default
containers Container[] required 1 to 100 container types
items Item[] required At least one; at most 2000 units in total (the sum of the quantities)
options Options
algorithm string or string[] "best" "best" (or "all") runs every algorithm and returns the best; one algorithm id, or a list of ids, runs only those. An unknown id is a 400
includeAllSolutions boolean false Adds solutions: every algorithm's full solution. Send true when the page shows the ranking, so its rows can open the other solutions

Container#

A box type, pallet type or bin location.

Field Type Default
id string required See ids
length, width, height number required Inner dimensions, 0.01 to 1e6. For a pallet, height is the virtual stack height
maxWeight number no limit The most the contents may weigh, above 0
tareWeight number 0 The empty container's weight, added to grossWeight only, at least 0
quantity integer unlimited How many of this container exist (box stock is usually unlimited; rack bins are not), at least 1
cost number 0 The judge under objective: "lowest-cost", a tie-breaker otherwise, at least 0
hazmat boolean false A hazmat-certified container. It holds only hazmat items, one hazard class per container
hazmatClasses string[] any class The hazard classes it is certified for (1 to 20, each 1 to 20 characters). Only with hazmat: true
maxOverhang number 0 How far items may extend past each length/width edge, per side (pallets), 0 to 1e6
outerLength, outerWidth, outerHeight number inner size Multi-level only: the size when this container is packed at the next level. At least the inner dimensions
baseHeight number Multi-level only: the deck height of an open container such as a pallet (see multi-level)
rotation, stackable, maxLoad upright, true, no limit Multi-level only: how this container is handled when packed at the next level (as for items)

Item#

Field Type Default
id string required See ids
length, width, height number required 0.01 to 1e6
weight number required Per unit, above 0
quantity integer 1 Identical units, at least 1
rotation "any", "upright", "none" "any" any: the 6 axis-aligned orientations; upright: the height stays vertical (spins around z only); none: as given
stackable boolean true false: nothing may be placed on top of it
maxLoad number no limit Crush limit: the most weight that may rest on it, directly or through the items stacked above it. At least 0
hazmatClass string Hazard class, for example "3", 1 to 20 characters. A hazmat item goes only into a hazmat container certified for its class

Options#

Field Type Default
minSupportRatio number, 0 to 1 0 The fraction of an item's base that must rest on the floor or on items below it. 0 for cartons with void fill; about 0.7 for pallets. An item above the floor always needs some support
timeLimitMs integer, 10 to 10000 2000 The time budget of each metaheuristic (genetic-algorithm, simulated-annealing)
totalTimeLimitMs integer, 100 to 60000 15000 The budget of a whole multi-algorithm run (see Limits)
objective "fill-rate", "fewest-containers", "lowest-cost", "weight-utilization" "fill-rate" What "best" means (see Ranking)
heavyOnBottom boolean false A hard rule: no item may rest on a lighter item
seed integer 42 The seed of the randomized algorithms, for reproducible results

Algorithms#

GET /api/algorithms lists them with their names and descriptions.

id Method
extreme-points Extreme Points, first-fit decreasing (Crainic, Perboli & Tadei 2008)
largest-area-fit-first LAFF: builds layers led by the item with the largest face (Gürbüz et al.)
deepest-bottom-left-fill DBLF over corner points (Karabulut & İnceoğlu)
best-fit-corner-points Best fit by contact area over corner points
empty-maximal-spaces Empty Maximal Spaces, best fit by least leftover volume (Lai & Chan; Parreño et al.)
guillotine-best-fit Guillotine cuts with best short side fit
layer-wall-building Wall/layer building (George & Robinson; Boxologic)
genetic-algorithm Permutation GA (OX1 crossover) over the item order. It tries 7 packers on the seed orders and searches with whichever packs best
simulated-annealing Simulated annealing over the item order. Each order is packed with the two best packers from the seed phase; the energy follows the objective

Every algorithm uses the same container selection: several box types, quantity limits, and right-sizing each container down to the smallest box that still holds its contents.

Ranking and objectives#

The fewest unpacked units always come first: a solution that leaves items behind never wins. Then options.objective decides:

objective Primary criterion Then
fill-rate (default) Highest overall fill rate (packed item volume ÷ volume of the containers used) Fewest containers, lowest cost
fewest-containers Fewest containers Fill rate, cost
lowest-cost Lowest total container cost Fill rate, containers
weight-utilization Highest packed weight ÷ summed maxWeight of the used containers Containers, fill rate, cost

With the default, fill rate comes before the container count, so the winner can occasionally use one more, smaller box than another algorithm. The response reports the objective that was used. Under lowest-cost a container without cost counts as free, so give every container type a cost when you use it.

Constraints#

Response#

json
{ "best": { ... }, "ranking": [ ... ], "objective": "fill-rate", "skipped": [], "solutions": [ ... ] }
Field Type
best Solution The winning solution
ranking RankingEntry[] Every algorithm that ran, best first
objective string The objective used for the ranking
skipped string[] Algorithms that didn't run because totalTimeLimitMs was used up
solutions Solution[] Only with includeAllSolutions: true: every algorithm's full solution, best first
license { "trial": true } Only while the install runs on a trial license; absent with a production license. The components show a TRIAL mark for it (License)

Solution#

Field Type
algorithm string The algorithm id
containers PackedContainer[] The containers used, in order
unpacked { uid, itemId, reason }[] Units left out, with the reason. Not an error: the response is still 200
metrics SolutionMetrics
elapsedMs number How long the algorithm ran

PackedContainer#

Field Type
index integer Its position in the solution, from 1
containerId string The container type's id
dimensions { length, width, height } The container type's inner dimensions
placements Placement[] The units in it
itemVolume number The packed units' volume
containerVolume number The container's volume (the overhang envelope's, when it has maxOverhang)
fillRate number, 0 to 1 itemVolume / containerVolume
netWeight number The contents' weight
grossWeight number netWeight + tareWeight
weightUtilization number or null netWeight / maxWeight; null when the container has no maxWeight
hazmatClass string Only for hazmat containers: the hazard class of the contents

Placement#

Field Type
uid string The unit, <itemId>#<n> (or the item id when its quantity is 1)
itemId string The item's id
x, y, z number The unit's corner nearest the origin (see coordinates)
length, width, height number Its dimensions along x, y and z, in the chosen orientation
weight number

SolutionMetrics#

Field Type
containerCount integer
packedUnits, unpackedUnits integer
totalItemVolume, totalContainerVolume number Packed volume, and the volume of the containers used
fillRate number, 0 to 1 totalItemVolume / totalContainerVolume
totalCost number The sum of the used containers' cost
weightUtilization number or null Packed weight ÷ the summed maxWeight of the used containers that have one; null when none has

RankingEntry#

{ rank, algorithm, metrics, elapsedMs }: rank from 1, metrics as in SolutionMetrics. The ranking has no placements; send includeAllSolutions: true to get every solution.

Unpacked reasons#

Reason
Item does not fit in any container type Too large in every allowed orientation
Item exceeds the max weight of every container it fits in Too heavy
No regular (non-hazmat) container available Only hazmat containers in the request (or none left)
No hazmat container certified for hazard class <c>
No container capacity left (container quantity exhausted) Every container's quantity is used up
No hazmat container capacity left for hazard class <c>

POST /api/capacity#

How many of one item fit in one container, and how many containers N units need. For example, how many cartons fit on a pallet:

json
{ "container": { "id": "EUR", "length": 120, "width": 80, "height": 160, "maxWeight": 800 },
  "item": { "id": "CTN", "length": 40, "width": 30, "height": 30, "weight": 9, "rotation": "upright" },
  "demand": 1000 }
Field Type Default
container Container required Its quantity is ignored
item Item required Its quantity is ignored
demand integer, 1 to 2000 Also packs this many units, and returns how many containers they need
algorithm, options "best" As for /api/pack

The response:

Field Type
unitsPerContainer integer How many fit in one container (40 for the example)
limitedBy "volume", "weight", "geometry", "unit-cap" volume / weight: that theoretical bound was reached; geometry: the shapes leave gaps; unit-cap: the search stopped at 2000 units, so the result is a lower bound
capped boolean The theoretical bound is above 2000 units
bounds { volume, weight } The theoretical bounds; weight is null without maxWeight
containersNeeded integer or null With demand: ceil(demand / unitsPerContainer) (25 for the example)
solution Solution or null One full container
demandSolution Solution or null With demand: the packing of all the units
license { "trial": true } Only on a trial license, as for POST /api/pack

POST /api/pack/multi-level#

Packs through 1 to 3 nested levels in one request, for example items into cartons, then cartons onto pallets:

json
{
  "items": [
    { "id": "MUG", "length": 12, "width": 9, "height": 10, "weight": 0.4, "quantity": 30, "rotation": "upright" },
    { "id": "PAINT", "length": 10, "width": 10, "height": 12, "weight": 1.2, "quantity": 6, "hazmatClass": "3" }
  ],
  "levels": [
    { "name": "Cartons", "algorithm": "best", "options": { "timeLimitMs": 500 },
      "containers": [
        { "id": "BOX-M", "length": 30, "width": 25, "height": 20, "maxWeight": 20, "tareWeight": 0.3, "outerLength": 31, "outerWidth": 26, "outerHeight": 21 },
        { "id": "HZ-BOX", "length": 30, "width": 20, "height": 20, "hazmat": true, "hazmatClasses": ["3"] } ] },
    { "name": "Pallets", "options": { "minSupportRatio": 0.7, "heavyOnBottom": true },
      "containers": [
        { "id": "EURO", "length": 120, "width": 80, "height": 150, "maxWeight": 1000, "tareWeight": 25, "maxOverhang": 2 },
        { "id": "HZ-PALLET", "length": 120, "width": 80, "height": 150, "hazmat": true } ] }
  ]
}

GET /api/algorithms and GET /health#

GET /v1/info#

The version and the license of this install, the same for every caller (it never contains the key, nor why a key was rejected: that is only in the server log). The Status page shows it. Never blocked, also without a license.

json
{"name":"cartonization","version":"1.0.0","license":{"status":"valid","trial":false,"source":"configured","allowed":{"cartonization":true},"products":["cartonization"],"licensee":"Acme Corp","id":"LIC-0007","expires":"2027-10-31","daysLeft":375,"maxInstances":2,"instanceId":"cz-3f9a51c0d2e47b8a16c5e0f1"}}
Field Type
license.status string valid, expired, missing (no key set) or invalid (the key was not accepted)
license.source string configured (LICENSE_KEY / LICENSE_FILE), bundled (the evaluation license of the download) or none
license.trial boolean An evaluation license: responses are marked and the components show a TRIAL mark
license.allowed object { "cartonization": true } when the packing API works now
license.products string[] The products in the license: ["cartonization"]
license.licensee, license.id string Who it is licensed to, and the license id to quote to support
license.expires string YYYY-MM-DD, valid through the end of that day (UTC). Absent for a trial without expiry
license.daysLeft number While valid: the days until it expires, the expiry day included
license.maxInstances number The installations the license covers (part of the license terms, not enforced)
license.instanceId string This installation's id (CARTONIZATION_INSTANCE_ID, or derived from the hostname)

Fields that don't apply are absent.

Errors#

Status When Body What to do
400 Invalid input: schema violations, an unknown algorithm, too many units or container types, malformed JSON { "error": "Invalid request", "issues": [...] }, or { "error": "<message>" } Fix the request; issues[].path points at the field
402 No valid license (POST /api/pack, /api/capacity, /api/pack/multi-level only), checked before the body is read { "error": "<message>", "code": "license_required" | "license_expired" | "product_not_licensed" } Install a valid license key (License); retrying doesn't help
503 The solver is busy (SOLVER_MAX_CONCURRENT requests are already running) or a request ran past SOLVER_TIMEOUT_MS { "error": "<message>" } Retry later with backoff, or lower totalTimeLimitMs
500 Unexpected server error { "error": "Internal server error" } Report it; the details are in the server log

Each entry of issues has path (the field, as a list of keys and indexes, for example ["containers", 0, "length"]), message and code, plus details that depend on the code (for example minimum):

json
{"error":"Invalid request","issues":[{"origin":"number","code":"too_small","minimum":0.01,"inclusive":true,"path":["containers",0,"length"],"message":"dimensions must be at least 0.01 (use a smaller unit, e.g. mm)"}]}

Limits#