Slotting 1.0.0

Bin setups

A layout (bin setup) describes your put walls: each wall, where it stands, and every bin with its size and limits. You create it once with POST /api/layouts and replace it with PUT /api/layouts/{layoutId} when the walls change. Waves always refer to a layout.

You can write the layout JSON yourself, or let the install draft it from a CAD drawing, a spreadsheet, a grid description or a photo. Every import returns a draft with warnings and saves nothing: check it (the setup editor on the admin page, or the <slotting-setup> component, shows it), fix what's wrong, then save it.

The layout model#

json
{
  "id": "PUTWALL-A",
  "name": "Put wall A, dock 3",
  "unit": "cm",
  "weightUnit": "kg",
  "fillFactor": 0.85,
  "walls": [
    {
      "id": "A",
      "kind": "fixed",
      "position": { "x": 0, "y": 0 },
      "rotation": 0,
      "bins": [
        { "id": "A-01-01", "x": 0,  "y": 30, "width": 40, "height": 30, "depth": 40, "maxWeight": 15 },
        { "id": "A-01-02", "x": 40, "y": 30, "width": 40, "height": 30, "depth": 40, "maxWeight": 15, "maxUnits": 20 },
        { "id": "A-02-01", "x": 0,  "y": 0,  "width": 40, "height": 30, "depth": 40, "disabled": true }
      ]
    }
  ]
}

Units. Every length of a layout is in its unit (mm, cm, m, in or ft) and every weight in its weightUnit (kg, g, lb or oz). The item dimensions and weights you send with a wave must use the same units: the service never converts.

