Always true on a 200 — a malformed body is a 400 with the exact bad field path instead. The body is the SAME one the matching optimize endpoint takes.
mode
string
"2d" | "1d" | "wood" | "nest" — the endpoint you called.
contractVersion
string
Shape version. Currently "1".
engineEnabled
boolean
NEST ONLY — whether the nesting engine is built into this deployment. Absent for the rectangular modes.
parts
object
{ rows, total } — part rows sent, and the total quantity after qty expansion. Check it against the part cap before you spend a call.
stock
object
{ rows, total } — the same for stock.
warnings
string[]
Approximate, SOLVE-FREE feasibility notes — e.g. a part that fits no stock (2D and nest: bounding box; wood: no matching section long enough). Empty ⇒ every part fits something. Ignores trim, kerf and material: a shape check, not a solve.
Unità di misura
L’API è indipendente dall’unità di misura. Scegli un’unità — millimetri, pollici, qualsiasi cosa — usala per ogni numero che invii, e ogni numero che ricevi in risposta sarà nella stessa unità. Nulla viene convertito lato server e nessun nome di campo presuppone un’unità.
Vale per le dimensioni dei pezzi e del materiale, per kerf, tolerance, trim e minOffcut in ingresso, e per ogni coordinata, posizione, lunghezza residua, sfrido e lunghezza di taglio in uscita. Mescolare unità di misura all’interno di una stessa richiesta produce un piano che passa la validazione ed è fisicamente sbagliato, e il server non è in grado di rilevarlo.
Sistema di coordinate
L’origine è l’angolo in alto a sinistra del pannello: x cresce verso destra lungo la larghezza del pannello, y cresce verso il basso lungo l’altezza. x e y di un pezzo indicano il suo angolo in alto a sinistra, mentre w e h sono le dimensioni così come è posizionato — già scambiate quando rotated è true — quindi il rettangolo x, y, w, h è l’ingombro sul pannello senza ulteriori calcoli. I rettangoli degli sfridi usano lo stesso sistema.
Il rifilo sposta i posizionamenti: trim.left sposta ogni pezzo verso destra e trim.top lo sposta verso il basso, perché i pezzi vengono disposti all’interno dell’area utile e poi riportati con un offset sul pannello intero. trim.right e trim.bottom riducono l’area utile senza spostare l’origine. w e h di un pannello sono sempre le dimensioni complete del materiale, rifilo incluso — ed è anche per questo che il rifilo conta come scarto in yieldPct.
default 1 — espanso lato server; conta nel limite di 2,000 pezzi
name
etichetta opzionale, riportata su ogni posizionamento
rotatable
default true — indica se il pezzo può essere ruotato di 90°
grainGroup
i membri di un gruppo restano sullo stesso pannello (corrispondenza venatura)
priority
da tagliare assolutamente: si aggiudica lo spazio sul pannello quando il materiale è limitato (con respectStock)
edgeBanding
bordatura per lato: indica un riferimento di tipo su uno tra top / right / bottom / left (una stringa libera, un tuo codice) — la risposta somma i metri per riferimento. Solo metadato, non sposta mai un pezzo. Solo 2D
material
tag di materiale (una stringa libera, un tuo codice): i pezzi e il materiale con lo stesso valore vengono disposti solo tra loro. A differenza di edgeBanding, cambia lo schema. Assente = un unico insieme non specificato. Funziona in ogni modalità
Campi del materiale
w e h sono obbligatori. qty vale 1 per default ed è un limite rigido solo con respectStock. price è per pannello e determina totalPrice e la modalità costo. priority (booleano) fa consumare prima questo materiale; material (una stringa libera) lo limita ai pezzi dello stesso materiale.
Opzioni
kerf
spessore della lama (default 0)
tolerance
accetta tagli che sforano al massimo di questo valore
trim
rifilo del bordo per lato: left, right, top, bottom
ordina i candidati per prezzo totale del materiale più basso; con più misure di materiale a prezzo le combina (2D) o sceglie la lunghezza più economica (1D) per ridurre la spesa, anche se così si usa più materiale
respectStock
tratta la qty di ogni riga di materiale come limite rigido
minOffcut
riporta solo gli sfridi il cui lato corto raggiunge almeno questo valore
maxCutStages
limite di fasi della sezionatrice — un conteggio di fasi, non la profondità dell’albero
minimizeRotations
preferisce gli schemi che ruotano meno pezzi
effort
'fast' | 'balanced' (default 'balanced'). Profondità di ricerca: 'balanced' esegue il best-of multi-strategia completo; 'fast' salta l'unica costosa ricerca di combinazioni di aree per pannello — molto più rapido sui lavori grandi al prezzo di qualche punto di utilizzo, sempre tagliabile a guillotina e mai più denso di 'balanced'. Sui lavori piccoli di solito è identico. Solo motore heuristic
include fa due cose. RESTRIZIONE — "cutPlan" e "offcuts" sono attivi per default; un array presente mantiene solo i token di restrizione che elenca (un array vuoto li esclude entrambi). EXPORT AGGIUNTIVO — "svg", "csv" e "dxf" aggiungono ciascuno quell’export alla risposta come STRINGA: svg un disegno dello schema 2D autonomo (solo 2D — una richiesta 1d/wood restituisce invece un avviso), dxf un disegno R12/AC1009 sui layer STOCK/PARTS/LABELS, csv una distinta di taglio. I token di export non influiscono sulla restrizione, quindi include:["svg"] aggiunge svg e — non nominando alcun token di restrizione — esclude cutPlan/offcuts; usa ["cutPlan","offcuts","svg"] per mantenere tutto e aggiungere svg.
Opaque JSON of your own — an ERP article number, an order-line id, a customer ref. Echoed VERBATIM on every placed piece of that part. The optimizer never reads it, so it can never change a layout.
stock[].meta
object
The same on a stock row: echoed on every sheet / rod / nested sheet cut from it, so the plan reconciles with your system without a lookup table.
L'opzione effort bilancia il tempo di calcolo con l'utilizzo. Ecco questo compromesso, misurato su un lavoro impegnativo — ogni cifra proviene dal packer reale.
Sulla maggior parte dei lavori (più piccoli) i due sono identici; il divario si apre solo su lavori grandi come questo. balanced è il valore predefinito e non è mai più denso di quanto fast possa raggiungere.
Ed è comunque veloce: anche i lavori di produzione più grandi — 2.000 pezzi e oltre — si risolvono in pochi secondi con il motore predefinito, ben entro il budget di tempo dell'API.
cutLines vs sawPasses — cutLines unisce i tagli collineari (una sola regolazione della guida); sawPasses conta ogni singolo passaggio. Due misure oneste dello stesso piano, non la pretesa di coincidere con il conteggio di un concorrente.
guillotineValid / cutPlan — Quando uno schema non è tagliabile da bordo a bordo, guillotineValid è false e cutPlan è null. È un’informazione reale — quel piano non è realizzabile su una sezionatrice — non un errore.
unplaced + warnings — Un lavoro non soddisfacibile restituisce 200 con i pezzi elencati in unplaced e una nota in warnings. Un piano su cui puoi agire vale più di un codice di stato.
edgeBanding — Quando un pezzo porta edgeBanding, la risposta aggiunge un blocco edgeBanding: i metri lineari che ogni riferimento di tipo consuma, per pezzo e come totale dell’ordine. È geometria esatta, senza margine di scarto — l’officina aggiunge il suo — e presuppone input in millimetri (÷1000 per i metri). La chiave manca del tutto in un lavoro senza bordatura.
materials / unmatchedMaterials — Quando un pezzo o del materiale porta material, la risposta aggiunge materials (un riepilogo per materiale — material, sheetCount/rodCount, yieldPct, placed, total, totalPrice) e unmatchedMaterials (la domanda il cui materiale non ha stock corrispondente). Nel legno il materiale viaggia invece per ogni sezione. Entrambe le chiavi mancano in un lavoro senza materiale, che resta byte-identico.
Campi della risposta
I nomi e i tipi dei campi sono il contratto, quindi le tabelle qui sotto restano in inglese in tutte le lingue — un nome di campo tradotto documenterebbe un’API che non esiste.
POST /v1/optimize/2d — top level
engine
string
Which engine ran: "heuristic", "balanced" or "max".
engineVersion
string
Algorithm identity — package version + a content hash of the algorithm source. Moves automatically on any packer change, and independently per engine.
contractVersion
string
Shape version, matching the /v1/ in the path. Currently "1".
deterministic
boolean
Always true. Present so a client can assert the guarantee it relies on.
sheets
array
One entry per sheet used, in cutting order.
metrics
object
Aggregate numbers for the whole job.
cutPlan
array | null
The sawing plan. null when guillotineValid is false; absent when excluded via include.
unplaced
array
Parts that did not fit, aggregated by name + size. Empty when everything fitted.
warnings
string[]
Free-text notes about the plan. Do not parse — branch on unplaced, guillotineValid and metrics.
guillotineValid
boolean
True when the layout is producible with edge-to-edge cuts, i.e. on a panel saw.
edgeBanding
object
Linear metres of edge banding, grouped by type reference. ABSENT unless a part requested banding via parts[].edgeBanding. 2D only.
materials
array
Per-material rollup. ABSENT unless a part or stock row carried material — a material-free job stays byte-identical. (OPEN-256)
unmatchedMaterials
array
Demand whose material has no matching stock at all. ABSENT when it does not happen. A missing-material report, not a did-not-fit one.
svg
string
Inline SVG of the 2D layout (self-contained, no external refs). Present ONLY when include contains "svg". 2D only. (OPEN-223)
csv
string
Inline CSV cut list. Present ONLY when include contains "csv".
dxf
string
Inline DXF (R12/AC1009) on layers STOCK/PARTS/LABELS. Present ONLY when include contains "dxf".
timing.solveMs
number
Milliseconds inside the packer, 2 decimals. Excludes parsing, auth and queueing.
sheets[]
w, h
number
FULL stock dimensions, trim included — not the usable area.
price
number | null
Price of the stock row this sheet came from, or null if none was given.
parts
array
Placements on this sheet.
offcuts
array
Usable leftover rectangles, filtered by options.minOffcut. Absent (not empty) when excluded via include.
meta
object
Present only when the stock row carried meta — echoed verbatim from stock[].meta. (OPEN-224)
sheets[].parts[]
name
string
The requested name, or the generated default "Part <row>".
x, y
number
Top-left corner of the part, from the top-left corner of the sheet.
w, h
number
Dimensions AS PLACED — already swapped when rotated is true. No client-side swap needed.
rotated
boolean
True if the part was turned 90° from the requested w×h. Informational only.
meta
object
Present only when the part carried meta — echoed verbatim from parts[].meta on every placed piece. (OPEN-224)
sheets[].offcuts[]
x, y, w, h
number
A leftover rectangle, in the same coordinate system as the placements.
metrics (2D)
sheetCount
integer
Sheets used. Equals sheets.length.
yieldPct
number
Placed part area ÷ total FULL sheet area × 100, 2 decimals. Trim and kerf count as waste.
placed
integer
Pieces placed, after qty expansion.
total
integer
Pieces requested, after qty expansion.
cutLines
integer
Collinear cuts merged: same axis, same coordinate, same stage counted once — one fence setting.
sawPasses
integer
Every cut, one per strip crossed — how many separate passes the saw makes.
cutLength
number
Total distance sawn, 3 decimals. Same under either counting convention. In your unit.
totalPrice
number
Sum of the used sheets’ prices, 2 decimals. 0 when no stock row carried a price.
unplaced[] (2D)
name
string
The part name.
w, h
number
As requested.
qty
integer
How many of this part could not be placed.
edgeBanding (2D — present only when a part is banded)
totalMeters
number
Order-wide total across every banded edge, 3 decimals. ⚠️ Assumes mm input: top/bottom edges run the part width, left/right the height, ×qty, ÷1000. In another unit it is your raw edge length ÷ 1000.
byType[]
{ reference, meters }
Order total split by the caller-supplied type reference, sorted by reference.
byPart[]
{ name, meters, byType }
One entry per requested part row that has any banded edge; its byType splits that part’s metres by reference.
cutPlan[]
sheet
integer
0-based index into sheets (2D) or rods (1D). Named sheet in both.
step
integer
1-based order within THIS sheet — it restarts at 1 on every sheet.
axis
"h" | "v"
"h": blade travels along x, separating top from bottom. "v": along y, separating left from right.
pos
number
The blade’s LOW-COORDINATE edge — the y value for "h", the x value for "v" — not its centre line. The kerf occupies pos to pos + kerf.
length
number
Distance the blade travels on this cut: the extent of the region crossed. Always 0 in 1D.
stage
integer
1-based machine pass. Increments only when the axis flips relative to the parent cut.
2D · edgeBanding
Quando un pezzo porta edgeBanding, la risposta aggiunge un blocco edgeBanding: i metri lineari che ogni riferimento di tipo consuma, per pezzo e come totale dell’ordine. È geometria esatta, senza margine di scarto — l’officina aggiunge il suo — e presuppone input in millimetri (÷1000 per i metri). La chiave manca del tutto in un lavoro senza bordatura.
Quando un pezzo o del materiale porta material, la risposta aggiunge materials (un riepilogo per materiale — material, sheetCount/rodCount, yieldPct, placed, total, totalPrice) e unmatchedMaterials (la domanda il cui materiale non ha stock corrispondente). Nel legno il materiale viaggia invece per ogni sezione. Entrambe le chiavi mancano in un lavoro senza materiale, che resta byte-identico.
The tag exactly as you sent it. Free text, matched exactly.
sheetCount | rodCount
integer
Stock consumed for this material — sheetCount on 2D and nest, rodCount on 1D.
yieldPct | density
number
This material’s own fill — yieldPct on the rectangular modes, density on nest (a polygon fill, not comparable to yieldPct).
placed, total
integer
Pieces placed and requested for this material, after qty expansion.
totalPrice
number
Sum of the prices of the stock used for this material.
unmatchedMaterials[] — demand with no matching stock
material
string
The tag that has no stock of its own anywhere in the request.
parts
array
The demand rows in that material, in the mode’s unplaced shape: name, w, h, qty on 2D; name, length, qty on 1D; name, qty on nest.
cutPlan — che cosa significa fisicamente uno step
Uno step è un singolo movimento della lama e l’elenco segue l’ordine in cui puoi realmente segare: un taglio padre prima dei tagli interni al pezzo che ha prodotto, perché non puoi troncare una striscia prima di averla staccata. axis "h" significa che la lama si muove lungo x e separa l’alto dal basso; axis "v" significa che si muove lungo y e separa sinistra da destra. pos è il bordo della lama con la COORDINATA PIÙ BASSA — il valore y per "h", il valore x per "v" — non la sua linea centrale: il kerf occupa da pos a pos + kerf, quindi la lama asporta materiale nella direzione in cui cresce la coordinata, cioè verso il basso per "h" e verso destra per "v". Il materiale sul lato della coordinata più bassa — sopra la linea per "h", alla sua sinistra per "v" — è il pezzo che quel taglio libera. length indica quanto si muove la lama in quel singolo taglio: l’estensione della regione che attraversa, non la larghezza dell’intero pannello.
stage è un passaggio macchina. Parte da 1 e aumenta solo quando axis si inverte rispetto al taglio padre: sezionare un pannello in sei strisce è un unico passaggio, troncarle è il passaggio successivo. È il senso che la sezionatrice dà al “taglio a tre fasi”, non la profondità dell’albero di taglio, ed è ciò che maxCutStages vincola. sheet è un indice in sheets che parte da 0, e step riparte da 1 su ogni sheet invece di proseguire per tutto il lavoro.
cutPlan è null — non assente, non vuoto — ogni volta che guillotineValid è false: uno schema che non si può tagliare da bordo a bordo non ha alcuna sequenza di taglio da restituire. Manca invece del tutto dal payload se lo hai escluso da include.
I pezzi usano length (più qty, name, priority); il materiale usa length, qty e price. Sia i pezzi sia il materiale accettano anche un tag material facoltativo (il materiale anche priority) — material limita un pezzo allo stock dello stesso materiale, e la risposta aggiunge allora materials e unmatchedMaterials come in 2D. Le opzioni sono kerf, tolerance, trim.start / trim.end, minimizeCost, respectStock e minOffcut. La risposta restituisce rods invece di sheets, ognuna con i propri parts, la lunghezza residua e gli offcuts.
rods sostituisce sheets e non esiste guillotineValid, perché un taglio lineare è sempre realizzabile. pos di ogni pezzo è la distanza della sua estremità più vicina dal capo della barra che trim.start rifila, quindi il primo pezzo inizia esattamente a trim.start e ogni pos successivo aggiunge un kerf. remaining è lo sfrido UTILIZZABILE: il kerf del taglio che lo stacca dall’ultimo pezzo è già dedotto, quindi è la lunghezza recuperabile, non il vuoto grezzo. Viene riportato su ogni rod anche quando è inferiore a minOffcut — minOffcut filtra soltanto l’array offcuts, che contiene al massimo una voce. Nel piano di taglio, sheet è l’indice della barra, axis è sempre "v", stage è sempre 1 e length è sempre 0: il troncaggio di una barra non ha alcuna distanza percorsa da riportare, ed è anche per questo che le metrics 1D contengono cuts ma non cutLength.
Il legno ha un’identità che una semplice barra non ha: un pezzo 50×150 non può uscire da materiale 50×100, per quanta lunghezza avanzi. Pezzi e materiale portano quindi sw e sh, i due lati della sezione, in qualsiasi ordine — 50×100 e 100×50 sono la stessa trave girata e vengono trattate come un’unica sezione. Il lavoro viene diviso per sezione, ogni sezione è abbinata al proprio materiale e risolta per conto suo, e una sola chiamata restituisce tutto. Anche i pezzi e il materiale accettano un tag material facoltativo (il materiale anche priority): con esso, un rovere 50×100 e un pino 50×100 diventano due sezioni separate, e ogni sezione porta il suo materiale. Le opzioni sono le stesse di 1D.
sections sostituisce rods al livello superiore: ogni voce è una sezione con i propri rods (identici nella forma a 1D) e le proprie metrics, così i valori per materiale ci sono già senza ricalcolarli. unmatched non ha equivalente in 1D — è la domanda per la cui sezione non hai fornito alcun materiale, un problema diverso da unplaced (pezzi che avevano materiale e non sono entrati) e con una soluzione diversa, per questo i due non si mescolano mai. metrics.total conta ogni pezzo richiesto, compresi quelli in unmatched. Nel piano di taglio ogni passo indica anche la sua section, e sheet è l’indice della barra ALL’INTERNO di quella sezione, non un contatore globale del lavoro.
POST /v1/optimize/wood — top level (what differs from 1D)
sections
array
Replaces rods at the top level: one entry per cross-section, each matched to its own stock and solved on its own.
unmatched
array
Demand whose cross-section has NO stock at all. Deliberately separate from unplaced (had stock, did not fit) — the fix differs, so the two are never mixed.
unplaced
array
Did-not-fit demand, aggregated across the sections that DID have stock. Same shape as 1D.
metrics
object
Job-wide totals across every section (below).
cutPlan
array | null
As 1D, except every step also carries section, and sheet is the rod index WITHIN that section, not a job-wide counter.
csv, dxf
string
Inline export, present only when include names the token. svg is 2D-only — a wood request asking for it gets a warning instead.
sections[]
section
string
Normalised cross-section key, e.g. "50x100" — short side first, so 50×100 and 100×50 are one section.
sw, sh
number
The two cross-section sides: sw the SHORT one, sh the long one, whatever order they arrived in.
stockName
string | null
The name of the stock row this section was matched to, or null when that row carried none.
material
string
Present only when the section carries a material — an oak 50×100 and a pine 50×100 are two sections. (OPEN-256)
rods
array
Identical in shape to the 1D rods[] above, meta and offcuts included.
metrics
object
This section’s own totals: rodCount, yieldPct, placed, total, cuts, totalPrice — so the per-section figures need no recomputation.
unplaced
array
This section’s parts that had stock and still did not fit.
metrics (wood)
sectionCount
integer
Cross-sections solved. Equals sections.length.
rodCount
integer
Bars used across every section.
yieldPct
number
Placed length ÷ total FULL bar length × 100 over the whole job, 2 decimals.
placed
integer
Pieces placed, after qty expansion.
total
integer
Pieces REQUESTED, after qty expansion — unmatched ones included.
cuts
integer
Crosscuts across every used bar.
totalPrice
number
Sum of the used bars’ prices, 2 decimals.
toleranceAcceptedCount
integer
Pieces that fitted only because options.tolerance allowed an overshoot.
unmatched[]
section
string
The cross-section key nothing in stock matched.
sw, sh
number
That cross-section’s two sides.
material
string
Present when the cross-section DOES exist in stock but only in a different material. (OPEN-256)
parts
array
The demand rows in that section, in the 1D unplaced shape: name, length, qty.
qty
integer
Total pieces in this section that had no stock at all.
Le tre modalità qui sopra dispongono rettangoli. POST /v1/optimize/nest dispone POLIGONI ARBITRARI: un pezzo è un contorno (polygon, con holes interni facoltativi), non una larghezza×altezza, quindi i pezzi si incastrano nelle rientranze concave gli uni degli altri e l’aria delle tacche che un bounding box spreca viene recuperata — su un lavoro rappresentativo, 6 pannelli dove gli stessi pezzi per bounding box ne richiedono 9. È una classe di algoritmo diversa (un motore di collisione geometrico, non il packer guillotine), per il taglio a laser, plasma e getto d’acqua. Con esso arrivano due cose che l’API rettangolare non può esprimere: le zone di esclusione per pannello (stock[].exclusions — un difetto, l’ingombro di un morsetto, un’area pre-stampata; una zona con quality 0 è una regione vietata a qualsiasi pezzo) e gli holes true-shape. La partizione per material e il passaggio di meta funzionano come ovunque. L’esempio qui sotto è una chiamata reale acquisita — otto pezzi su un unico pannello con un angolo danneggiato escluso.
POST /v1/optimize/nest — request (top level)
parts
array
One or more NestPart (see below). Required.
stock
array
One or more NestStock sheet types (see below). Required.
options
object
Solve options (see below). Optional.
engine
string
"lbf" (default, single-pass, instant) or "sparrow" (advertised for a future higher-density build; currently served by lbf with a warning).
include
string[]
Additive inline export of the ACHIEVED nest: "svg" a self-contained styled drawing (one titled band per sheet, parts as filled polygons with holes, exclusion zones hatched), "dxf" an R12/AC1009 document on layers STOCK/PARTS/HOLES/ZONES/LABELS. Unit-agnostic, exactly like the request coordinates. The geometry in sheets[] is returned either way.
parts[] (NestPart)
polygon
number[][]
The part outline: an ordered ring of ≥3 [x, y] vertices. Given closed (first == last) or open; winding order is not required — it is oriented internally. Required UNLESS the row carries source.
source
object
OPEN-262 — read the outline out of an SVG or DXF FILE instead of listing coordinates (fields below). EXACTLY ONE of polygon / source: both, or neither, is a 400.
holes
number[][][]
Optional interior holes — a part with a cut-out. Each hole is a ring like polygon. NOT accepted alongside source: the file already carries its own holes, and two sources of truth for one geometry is not a thing we resolve silently.
qty
integer
Copies to place. Default 1.
allowedRotations
number[] | "continuous"
Allowed rotations in DEGREES (e.g. [0,90,180,270]). Omit or "continuous" for free rotation.
minQuality
integer
The part may only be placed where sheet quality ≥ this. Default 1 → it avoids every quality-0 exclusion zone. Raise it to keep the part off inferior-but-not-forbidden zones too.
priority
boolean
Must-cut: wins sheet space when stock is capped (options.respectStock).
material
string
OPEN-256 — a part of material X nests only on material-X sheets; the job is partitioned by material.
name
string
Optional label, echoed on every placement. Defaults to "Part <1-based row index>".
meta
object
OPEN-224 — opaque JSON (your ERP ids), echoed verbatim on every placed copy. Never affects the layout.
stock[] (NestStock)
w, h
number
Rectangular sheet size. Give w & h OR polygon, not both.
Default 1. A HARD cap only when options.respectStock is true.
price
number
Per-sheet price, for cost mode + totalPrice.
exclusions
object[]
OPEN-233 — per-sheet excluded / inferior zones. Each: { polygon: number[][], quality?: integer }. quality 0 (the default) = a hard no-go region for any part; a higher quality only excludes parts whose minQuality demands at least that. This is what the rectangular API cannot express.
material
string
OPEN-256 — this sheet serves only material-matching parts.
meta
object
OPEN-224 — echoed on every sheet cut from this stock row.
options (NestOptions)
minSeparation
number
Minimum clearance between parts and between a part and any hazard (sheet edge / exclusion zone). Use for kerf / beam / torch width. Default 0.
seed
integer
Determinism: a fixed seed → a reproducible layout. Omit and the server pins a fixed default so the response stays reproducible.
minimizeCost
boolean
Rank plans by total sheet price rather than sheet count.
respectStock
boolean
Treat each stock qty as a hard cap.
simplifyTolerance
number
Polygon simplification tolerance (max area deviation as a fraction). Speeds up dense DXF outlines with hundreds of vertices. 0 disables.
timeBudgetMs
integer
Wall-clock budget for the metaheuristic (engine "sparrow"). Ignored by "lbf" (single-pass).
Ogni voce in sheets è un pannello utilizzato; un pezzo posizionato porta la trasformazione rigida (rotation in gradi, poi traslazione x/y), NON un poligono ri-emesso — ruota il tuo contorno di input di rotation attorno alla sua origine e aggiungi (x, y) per ricostruire esattamente il posizionamento. rotation può essere negativo; la ricostruzione è esatta indipendentemente dal segno. ⚠️ density è l’area del POLIGONO posizionato sull’area del pannello utilizzato — il riempimento onesto, con le rientranze concave contate come vuote — e NON è confrontabile con lo yieldPct di un packer rettangolare (che conta ogni bounding box come pieno, quindi risulta più alto per un risultato peggiore); la metrica confrontabile tra i due è sheetCount sugli stessi pezzi. Lo schema è deterministico: imposta options.seed per riprodurlo. exclusions viene riportato su ogni pannello per il rendering.
POST /v1/optimize/nest — top level
engine
string
Which nesting engine ran: "lbf" or "sparrow".
engineVersion
string
The nesting engine's algorithm identity (jagua-rs revision + build hash). Versions independently of the rectangular engines.
contractVersion
string
Nest contract version, currently "1". Versions independently of the /v1/ rectangular contract — it is a different path and engine family.
deterministic
boolean
Always true — guaranteed by the pinned seed.
sheets
array
One entry per used sheet.
metrics
object
Job totals (see below).
unplaced
array
Demand that could not be placed — a plan-plus-warning, not an error.
warnings
string[]
e.g. an engine substitution ("sparrow" served by "lbf"), unplaced parts, or a material with no matching stock.
materials
array
OPEN-256 per-material rollup — present only when parts/stock carry material.
unmatchedMaterials
array
Parts whose material has no matching stock — present only when it happens.
imported
object
OPEN-262 — what the request’s source files contributed (fields above). ABSENT for a coordinates-only job, which is what keeps such a request byte-identical to before the feature existed.
svg, dxf
string
The achieved nest as an inline drawing — present ONLY when include contains that token. svg is self-contained (no external refs); dxf is R12/AC1009.
timing
object
{ solveMs: number } — the solve time; environment-dependent.
sheets[] (nest)
w, h
number
Present for rectangular sheets.
polygon
number[][]
Present for arbitrary-outline sheets instead of w/h.
price
number | null
The stock row's price, or null.
density
number
This sheet's fill = placed polygon area / sheet area.
parts
array
Placements on this sheet (see below).
exclusions
object[]
The zones that applied to this sheet, echoed for rendering.
material
string
Present when the sheet carried a material (OPEN-256).
meta
object
Echoed from the stock row's meta (OPEN-224).
sheets[].parts[] (nest — placed)
name
string
The requested name, or the generated default.
sheet
integer
0-based index into sheets.
x, y
number
Translation, applied AFTER rotation about the part's origin.
rotation
number
⚠️ Degrees, applied about the part's own origin FIRST. A placed part carries this rigid transform, NOT a re-emitted polygon: rotate your input outline by rotation, then add (x, y) to reconstruct the placement exactly. May be negative; the reconstruction is exact regardless of sign.
material
string
The material this copy nested from (OPEN-256).
meta
object
The part's opaque passthrough (OPEN-224).
metrics (nest)
sheetCount
integer
Sheets used. Equals sheets.length. ⚠️ The honest, cross-comparable metric between nesting and the rectangular modes.
density
number
⚠️ Placed POLYGON area / used sheet area — the honest fill (concave pockets count as empty). NOT comparable to a rectangular packer's yieldPct, which counts each bounding box as solid and so reads higher for a worse result.
placed
integer
Part copies placed, after qty expansion.
total
integer
Part copies requested, after qty expansion.
totalPrice
number
Sum of used sheet prices.
unplaced[] (nest)
name
string
The part name.
qty
integer
How many copies could not be placed.
rods[]
length
number
FULL bar length as supplied in stock.
price
number | null
Price of the stock row, or null.
remaining
number
USABLE off-cut left on this bar, 3 decimals — the kerf of the cut that frees it from the last piece is already deducted (OPEN-257), so it is the reclaimable length, not the raw gap. Reported even when below minOffcut.
parts
array
Placements, in cutting order along the bar.
offcuts
number[]
At most one entry: [remaining] (the kerf-corrected usable off-cut, OPEN-257) when it is > 0 and ≥ minOffcut, else []. Absent when excluded via include.
meta
object
Present only when the stock row carried meta — echoed from stock[].meta (OPEN-224). Applies to 1D and wood rods.
rods[].parts[]
name
string
The requested name, or the generated default.
pos
number
Offset of the part’s NEAR end from the bar end that trim.start trims.
length
number
The part length, as requested.
meta
object
Present only when the part carried meta — echoed from parts[].meta (OPEN-224).
metrics (1D)
rodCount
integer
Bars used. Equals rods.length.
yieldPct
number
Placed length ÷ total FULL bar length × 100, 2 decimals. Trim and kerf count as waste.
placed
integer
Pieces placed, after qty expansion.
total
integer
Pieces requested, after qty expansion.
cuts
integer
Total crosscuts across all used bars — one per placed piece.
totalPrice
number
Sum of used bar prices, 2 decimals.
toleranceAcceptedCount
integer
Pieces that fitted only because options.tolerance allowed an overshoot.
unplaced[] (1D)
name
string
The part name.
length
number
As requested.
qty
integer
How many could not be placed.
Pezzi da un file (SVG · DXF)
Un pezzo non deve per forza arrivare come coordinate. Metti un documento SVG o DXF in parts[].source e il server ne estrae il contorno — e i suoi fori — con lo stesso parser che l'app CutOptim usa quando trascini un disegno sulla sua modalità Nesting. Il file sostituisce SOLO la geometria: qty, material, allowedRotations, minQuality, priority e meta si comportano esattamente come su un pezzo polygon, quindi una libreria di pezzi che esiste già come file CAD non richiede un tuo appiattitore di curve e archi. Una source descrive UN pezzo; un disegno che contiene più componenti separati produce un 400 che rimanda all'endpoint di import qui sotto. La risposta porta allora un blocco imported: quante righe provengono da un file, quanti vertici hanno prodotto e quali unità quei file dichiaravano — riportate, mai applicate, perché questa API non converte nulla.
Nulla viene memorizzato. I byte esistono solo come corpo della richiesta, vengono analizzati in memoria e spariscono quando la risposta è scritta: nessun disco, nessun database, nessun file temporaneo, nessuna riga di log. Non c'è nulla da cancellare dopo e nulla resta — la stessa assenza di stato che ogni altro endpoint mantiene.
I nomi e i tipi dei campi sono il contratto, quindi le tabelle qui sotto restano in inglese in tutte le lingue — un nome di campo tradotto documenterebbe un’API che non esiste.
The file TEXT — not base64, not a URL, not a multipart upload. Both formats are text documents, so they travel inside the JSON body like any other field. At most 4,000,000 characters per file and 8,000,000 across one request; over either is a 413.
format
"svg" | "dxf"
Omit and the server sniffs the content (an <svg tag ⇒ svg, otherwise dxf). An explicit value ALWAYS wins — including over a filename whose extension disagrees, which is the case worth setting it for.
filename
string
Used for format detection AND as the part name when the row has no name of its own (extension stripped). It never touches a filesystem — there is no file on our side to name.
flattenTolerance
number
Curve and arc flattening tolerance, in the FILE’s own coordinate unit. Default 0.2. Larger = fewer vertices; this is the lever when a dense outline trips the 2,000-vertex per-ring cap.
imported — what the files contributed (response, absent without a source)
parts
integer
Part ROWS whose geometry came from a file.
vertices
integer
Total vertices those files produced after flattening, outlines and holes together — the number to watch against the per-ring cap.
units
string[]
⚠️ Units the files DECLARED (a DXF $INSUNITS), not units we applied. Empty when none declared one. More than one entry also raises a warning: mixing a millimetre drawing with an inch one produces a plan that validates and is physically wrong, and the server must not "fix" that by converting — no field in this API asserts a unit.
POST /v1/import/nest — un file, tutti i contorni
Quando un singolo disegno contiene più pezzi diversi, importalo prima: questo endpoint restituisce ogni contorno chiuso che contiene, dal più grande, esattamente nella forma che una riga parts[] si aspetta. Incolla quelli che ti servono, aggiungi il tuo qty e material e invia il risultato a /v1/optimize/nest. È anche il modo per vedere cosa c'è in un file prima di spendere una risoluzione. Richiede una chiave — appiattire geometria arbitraria è lavoro di CPU reale, e la CPU anonima è un cattivo affare — ma non riserva nulla: la tua quota resta intatta e non tornano header di rate limit, esattamente come quando interroghi un job.
The file text. Required. Same 4,000,000-character cap as parts[].source.
format
"svg" | "dxf"
Omit to sniff; an explicit value wins over the filename extension.
filename
string
Used for detection and to NAME the results: a file with one outline keeps the bare name, several are numbered "<name> 1", "<name> 2", …
flattenTolerance
number
As on parts[].source. Default 0.2.
POST /v1/import/nest — response
format
"svg" | "dxf"
The parser that actually ran — the useful bit when you let the server sniff.
parts[]
{ name, polygon, holes? }
One entry per closed outline, LARGEST FIRST. Each is already in the shape a parts[] row wants: paste it in and add your own qty / material / allowedRotations. Deterministic — the same bytes always give the same vertices in the same order.
vertices
integer
Total vertices after flattening. Compare it against the 2,000-per-ring cap before you build a large request.
sourceUnit
"mm" | "in"
A DXF $INSUNITS declaration, when the file carries one. REPORTED, NEVER APPLIED. Absent for SVG (the format has no unit) and for a DXF that declares none.
warnings
string[]
What the parser could not honour — an SVG transform= attribute (CAD part exports are flat, so we do not apply them), or geometry that never closed into a contour. Empty means the file was read whole.
engineEnabled
boolean
Whether THIS deployment can also SOLVE a nest. Importing is pure parsing and works everywhere; where the nesting engine is not built in, /v1/optimize/nest answers 503 and this flag says so up front. Same field /v1/validate/nest carries.
contractVersion
string
Nest contract version. Currently "1".
Motori
heuristic(predefinito) — l’algoritmo di posizionamento guillotine multi-strategia. Utilizzo massimo, ogni schema tagliabile alla sega, sempre un cutPlan completo.
balanced — un algoritmo di posizionamento MaxRects a nesting libero. Molto più rapido sui lavori grandi (misurato ~25× su 2.000 pezzi) a fronte di una piccola perdita di utilizzo, e i suoi schemi spesso non sono guillotine (guillotineValid: false, cutPlan: null). Non modella tolerance, minimizeCost, grainGroup, maxCutStages o minimizeRotations — se ne imposti uno, un avviso ti segnala che è stato ignorato.
max — il livello di ricerca ad albero asincrono (solo 2D): raggiunge l’ottimo dimostrato su molti più lavori, al costo di secondi-fino-a-un-minuto per risoluzione. Sempre deterministico e tagliabile a guillotina. Non restituisce un piano direttamente — vedi Lavori asincroni qui sotto. Modella UN SOLO formato di stock a piena dimensione con disponibilità illimitata e uno schema a guillotina fisso a 3 fasi: una seconda riga di stock, trim, respectStock, material o grainGroup vengono rifiutati con 400 prima che una chiamata sia riservata; tolerance, minimizeCost, maxCutStages, minimizeRotations, firstCut ed effort passano ma vengono ignorati con un avviso, e un risultato max non riporta ritagli. Manda quei lavori al motore heuristic.
Lavori asincroni (engine = max)
Una risoluzione max richiede da secondi a un minuto, quindi POST /v1/optimize/2d con engine:"max" non restituisce un piano — restituisce 202 Accepted con un jobId, e la chiamata viene conteggiata all’invio. Interroga GET /v1/jobs/{id} finché status è "succeeded" (result contiene la stessa risposta 2D che restituisce una risoluzione sincrona) oppure "failed" (error contiene il messaggio). L’interrogazione non consuma quota; vedi solo i tuoi lavori. Dove il livello non è abilitato in un deployment, engine:"max" fallisce in modo chiuso (fail closed) con 503.
field
type
meaning
jobId
string
The 202 body's id. Poll GET /v1/jobs/{id}.
status
string
queued → running → succeeded | failed.
mode · engine
string
Always "2d" and "max".
pollAfterMs
integer
202 only — suggested delay before the first poll.
result
object
Present once succeeded — the same shape as a synchronous 2D response.
error
string
Present once failed — the reason.
quota
object
202 only — reserved, used and limit: the one call metered at submission, and where the ACCOUNT stands this month.
createdAt
string
Poll only — when the job was submitted (ISO-8601).
finishedAt
string | null
Poll only — when the worker finished; null while queued or running.
Determinismo e versionamento
Ogni risposta contiene engineVersion. L’algoritmo è deterministico, quindi migliorarlo cambia l’output a parità di input — il che è una breaking change se usi la cache. Fissa il comportamento inviando engine in modo esplicito e tenendo d’occhio engineVersion; la versione nel path /v1/ cambia solo se cambia la struttura della risposta.
Ogni motore ha un versionamento indipendente, quindi una modifica a uno non sposta mai la versione dell’altro.
Errori
400
invalid_request
Errore di schema. details.path indica il campo che ha causato il problema.
401
unauthorized
Chiave API mancante o sconosciuta.
402
quota_exceeded
Quota mensile raggiunta. Retry-After indica i secondi che mancano al rinnovo del mese.
403
key_revoked
La chiave esiste ma non può essere usata: è stata revocata, oppure l’abbonamento Engine API dell’account non è più attivo. Il campo message dice quale dei due.
404
not_found
Route inesistente — è anche ciò che ottieni per un path corretto con il metodo sbagliato.
413
too_large
Input oltre un limite (vedi Limiti).
429
busy
Capacità momentaneamente esaurita. Retry-After in secondi — non viene mai conteggiato nella tua quota.
500
internal
Errore inatteso, oppure il backend di autenticazione non è raggiungibile (in tal caso le richieste vengono rifiutate — fail closed).
503
service_unavailable
Un motore richiesto non può essere servito ora — il motore di nesting o il motore max asincrono. Per max le cause sono due, e il campo message dice quale: il livello non è incluso in questo deployment, oppure è incluso ma il worker che risolve i lavori non risponde. Fail-closed prima che una chiamata sia riservata, quindi non costa mai nulla.
504
solve_timeout
La risoluzione ha superato il proprio limite rigido di tempo. Sui percorsi rettangolari lo impone il proxy; su /v1/optimize/nest è il motore a imporre il proprio budget, più breve, e risponde con solve_timeout nella busta normale.
Corpo dell’errore
Ogni errore prodotto dal motore stesso usa la stessa struttura. Basa la logica condizionale su error, che è un codice stabile; mai su message, la cui formulazione può cambiare da una release all’altra. details è presente su invalid_request, dove path indica il campo che ha causato il problema, e su too_large, dove max e got riportano il limite e ciò che hai inviato.
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
{
"error": "invalid_request",
"message": "parts[0].h: must be greater than 0",
"details": { "path": "parts[0].h" }
}
Sia 402 sia 429 riportano un header Retry-After in secondi. Su 402 conta alla rovescia fino all’azzeramento della quota alle 00:00 UTC del 1° del mese successivo; su 429 è una breve attesa prima di riprovare, e un 429 non consuma mai quota — la chiamata riservata viene restituita.
Un errore di routing risponde con not_found, un codice volutamente fuori dall’elenco qui sopra perché lo produce il router e non il contratto dell’API. Ottieni 404 e non 405 quando il path è corretto ma il metodo è sbagliato: tutti e quattro gli endpoint di ottimizzazione accettano solo POST.
Sui percorsi rettangolari il 504 arriva dal reverse proxy, non dal motore, quindi il suo corpo è quello del proxy e non questa struttura JSON; entro i limiti di input indicati sotto non dovrebbe mai verificarsi. /v1/optimize/nest è l’eccezione: quella risoluzione è un sottoprocesso con un proprio budget, tenuto deliberatamente sotto il limite del proxy, quindi lì un timeout usa questa struttura, con il codice solve_timeout.
Header del rate limit
Una chiamata di ottimizzazione riuscita riporta X-RateLimit-Limit (il limite mensile dell’ACCOUNT: tutte le chiavi dell’account ne condividono uno solo) e X-RateLimit-Remaining (le chiamate rimaste all’account per il mese in corso, già al netto di questa). Vengono inviati solo dagli endpoint di ottimizzazione: il contatore viene riservato come parte dell’autorizzazione di una risoluzione, quindi /v1/usage e /v1/health non hanno nulla da riportare.
X-RateLimit-Limit
integer
The ACCOUNT’s monthly quota — the same number GET /v1/usage returns as limit, not a per-key cap (049).
X-RateLimit-Remaining
integer
Calls left this month on the ACCOUNT, after this one.
Sola lettura: non consuma una chiamata e non invia header di rate limit. ⚠️ used e limit descrivono l’ACCOUNT, non la chiave con cui hai chiamato: ogni chiave attiva dell’account attinge a un unico monte condiviso, quindi creare più chiavi non crea più quota. used conta il mese solare UTC in corso su tutte, remaining è limit meno used e non va mai sotto zero, periodEnd è il giorno di azzeramento come semplice data YYYY-MM-DD e keyPrefix è il prefisso non segreto della chiave usata. La chiave stessa non viene mai restituita da alcun endpoint — se ne memorizza solo l’hash, quindi una chiave persa si sostituisce, non si recupera.
plan
string
Tier slug frozen onto the key when it was created.
used
integer
Calls counted in the current UTC calendar month across EVERY key on the ACCOUNT — revoked keys included, so revoking a key cannot un-spend what it spent (053).
limit
integer
The ACCOUNT’s cap: the largest monthly quota frozen onto any of its ACTIVE keys (049). Not a per-key allowance — more keys do not add quota.
remaining
integer
limit − used, never negative.
periodEnd
string
Reset day as YYYY-MM-DD — a date, not a timestamp.
Nessuna chiave, nessuna quota, nessun database. Non tocca deliberatamente nulla che abbia stato, così un guasto nell’archivio delle chiavi non può far sembrare il servizio morto a un orchestratore. engines elenca gli id che questo deployment accetta in engine, mentre engineVersion è la versione del motore predefinito.
status
string
Always "healthy" when the process answers.
service
string
Always "cutoptim-engine".
contractVersion
string
Shape version. Currently "1".
engineVersion
string
The DEFAULT engine’s version, not a per-engine list.
engines
string[]
SYNCHRONOUS engine ids this deployment accepts in engine — ["heuristic","balanced"]. The async max tier is reported separately in maxEngines, never here.
modes
string[]
Optimize paths this deployment serves: "2d", "1d", "wood", plus "nest" only where the nesting engine is built in. Endpoint discovery without reading this page.
nestEngines
string[]
Nesting engine ids this deployment can serve — ["lbf"] on the production API, [] where the Rust nesting stage is not built in.
maxEngines
string[]
The async tree-search tier — ["max"] where it is enabled, [] otherwise. Health never advertises a capability it cannot serve.
uptimeSec
integer
Whole seconds since process start.
Limiti
2,000 pezzi per richiesta (quantità totale, dopo l’espansione di qty)
50 righe di materiale · corpo della richiesta fino a 1 MB
10 chiavi attive per account — condividono UN’UNICA quota mensile: le chiavi separano ambienti e integrazioni, non aumentano il monte
10 MB di corpo della richiesta sui due percorsi nest che possono trasportare un disegno (/v1/optimize/nest e /v1/import/nest); un file source al massimo 4.000.000 di caratteri, 8.000.000 per richiesta
la concorrenza è limitata lato server — un burst riceve 429, mai una coda lenta. Gli endpoint validate senza chiave hanno inoltre un tetto per indirizzo (429 con Retry-After); con una chiave non si viene mai limitati così. Un account può tenere 5 lavori max in queued/running contemporaneamente.
Specifica OpenAPI
Un documento OpenAPI 3.1 leggibile dalle macchine descrive tutti e dodici gli endpoint, ogni corpo di richiesta, ogni struttura di risposta e ogni errore. Fai puntare lì il generatore del tuo client, invece di trascrivere questa pagina. Il documento in sé è solo in inglese: è composto da token del contratto, e OpenAPI non prevede alcun meccanismo di localizzazione.
A two-page summary of the Engine API — the four modes (2D, 1D, wood and true-shape nest), a request and response, determinism and pricing. Print-ready, with a QR back to the docs.