Bin-Packing APIFree beta
The same Extreme-Point engine that powers the planner, as one HTTP call. Weight limits, stacking rules, fragile cartons, keep-together groups and multi-stop unload zones — all enforced server-side. On the Bischoff–Ratcliff academic benchmark (300 container-loading instances) the engine averages 80% volume utilization with full stability constraints — run scripts/benchmark.mjs to reproduce. CORS is open: call it from browsers, scripts, or AI agents.
POST /api/case-design
Explore product factorisations into shipcases, TI-HI, pallet/container fill and delivered cost per unit. Anonymous: 10/minute, 100/day; JSON body ≤32 KB. Optional X-API-Key or Authorization. GET returns a runnable example.
Request: units cm-kg or in-lb; product {l,w,h,weight,keepUpright?,label?}; unitsPerCase integer or {min,max}; optional caseConstraints {maxWeight,maxDim,boardThickness,headspace,allowedOrientations:any|upright}, pallet {l,w,baseHeight,maxHeight,maxWeight,overhang?}, container {preset:20gp|40gp|40hq} or custom l,w,h,maxWeight; costs {boardCostPerM2,freightPerContainer,freightPerPallet,handlingPerCase,currency}; candidates 1–10 (default 5).
Response: found or none_within_search; candidates with arrangement, caseOuter (each axis cm/in), caseWeight (kg/lb), pallet, container, optional costPerUnit breakdown, checks and assumptions. Top level includes checks, CHECK_SEMANTICS, engineVersion, SHA-256 inputHash, notes, searched counts and limits. No costs: units/container descending then pallet fill; costs: lowest supplied cost/unit first. Missing costs are zero; pallet and container freight are additive. No destination is allowed for case-only screening.
Search: up to 24 evenly sampled quantities including endpoints, 400 deduplicated outer dimensions (0.1 cm key), 2,000 floor-loaded cases. Product counts 1–2,000. Cases stay upright in transit; column stacks only; pallet container loads use at most two tiers and assume 25 kg pallet tare. Board area ×0.6 kg/m² is an estimate, with 0.4 cm board per side and 0.5 cm headspace defaults. Not ECT/BCT certification or an optimality proof. Infeasible candidates are rejected; no feasible result returns explicit not_evaluated checks. Errors: 400 with field for validation, 413 oversized body, 415 media type, 429 rate limit.
curl https://www.dimpack3d.com/api/case-design
curl https://www.dimpack3d.com/api/case-design -H 'Content-Type: application/json' --data-binary @case-design-request.jsonOrder options — avoid another pallet
POST /api/order-options accepts the order-quote request plus the fields below. It searches unchanged quantities first using three strategies and four item orderings, then permitted single-SKU changes and pairs only if repacking found nothing. Returns up to three complete alternatives ranked by pallet count, absolute units changed, then net savings. All envelope, stacking, packaging and receiver limits stay unchanged.
targetPalletCount: integer 1–20; defaults to baseline count minus one, at least one. A goal: smaller improvements and additions without another pallet may still be returned.adjustments[]: {sku,minQty,maxQty,qtyStep?}: all SKUs locked by default. Unique existing SKU; bounds 0–200 must include current quantity; step defaults to 1, anchored at current quantity. Zero defers the entire SKU; empty shipments are excluded.searchBudget: integer 1–200, default 60; baseline excluded. SEARCH_BUDGET warns when exhausted. Best option found, not a proof of optimality.economics: optional palletFreight, handlingPerPallet, contributionPerUnit and deferralCostPerUnit maps by SKU, currency (three uppercase letters). Non-negative customer-supplied costs only. Net = freight saved + handling saved − deferred contribution − deferral cost. Added contribution is a negative deferred contribution. Missing required costs return netSavings:null; enter explicit zero for no cost.
Response: baseline{palletCount,quote}, target, searched{candidates,budget,phase1,phase2}, alternatives[]{id,palletCount,strategy,packing,quantityChanges,quote,candidateHash,economics}, outcome found|none_within_search|already_at_target, checks, semantics, notes, engineVersion, inputHash, tier and warning for invalid keys. Complete nested quotes include both cm/in and kg/lb. quoteOrder also accepts actuals.summary{actualPalletCount,selectedOptionId,measuredAt} and echoes it in variance.summary; measuredAt is an ISO timestamp. To reproduce a selected build, send its packing selection with the changed order-quote request.
Anonymous: 5/min, 50/day. JSON body ≤48 KB. GET returns an example; OPTIONS supports CORS with X-API-Key and Authorization. Errors: 400 with field for validation, 413 oversized body, 415 wrong content type, 429 rate limit. Checks are screening, not certification.
curl -s https://www.dimpack3d.com/api/order-options > order-options-help.json
curl -X POST https://www.dimpack3d.com/api/order-options -H 'Content-Type: application/json' --data-binary @order-options-request.jsonTry the options panel →POST /api/consolidate
Multi-PO consolidation: up to three best options found, with container placements, checks, PO/SKU remainder and optional LCL economics. Same-origin-region, one-destination screening; not a booking.
Request: units (cm-kg or in-lb), pos[]{poId,supplier?,readyDate?,priority?,items[]{sku?,label?,l,w,h,qty,weight,keepUpright?,maxStack?,fragile?}}, window{from?,to?}, containers[]{preset:20gp|40gp|40hq,maxCount?,ratePerContainer?}, lcl{ratePerCbm?,minCbm?}, rules{keepPoTogether?,maxSuppliersPerContainer?,unloadOrderByPo?}, searchBudget. Inclusive ISO dates; undated POs are skipped when a window is set. Priority 1 ships first among eligible POs.
Limits: 30 POs, 60 item types, 3,000 cartons, 64 KB. Anonymous 5/min, 50/day; optional X-API-Key or Authorization: Bearer key. Budget defaults to 40 packing runs, maximum 120; up to 12 mixes in the 55–85% volume band, with an explicitly noted closest-volume fallback if none exist. maxCount defaults to 12. Fragile enforces zero load on top; maxStack is weight, not layer count.
Each plan has status complete when every eligible carton is in a container, partial otherwise. Top-level status is complete when a complete plan is found (or no cartons are eligible), partial otherwise. Existing outcome is unchanged.
Response: eligible{poIds,skipped}, cargo, plans[]{containers{boxes,zones,checks},remainder{cartons,cbm,byPo},lcl?,cost,candidateHash,checks}, outcome found|none_fit_budget, searched{mixes,packs,budget}, engineVersion, inputHash, semantics, notes, tier and invalid-key warning. Missing rates return null cost; all quotes must use the same currency. LCL is unverified volume economics. Boxes/zones use cm/kg; dimensions and weights include both units. Errors: 400 with field for validation, 413 oversized, 415 content type, 429 quota.
curl https://www.dimpack3d.com/api/consolidate > consolidate-docs.json curl -X POST https://www.dimpack3d.com/api/consolidate -H 'Content-Type: application/json' --data-binary @consolidation-request.jsonOpen consolidation planner →
POST /api/box-catalog
Choose a small box catalog for order history. GET returns a complete example. POST accepts units, skus[{sku,l,w,h,weight,keepUpright?,fragile?}], orders[{orderId?,lines[{sku,qty}],count?}], currentBoxes?, candidateBoxes? (id,l,w,h,maxWeight?,cost?), catalogSize 1–12, billing{dimDivisor,unit,minBillableWeight?,ratePerKgOrLb?}, voidFillCostPerLitre?, searchBudget? (200 default, 1000 max).
Limits: 96 KB, 300 SKUs, 500 shapes, 60 units / 10 SKUs per shape, 40 boxes per pool. Anonymous 5/min, 50/day; X-API-Key or Authorization supported. Returns catalog{dims:cm/in,usedByOrders,sharePct}, perOrder, totals (weights kg/lb), baseline?, savings?, checks, searched, semantics, engineVersion, inputHash, tier and warning for invalid keys. Missing rate gives RATE_PROVIDED:not_evaluated and omits costs. 400 invalid input with field, 413 oversized body, 415 wrong content type, 429 rate limit.
Generated candidates always use a 1 cm grid, including imperial requests; dimensions are returned in cm and inches. Status is complete when all orders fit, partial otherwise.
Best option found, not optimum. No cushioning, carrier rounding or box tare. Unfit orders are flagged and charged at the largest candidate by volume, in totals only; savings exclude orders unfit in either catalog. Box cost and void fill join the monetary objective only when a rate is supplied; otherwise search uses billed kg. Low search budgets fill remaining slots deterministically. chooseBox uses the smallest billed weight for a complete single order.
coverageFirst defaults to true: maximize count-weighted coverage, then minimize billed weight/cost. Set false for analysis only. Savings use only orders fitting both catalogs, with basisOrders, excludedUnfit and partial when exclusions exist; per-1,000 savings use basisOrders. Without currentBoxes, baseline and savings are omitted. Unfit results include unfitSharePct and COVERAGE_LIMITED_BY_CATALOG_SIZE; coverage is the best found within the search budget, not a proof of impossibility.
curl https://www.dimpack3d.com/api/box-catalog -H 'Content-Type: application/json' --data-binary @box-request.jsonTry box catalog →
POST /api/receiver-check
Send an order-quote request plus profileId or a complete profile object. Profile dimensions and limits are cm/kg, converted to the request units. GET lists templates with sources and dates. Anonymous: 10/min, 100/day. Returns the quote, profile metadata and version, profileChecks, tier, and warning for invalid keys. Any failed profile rule yields needs_review; always inspect cartonsUnplaced for partial orders. Manual rules are not_evaluated. Templates require buyer confirmation; this is not acceptance or chargeback prediction.
{
"profileId": "ltl-standard-us",
"orderId": "SO-1",
"pallet": {
"l": 121.92,
"w": 101.6,
"baseHeight": 14.5,
"maxHeight": 200,
"maxWeight": 900,
"tareWeight": 22
},
"items": [
{
"sku": "A",
"l": 40,
"w": 30,
"h": 25,
"qty": 10,
"weight": 5
}
]
}400: invalid fields · 404: unknown profileId · 413: body too large · 415: application/json required · 429: rate limit
Order → pallet quote API
POST /api/order-quote is the quoting-workflow wrapper over order-plan: explicit units (cm-kg or in-lb), stable sku ids, gross weight incl. pallet tare and packaging allowance, receiver/carrier limit checks with stated assumptions, inputHash + engineVersion, and a meter id (one distinct order). Interactive version with CSV import and predicted-vs-measured tracking: /order-quote
- status
- complete — every carton placed and every supplied limit passed. partial — not all cartons fit within maxPallets; never quote it as a whole shipment. needs_review — placed, but a supplied limit (limits.maxLoadedHeight / maxGrossWeight) failed; reviewReasons[] says which.
- pallets[].outerDims / grossWeight / checks[]
- Outer height = base + cargo + packagingAllowance.height; gross = cargo + pallet.tareWeight + packagingAllowance.weight. Both unit systems are returned. Every check carries code, status (pass|fail|warn|not_evaluated), observed, limit and the assumption used — an empty or not_evaluated check is never a pass.
- inputHash / engineVersion / meter
- SHA-256 of the canonical cm/kg planning input (unit system does not change it; actuals do not either). Same hash + same engineVersion = same result. meter.unit is "order" and meter.id is your orderId (or the hash prefix) — the billable unit on paid plans.
- actuals
- Optional { "0": { height, grossWeight, note } } keyed by pallet index, in request units; returns variance per pallet (delta, deltaPct). This is how the pilot measures us.
- Limits
- 20 carton types, 200 cartons, 20 pallets, 32 KB body per order. Anonymous 10/min, 100/day per IP; free key 5×. GET the endpoint for a full example request.
curl -s https://www.dimpack3d.com/api/order-quote | jq .request > order-quote-request.json
curl -X POST https://www.dimpack3d.com/api/order-quote \
-H "Content-Type: application/json" -H "X-API-Key: dp_live_…" \
--data-binary @order-quote-request.json | jq '{status, reviewReasons, summary, pallets: [.pallets[] | {outerDims, grossWeight, checks: [.checks[] | {code, status}]}]}'Legacy synchronous cm/kg-only API: supplying a units field in the pallet request is rejected. Responses use version, without engineVersion or inputHash. Use /api/order-quote for unit conversion and SHA-256 identity.
Multi-pallet order planning API
POST /api/order-plan allocates remaining cartons across up to 20 pallets. Send { request, maxPallets }, using the pallet and items fields below. All API dimensions are centimetres and all weights kilograms, even when the website displays inches and pounds.
Build an order, compare footprints and download its API request →- status / placedCount / unplacedCount / byItem
- Use these top-level fields for the whole order. complete means every carton is placed. partial means some remain, even if individual pallets look full. Always check unplacedCount before quoting a whole shipment.
- pallets[] / loadedHeight / boxes
- Each pallet contains its height including the base, cargo weight, positions and per-SKU remainder at that allocation step. Box IDs remain unique across the order. py is measured from the pallet deck, not the ground. Sort by py, then pz, then px for bottom-up build steps.
- Limits and interpretation
- 20 carton types, 200 cartons total, maxPallets 1–20, JSON body ≤32 KB. Free beta: 10 requests/minute and 100/day per IP when rate-limit storage is configured. Error codes match the single-pallet endpoint below. This is a heuristic allocation, not proof of minimum height or fewest pallets; support checks do not certify transport stability.
JavaScript · Node.js 20+
import { readFile } from 'node:fs/promises';
const input = JSON.parse(await readFile('order-request.json', 'utf8'));
const response = await fetch('https://www.dimpack3d.com/api/order-plan', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(input), signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(await response.text());
const plan = await response.json();
console.log(plan.status, plan.palletCount, plan.unplacedCount);
for (const pallet of plan.pallets) console.log(pallet.loadedHeight);Python · standard library
import json
from urllib.request import Request, urlopen
with open('order-request.json', 'rb') as source:
request = Request('https://www.dimpack3d.com/api/order-plan',
data=source.read(), headers={'Content-Type': 'application/json'})
with urlopen(request, timeout=30) as response:
plan = json.load(response)
print(plan['status'], plan['palletCount'], plan['unplacedCount'])
for pallet in plan['pallets']:
print(pallet['loadedHeight'])Estimate a pallet’s loaded height
POST /api/pallet-estimate compares three packing approaches and returns the best plan found. Height includes the pallet base. A partial result describes only the cartons placed, not the whole order. Minimum height is not proven.
Legacy synchronous cm/kg-only API: supplying a units field in the pallet request is rejected. Responses use version, without engineVersion or inputHash. Use /api/order-quote for unit conversion and SHA-256 identity.
Try an order and download a working request →{
"pallet": {
"l": 120,
"w": 80,
"baseHeight": 15,
"maxHeight": 160,
"maxWeight": 750
},
"items": [
{
"label": "Main cartons",
"l": 60,
"w": 40,
"h": 30,
"qty": 12,
"weight": 8,
"keepUpright": true
},
{
"label": "Small cartons",
"l": 40,
"w": 30,
"h": 20,
"qty": 8,
"weight": 4,
"keepUpright": true
}
]
}- pallet
- l/w: footprint; baseHeight: empty pallet height; maxHeight: total height cap including base; maxWeight: cargo-only payload. All cm/kg. Limits must be supplied.
- items[]
- label, l/w/h, integer qty, positive weight and boolean keepUpright are required. Optional maxStack is the allowed kg on top; 0 prevents stacking. Missing maxStack leaves carton strength unchecked.
- status / loadedHeight / byItem / boxes
- complete means all cartons placed; partial means some remain. loadedHeight includes baseHeight. byItem lists requested, placed and remaining counts. boxes uses px/py/pz min-corner positions, with py measured above the pallet deck. cargoWeight excludes pallet tare.
- Limits and errors
- 20 types / 200 cartons / 32 KB per request. Free beta: 20/min and 200/day per IP when rate-limit storage is configured. HTTP 400: invalid JSON or fields; 413: body too large; 415: wrong content type; 429: rate limit (Retry-After header). Larger production orders: contact us.
Request
curl -X POST https://www.dimpack3d.com/api/pack \
-H "Content-Type: application/json" \
-d '{
"container": { "l": 589, "w": 235, "h": 239, "maxWeight": 28200 },
"items": [
{ "label": "Master carton", "l": 60, "w": 40, "h": 40, "weight": 18, "qty": 120 },
{ "label": "Fragile", "l": 45, "w": 35, "h": 25, "weight": 6, "qty": 30, "maxStack": 0 }
]
}'Response
{
"boxes": [ { "id": "i0-0", "label": "Master carton", "l": 60, "w": 40, "h": 40,
"px": 0, "py": 0, "pz": 0, "weight": 18 }, … ],
"unplaced": 0,
"stats": { "volumeUtil": 78.4, "totalWeight": 2340, "weightUtil": 8.3,
"cog": { "x": 271, "y": 63, "z": 115 }, "cogOffsetPct": { "x": -8, "z": -2 } },
"zones": [ { "unloadOrder": 1, "xStart": 0, "xEnd": 589, "count": 150 } ],
"checks": [ { "code": "DOOR_APERTURE", "status": "not_evaluated",
"assumption": "Send container.door {w,h} or container.preset to evaluate…" },
{ "code": "HEAVY_OVER_LIGHT", "status": "pass", "observed": "none", "assumption": "…" }, … ],
"checksSummary": { "evaluated": 7, "notEvaluated": ["DOOR_APERTURE","AXLE_LOADS","VGM"], "failed": [], "warned": [] },
"computeMs": 41,
"engine": "dimpack3d-extreme-point"
}Fields
| container.l/w/h | Internal dimensions in cm (required) |
| container.maxWeight | Payload limit in kg (optional) |
| container.preset | "20gp" | "40gp" | "40hq" — fills interior, payload, door aperture and tare; explicit fields override |
| container.door | {w,h} door aperture in cm (optional) — enables the DOOR_APERTURE check |
| container.axles | {frontPos, rearPos, frontLimit, rearLimit} cm/kg (optional, trucks) — enables AXLE_LOADS |
| container.tare | Container tare kg (optional) — shown on the VGM line; VGM is never declared here |
| items[].l/w/h | Item dimensions in cm (required) |
| items[].qty | Units of this item (default 1; ≤2,000 total per request) |
| items[].weight | kg per unit (optional; enables weight/CoG stats) |
| items[].maxStack | Max kg allowed on top; 0 = fragile, nothing stacks on it |
| items[].keepUpright | true = never tip on its side ("this way up") |
| items[].allowRotate | false = fixed orientation |
| items[].group | Keep-together group name (packed contiguously) |
| items[].unloadOrder | 1 = unload first (loaded nearest the door); LIFO zones |
checks[] — every response, every check, explicit status
Each /api/pack response carries the reality-check library as checks[] with status pass | fail | warn | not_evaluated, the observed value, the limit and the assumption used: PLACEMENT_COMPLETE, DOOR_APERTURE, PAYLOAD, STACK_LIMITS_PROVIDED, HEAVY_OVER_LIGHT, COG_HEIGHT, LOAD_VOIDS, AXLE_LOADS, VGM, ZONE_SEGREGATION. A check whose input you did not send comes back not_evaluated with the field to add — it is never silently a pass. checksSummary lists what was evaluated, failed and warned. /api/pallet-estimate, /api/order-plan and /api/order-quote carry the pallet equivalents. These are screens against your numbers, not certification of stability, road legality, dangerous-goods compatibility or receiver acceptance.
Units: cm / kg. Limits: 100 item types, 2,000 total units per request. Positions are min-corner (px, py, pz) with the door at +X.
Terms: Free during beta, fair use. Results are estimates — verify critical loads. Higher limits and commercial plans: /api-pricing