Bins on the wall face. Look at the wall from the side where the operator puts. x is the distance of a bin's bottom-left corner from the wall's left edge, y its height above the floor (or above the wall's lowest bin; only the differences matter). width and height are the opening, depth how far the bin goes into the wall:

text
   y ▲
     │  ┌────────┬────────┬────────┐
     │  │A-01-01 │A-01-02 │A-01-03 │   row 1 (top)
  30 ┤  ├────────┼────────┼────────┤
     │  │A-02-01 │A-02-02 │A-02-03 │   row 2
   0 ┼──┴────────┴────────┴────────┴──▶ x
        0        40       80      120
Field
id (layout) required 1 to 100 of A-Z a-z 0-9 . _ : -, starting with a letter or digit
unit, weightUnit required See above
fillFactor 0.85 The share of a bin's volume that can really be filled (0.1 to 1). A wave can override it. See Capacity
walls[].id required Unique in the layout
walls[].kind required fixed, or mobile for a put cart or a wall on wheels that can move while waves run
walls[].position Where the wall's bottom-left corner stands on the floor (x, y in layout units). Needed only when an order may get bins on two walls (see below)
walls[].rotation 0 The direction the wall face runs along the floor, in degrees counter-clockwise from the floor's x axis
bins[].id required The location code printed on the bin label. Unique in the whole layout, not just the wall: scans return it
bins[].x, y required Bottom-left of the opening on the wall face
bins[].width, height, depth required The opening and the depth, 0.01 to 1,000,000
bins[].maxWeight no limit The most the bin may hold, in weightUnit
bins[].maxUnits no limit The most units the bin may hold. Set it for bins that take items without dimensions (why)
bins[].disabled false Out of service: never assigned
bins[].lightAddress the bin id The address of the bin's put-to-light module: 1 to 64 of A-Z a-z 0-9 . _ : / -, unique in the layout, and not another bin's id

The install checks a layout when it is saved and answers 422 invalid_layout with the problems listed in issues:

Where the walls stand#

Bins of one wall are measured on the wall itself, so a layout with one wall needs no positions. When an order may get bins on two walls (a big order on a station with two walls, or a wall pair facing each other), slotting needs to know how far apart they are: give each wall a position and rotation on the floor plan. Without positions, bins of different walls count as very far apart, so an order only spans two walls when there is no other way.

Mobile walls (put carts) can be moved while waves run: PATCH /api/layouts/{layoutId}/walls/{wallId} with { "position": { "x": 1200, "y": 300 }, "rotation": 90 }. It changes only the choice of bins from then on; units already put stay where they are. A wave can also be limited to some walls with wallIds (for example the carts docked at a station).

Changing a layout#

PUT /api/layouts/{layoutId} replaces the layout. Adding walls and bins is always allowed. Removing, shrinking or disabling a bin that an active wave holds is refused with 409 layout_in_use: release the orders or close the wave first. Moving a held bin to another wall, or adding or lowering the maxWeight / maxUnits of a held bin, is refused the same way. DELETE /api/layouts/{layoutId} is refused while the layout has a wave that is not closed.

From a CAD drawing (DXF)#

POST /api/import/dxf with { "content": "<the DXF file as text>", "options": { ... } } reads a front elevation of one or more walls and returns a draft layout.

What the drawing must look like#

Limits: 20 MB of text, 1,000,000 entities (counting expanded blocks), 10,000 bins.

Option Default
depth required The depth of every bin: an elevation has no depth. Edit single bins in the draft afterwards
unit the drawing's $INSUNITS mm, cm, m, in, ft. Required when the drawing has no units setting
layer every layer A layer name or a list: only geometry on these layers (case-insensitive). Text is read from every layer
splitWalls layer if any layer is named WALL-*, else none layer: one wall per layer; none: everything is one wall
wallId W1 The wall id when the drawing is one wall
binIdPattern {wall}-{row}-{col} The id of a bin without text. {row} counts from the top and {col} from the left, from 1, at least two digits; {n} numbers the bins in reading order
minBinSize 0 Ignore rectangles narrower or lower than this (drawing units)
layoutId, name imported The draft's id and name
weightUnit kg
bash
# Read the file into the JSON body with jq, then import:
jq -Rs '{content: ., options: {depth: 40, unit: "cm", binIdPattern: "{wall}-{row}-{col}"}}' wall-a.dxf \
  | curl -s -X POST http://127.0.0.1:10083/api/import/dxf -H 'Content-Type: application/json' -d @-

The answer is { "layout": { ... }, "warnings": [ ... ] }. Bin positions are measured from the bottom-left of each wall's bins, wherever the wall sits in the drawing.

Exporting ASCII DXF#

To check a file: open it in a text editor. An ASCII DXF starts with lines like 0, SECTION, 2, HEADER. A binary DXF starts with AutoCAD Binary DXF, and a DWG with AC10.

From a spreadsheet (CSV)#

POST /api/import/csv with { "content": "<the CSV text>", "options": { "unit": "cm", ... } }. One row per bin, with a header row. Column names are matched without regard to case, spaces, _ and -, and common aliases work:

Column Aliases
bin binId, location, locationCode, locationId, slot, cubby, id, code Required. The bin id
wall wallId, putWall, wallName The wall; default the wallId option, else W1
x, y left, bottom Bottom-left on the wall face. Either x and y, or row and col
row, col level, shelf / column Instead of x / y: row 1 is the top. Positions are computed by stacking: each column is as wide as its widest bin and each row as tall as its tallest
width, height, depth w, h, d Required per row, unless given once as options
maxWeight weightLimit, maxWt
maxUnits unitLimit, capacity, maxQty
disabled inactive yes, true, 1, x = out of service; empty, no, false, 0 = in service
wallKind kind, wallType fixed or mobile
wallX, wallY, wallRotation rotation The wall's floor position and rotation (once per wall is enough)
lightAddress light, light address, address The bin's put-to-light address; empty = the bin id. Checked like bins[].lightAddress above (an invalid or repeated address is invalid_layout)
Option Default
unit required The unit of the lengths in the file
width, height, depth Used for rows that leave those columns empty
wallId W1 For rows without a wall
layoutId, name, weightUnit imported, -, kg

The simple form, with rows and columns:

text
wall;location;row;col;width;height;depth;maxUnits
A;A-01-01;1;1;40;30;40;20
A;A-01-02;1;2;40;30;40;20
A;A-02-01;2;1;40;30;40;
A;A-02-02;2;2;40;30;40;

A regular grid#

POST /api/import/grid generates one wall of equal (or row- and column-wise sized) bins. The body is the grid itself:

json
{ "unit": "cm", "wallId": "B", "rows": 4, "cols": 6, "cellWidth": 30, "cellHeight": 25, "depth": 45, "gap": 2, "maxUnits": 15 }
Field Default
unit, wallId, depth required
rows required A count (then cellHeight is required) or the row heights, top to bottom: [25, 25, 40]
cols required A count (then cellWidth is required) or the column widths, left to right
cellWidth, cellHeight The bin size when rows / cols are counts
gap 0 Space between neighbouring bins (shelf and divider thickness)
origin { "x": 0, "y": 0 } Bottom-left of the grid on the wall face
binIdPattern {wall}-{row}-{col} As for DXF
kind fixed mobile for a put cart
maxWeight, maxUnits For every bin
layoutId, name, weightUnit imported, -, kg

To build a layout of several walls, import each wall and combine the drafts' walls into one layout (the setup editor does this when you import several times).

From a photo (AI)#

POST /api/import/photo drafts one wall from a photo. An AI vision model finds the wall and the bin openings and reads the bin labels it can see; the install turns that into real sizes with the measurements you give.

json
{ "image": "<base64 of the JPEG, PNG or WebP>", "mediaType": "image/jpeg",
  "hints": { "unit": "cm", "wallWidth": 240, "wallHeight": 180, "depth": 45, "rows": 6, "cols": 8, "wallId": "C" } }
Hint Default
unit required The unit of the measurements
depth required The depth of every bin (a front photo doesn't show it)
wallWidth, wallHeight The measured width and height of the bin area. Give at least one; the other is then estimated from the photo's proportions (with a warning). Without either, the model looks for something of known size in the photo (a tape measure, a pallet) and the result is a rough guess
rows, cols The grid you expect: passed to the model, and checked (a warning when the result differs)
wallId W1
binIdPattern {wall}-{row}-{col} Ids of bins whose label can't be read
layoutId, name, weightUnit imported, -, kg

Taking the photo: stand straight in front of the middle of the wall, far enough back to get the whole wall in one picture, with even light and the bin labels readable. Perspective and lens distortion are not corrected, so a photo taken from the side gives skewed sizes (there is a warning when the measured proportions and the photo differ by more than 15%). The draft always says that its sizes come from a photo: measure a few bins and correct the draft before saving it.

Phone photos can be taken in portrait or landscape: the rotation the phone stores in the photo (EXIF orientation) is honoured, so the draft matches the photo as you see it (the warnings say when the photo was rotated).

Limits: the image is JPEG, PNG or WebP (checked by its content, not its name), up to 15 MB; up to 2,000 bins per photo. iPhones save HEIC by default: export or share the photo as JPEG (or set Camera > Formats > Most Compatible). The import takes as long as the model needs, typically 10 to 60 seconds (SLOTTING_AI_TIMEOUT_MS, default 60 s).

Choosing an AI provider#

Photo import is off until the operator configures a provider with environment variables (DXF, CSV, grid and JSON work without one). GET /v1/info (ai) and the Status page show whether it is configured; without it the import answers 503 ai_not_configured, and a provider error or timeout answers 502 ai_failed.

Provider SLOTTING_AI_PROVIDER Settings
Anthropic (Claude) anthropic SLOTTING_AI_API_KEY or ANTHROPIC_API_KEY. SLOTTING_AI_MODEL optional (default claude-opus-5-5)
Amazon Bedrock bedrock SLOTTING_AI_MODEL: a model or inference profile id with image input. AWS_REGION, and AWS credentials from the default chain (environment, shared config, instance or task role). No API key
OpenAI openai SLOTTING_AI_API_KEY or OPENAI_API_KEY, and SLOTTING_AI_MODEL (a vision-capable model)
Azure OpenAI azure-openai AZURE_OPENAI_ENDPOINT (https://<resource>.openai.azure.com), AZURE_OPENAI_DEPLOYMENT (or SLOTTING_AI_MODEL), SLOTTING_AI_API_KEY or AZURE_OPENAI_API_KEY. AZURE_OPENAI_API_VERSION optional (default 2024-10-21)
Google Gemini google SLOTTING_AI_API_KEY or GEMINI_API_KEY, and SLOTTING_AI_MODEL (a Gemini model with image input)
OpenAI-compatible server (Ollama, vLLM, LM Studio, LiteLLM, ...) openai-compatible SLOTTING_AI_BASE_URL (for example http://ollama.internal:11434/v1) and SLOTTING_AI_MODEL (a vision model it serves). SLOTTING_AI_API_KEY only if the server needs one

SLOTTING_AI_BASE_URL also overrides the endpoint of the other providers, for example to go through your proxy or gateway. The full list of settings is in Install and operate.

Privacy. The photo is sent to the configured provider, together with the instructions for the model; nothing else (no orders, no layouts). With a cloud provider (Anthropic, OpenAI, Gemini, Azure OpenAI, Bedrock) the photo leaves your network and is processed under that provider's terms and data-retention rules, so check what may be visible in it (people, screens, documents) before uploading. To keep photos inside your network, use openai-compatible with a model you host yourself. The install never logs the image or the API key.