Intelligent Packing API
The Intelligent Packing API provides AI-enhanced 3D bin packing built on a three-layer hybrid architecture. It combines a deterministic rule engine, an AI matching/pre-screening layer, and an OR-Tools mathematical solver.
Overview
The service processes each request through three layers:
- Layer 1 — Rule Engine: handles pre-assigned pallets and filters out unavailable / unfit pallet types.
- Layer 2 — AI Model: SKU–pallet matching, pallet pre-screening, and solution recommendations.
- Layer 3 — OR-Tools: mathematically guaranteed bin-packing solve.
Additional capabilities:
- AI pallet pre-screening (reduce a large pallet catalog to an optimal subset)
- Parallel and batch processing for large item counts
- Dynamic entropy tuning
- LDM (Loading Meter) output for logistics billing
Base URL:
https://api.alaikis.comAll endpoints are mounted under both /api/intelligent-packing and /api/v1/intelligent-packing (v1 alias), and require authentication.
Endpoints
| Method | Path | Description |
|---|---|---|
| POST | /api/intelligent-packing/pack | Run intelligent packing (three-layer solve) |
| POST | /api/intelligent-packing/pack/preview | Preview a plan without solving |
| POST | /api/intelligent-packing/filter-pallets | Filter unavailable / unfit pallet types |
| POST | /api/intelligent-packing/prescreen-pallets | AI pallet pre-screening (top-K subset) |
| GET | /api/intelligent-packing/health | Health check |
Pack
Run the full three-layer intelligent packing solve.
Endpoint:
POST /api/intelligent-packing/packRequest Body:
{
"skus": [
{
"id": "SKU001",
"weight": 5.5,
"weight_unit": "kg",
"length": 50,
"width": 30,
"height": 20,
"length_unit": "cm",
"quantity": 10,
"is_dangerous": false,
"is_stackable": true
}
],
"pallets": [
{
"id": "PALLET_A",
"length": 120,
"width": 100,
"height": 150,
"weight": 20,
"max_weight": 500,
"length_unit": "cm",
"weight_unit": "kg",
"cost": 5.0
}
],
"use_ai": true,
"entropy_factor": 1.0,
"time_limit_ms": 60000,
"ldm_width": 2.44,
"ldm_mode": "projection"
}SKU Fields (skus[])
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | Yes | — | SKU identifier |
weight | float | Yes | — | Item weight |
weight_unit | string | No | "kg" | Weight unit |
length / width / height | float | Yes | — | Item dimensions |
length_unit | string | No | "cm" | Dimension unit |
quantity | integer | No | 1 | Number of items |
is_dangerous | boolean | No | false | Dangerous goods flag (isolated during packing) |
is_stackable | boolean | No | true | Whether the item can be stacked |
Pallet Fields (pallets[])
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | Yes | — | Pallet type identifier |
length / width / height | float | Yes | — | Pallet dimensions |
weight | float | No | 0.0 | Pallet tare weight |
max_weight | float | Yes | — | Maximum load weight |
length_unit | string | No | "cm" | Dimension unit |
weight_unit | string | No | "kg" | Weight unit |
cost | float | No | 0.0 | Pallet cost |
Request Options
| Parameter | Type | Default | Range | Description |
|---|---|---|---|---|
use_ai | boolean | true | — | Enable AI enhancement |
entropy_factor | float | 1.0 | 0.8–2.0 | Packing entropy factor |
time_limit_ms | integer | 60000 | 1000–300000 | Solver time limit (ms) |
enable_parallel | boolean | true | — | Enable parallel processing |
prescreen_pallets | boolean | true | — | Enable AI pallet pre-screening |
top_k_pallets | integer | 15 | 5–100 | Pallet subset size after pre-screening |
max_workers | integer | 8 | 1–16 | Max parallel worker threads |
pre_assigned_pallets | array | null | — | Pre-assigned pallets (see below) |
unavailable_pallet_types | string[] | null | — | Pallet type IDs to exclude |
ldm_width | float | null | > 0 | LDM normalization vehicle width (m); defaults to 2.4 |
ldm_mode | string | "projection" | — | LDM mode: "projection" or "arrangement" |
Pre-assigned pallets
pre_assigned_pallets[] items use the shape: { "pallet_type_id": "PALLET_A", "pallet_id": "P-1", "items": [{ "sku_id": "SKU001", "quantity": 4 }], "used_volume": 0.0, "used_weight": 0.0 }.
Response:
{
"pallets": [
{
"id": "pallet_1",
"pallet_id": "pallet_1",
"pallet_type": "PALLET_A",
"items": [
{ "sku_id": "SKU001", "quantity": 10 }
],
"total_weight": 75.0,
"total_volume": 0.347,
"utilization": 19.3,
"source": "computed",
"ldm": 0.492
}
],
"total_pallets": 1,
"total_weight": 75.0,
"total_volume": 0.347,
"processing_time_ms": 12.3,
"layers_used": ["L1_RULE_ENGINE", "L2_AI_MODEL", "L3_OR_TOOLS"],
"ai_recommendations": null,
"stats": {
"total_pallets": 1,
"total_weight": 75.0,
"total_volume": 0.347,
"total_ldm": 0.492,
"ldm_mode": "projection",
"ldm_width_used": 2.44
},
"prescreen_stats": null,
"performance_stats": {},
"errors": [],
"warnings": [],
"success": true,
"message": "装托成功",
"total_ldm": 0.492,
"ldm_mode": "projection",
"ldm_width_used": 2.44
}Response Fields
| Field | Type | Description |
|---|---|---|
pallets[] | array | Solved pallets |
pallets[].id / pallet_id | string | Pallet instance ID (kept in sync) |
pallets[].pallet_type | string | Source pallet type ID |
pallets[].items | array | { sku_id, quantity } placed on the pallet |
pallets[].total_weight | float | Loaded weight (kg) |
pallets[].total_volume | float | Loaded volume (m³) |
pallets[].utilization | float | Volume utilization (%) |
pallets[].source | string | "computed" or "pre_assigned" |
pallets[].ldm | float | Per-pallet loading meter |
total_pallets | integer | Number of pallets used |
layers_used | string[] | Processing layers applied |
stats | object | Aggregated statistics (weights, volumes, LDM, etc.) |
total_ldm | float | Total loading meters for the solution |
ldm_mode | string | LDM mode used |
ldm_width_used | float | Actual vehicle width used (m) |
success | boolean | Whether the solve succeeded |
message | string | Human-readable status |
LDM (Loading Meter)
LDM (Loading Meter) measures the equivalent floor length a solution occupies in a vehicle — the standard billing metric for European road freight.
Request Parameters (top-level, alongside skus / pallets)
| Parameter | Type | Default | Description |
|---|---|---|---|
ldm_width | float | null | Vehicle width in meters. When omitted, defaults to 2.4. |
ldm_mode | string | "projection" | Calculation mode: "projection" or "arrangement" |
Vehicle width on this endpoint
The /pack request has no vehicle field. Vehicle width therefore resolves as ldm_width (if provided) otherwise the 2.4 m default. Pass ldm_width explicitly to override.
Response Fields
| Field | Type | Description |
|---|---|---|
total_ldm | float | Total loading meters for the solution |
ldm_mode | string | Mode used for calculation |
ldm_width_used | float | Actual vehicle width used (meters) |
pallets[].ldm | float | Per-pallet loading meter |
projection Mode
ldm = (pallet_length_m * pallet_width_m) / ldm_width_m
total_ldm = Σ ldmExample: a 1.2 m × 1.0 m pallet with ldm_width = 2.44 yields ldm = 1.2 * 1.0 / 2.44 = 0.492.
arrangement Mode
per_row = max(1, floor(ldm_width_m / pallet_width_m))
rows = ceil(total_pallets / per_row)
total_ldm = rows * pallet_length_m / ldm_width_marrangement assumes pallets are placed side-by-side along the vehicle width.
Example Request with LDM
payload = {
"skus": [
{"id": "SKU001", "weight": 5.5, "length": 50, "width": 30, "height": 20, "quantity": 10}
],
"pallets": [
{"id": "PALLET_A", "length": 120, "width": 100, "height": 150, "max_weight": 500}
],
"ldm_width": 2.44,
"ldm_mode": "projection"
}Preview
Preview a packing plan without running the full solve. Returns valid SKU / available pallet counts, AI recommendations, and an estimated pallet count.
Endpoint:
POST /api/intelligent-packing/pack/previewThe request body is the same shape as /pack.
Response:
{
"valid_skus": 1,
"available_pallets": 1,
"pre_assigned_pallets": 0,
"ai_recommendations": null,
"estimated_pallets": 2,
"warnings": [],
"preview": true
}Filter Pallets
Filter out pallet types that are marked unavailable or that no SKU can fit into.
Endpoint:
POST /api/intelligent-packing/filter-palletsRequest Body:
{
"skus": [
{"id": "SKU001", "weight": 5.5, "length": 50, "width": 30, "height": 20, "quantity": 10}
],
"pallets": [
{"id": "PALLET_A", "length": 120, "width": 100, "height": 150, "max_weight": 500}
],
"unavailable_pallet_types": ["PALLET_X"]
}Response:
{
"available_pallets": [
{"id": "PALLET_A", "length": 120, "width": 100, "height": 150, "max_weight": 500}
],
"filtered_count": 0,
"total_count": 1,
"filter_reasons": []
}filter_reasons[] entries carry a pallet_id and a reason of "marked_unavailable" or "no_sku_fits".
Prescreen Pallets
AI pallet pre-screening — reduce a large pallet catalog to an optimal top-K subset before solving.
Endpoint:
POST /api/intelligent-packing/prescreen-pallets?top_k=20&use_ai=true| Query Parameter | Type | Default | Description |
|---|---|---|---|
top_k | integer | 20 | Number of pallet types to keep |
use_ai | boolean | true | Enable AI scoring |
The request body uses the same skus / pallets / unavailable_pallet_types shape as Filter Pallets.
Response:
{
"original_pallets": [ /* PalletType[] */ ],
"filtered_pallets": [ /* PalletType[] */ ],
"prescreen_stats": {},
"performance_stats": {}
}Health
Endpoint:
GET /api/intelligent-packing/healthResponse:
{
"status": "healthy",
"service": "intelligent-packing",
"version": "2.1.0",
"layers": ["L1_RULE_ENGINE", "L2_AI_MODEL", "L3_OR_TOOLS"],
"features": ["ai_prescreen", "parallel_processing", "performance_stats"]
}Example Workflow
import requests
headers = {"Authorization": "Bearer <your-jwt-token>"}
base = "https://api.alaikis.com/api/intelligent-packing"
skus = [
{"id": "SKU001", "weight": 5.5, "length": 50, "width": 30, "height": 20, "quantity": 10}
]
pallets = [
{"id": "PALLET_A", "length": 120, "width": 100, "height": 150, "max_weight": 500}
]
# 1) Preview to estimate pallet count
preview = requests.post(f"{base}/pack/preview",
json={"skus": skus, "pallets": pallets},
headers=headers).json()
print("Estimated pallets:", preview["estimated_pallets"])
# 2) Run the full solve with LDM
result = requests.post(f"{base}/pack",
json={
"skus": skus,
"pallets": pallets,
"ldm_width": 2.44,
"ldm_mode": "projection"
},
headers=headers).json()
print("Pallets used:", result["total_pallets"])
print("Total LDM:", result["total_ldm"])
for pallet in result["pallets"]:
print(pallet["pallet_type"], "->", pallet["utilization"], "% | ldm", pallet["ldm"])Performance Notes
- Large item counts automatically switch to batch mode; medium counts use parallel processing.
- Pre-screening (
prescreen_pallets) is applied automatically when the pallet catalog exceedstop_k_pallets. - Use
time_limit_msto cap solver time for real-time scenarios.