optymalizacja drewna — 1D z dopasowaniem przekroju
POST
/v1/optimize/nest
nesting kształtów rzeczywistych — nieregularne wielokąty na płytach o stałym rozmiarze, ze strefami wykluczeń (laser / plazma / strumień wody)
POST
/v1/validate/2d
walidacja zapytania bez rozwiązywania — bezpłatnie, bez klucza, bez limitu (2d / 1d / wood / nest)
POST
/v1/validate/1d
walidacja zapytania bez rozwiązywania — bezpłatnie, bez klucza, bez limitu (2d / 1d / wood / nest)
POST
/v1/validate/wood
walidacja zapytania bez rozwiązywania — bezpłatnie, bez klucza, bez limitu (2d / 1d / wood / nest)
POST
/v1/validate/nest
walidacja zapytania bez rozwiązywania — bezpłatnie, bez klucza, bez limitu (2d / 1d / wood / nest)
GET
/v1/jobs/{id}
odpytaj asynchroniczne zadanie silnika max — zwraca jego status, a po zakończeniu plan (bez limitu; wyłącznie własne zadania)
POST
/v1/import/nest
odczytuje obrysy elementów z pliku SVG lub DXF — wymaga klucza, nie zużywa limitu
GET
/v1/usage
zużycie i limit KONTA w bieżącym miesiącu
GET
/v1/health
liveness — bez klucza, bez limitu zapytań, bez bazy danych
POST /v1/validate/{2d,1d,wood,nest} — response
valid
boolean
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.
Jednostki
API nie narzuca jednostek. Wybierz jedną jednostkę — milimetry, cale, cokolwiek — używaj jej w każdej przesyłanej liczbie, a każda liczba, którą otrzymasz z powrotem, będzie w tej samej jednostce. Nic nie jest przeliczane po stronie serwera i żadna nazwa pola nie zakłada konkretnej jednostki.
Dotyczy to wymiarów elementów i materiału bazowego, kerf, tolerance, trim oraz minOffcut na wejściu, a także każdej współrzędnej, pozycji, pozostałej długości, resztki i długości cięcia na wyjściu. Mieszanie jednostek w jednym zapytaniu daje plan, który przechodzi walidację, a fizycznie jest błędny — i serwer nie jest w stanie tego wykryć.
Układ współrzędnych
Początek układu to lewy górny narożnik płyty: x rośnie w prawo wzdłuż szerokości płyty, y rośnie w dół wzdłuż wysokości płyty. Pola x i y elementu wskazują jego lewy górny narożnik, a w i h to wymiary w takim ułożeniu, w jakim element został rozmieszczony — już zamienione, gdy rotated ma wartość true — więc prostokąt x, y, w, h to gotowy obrys na płycie, bez żadnych dalszych obliczeń. Prostokąty resztek korzystają z tego samego układu.
Okrawanie przesuwa rozmieszczenia: trim.left przesuwa każdy element w prawo, a trim.top przesuwa każdy element w dół, ponieważ elementy są rozmieszczane wewnątrz obszaru użytkowego, a następnie przesuwane z powrotem na całą płytę. trim.right i trim.bottom zmniejszają obszar użytkowy, nie przesuwając początku układu. Pola w i h płyty to zawsze pełne wymiary materiału bazowego, razem z okrawaniem — i właśnie dlatego okrawanie liczy się jako odpad w yieldPct.
domyślnie 1 — rozwijane po stronie serwera; liczy się do limitu 2,000 elementów
name
opcjonalna etykieta, zwracana przy każdym rozmieszczeniu
rotatable
domyślnie true — czy element można obrócić o 90°
grainGroup
elementy jednej grupy pozostają na tej samej płycie (dopasowanie słojów)
priority
element obowiązkowy: wygrywa miejsce na płycie, gdy zapas jest ograniczony (przy respectStock)
edgeBanding
okleinowanie każdej krawędzi osobno: podaj oznaczenie typu na dowolnej z top / right / bottom / left (dowolny ciąg znaków, Twój własny kod) — odpowiedź sumuje metry według oznaczenia. Wyłącznie metadane, nigdy nie przesuwa elementu. Tylko 2D
material
znacznik materiału (dowolny ciąg znaków, Twój własny kod): elementy i materiał bazowy o tym samym materiale są układane wyłącznie razem. W przeciwieństwie do edgeBanding zmienia układ. Brak = jedna nieokreślona pula. Działa w każdym trybie
Pola materiału bazowego
w oraz h są wymagane. qty domyślnie wynosi 1 i jest twardym limitem tylko przy respectStock. price dotyczy jednej płyty i zasila totalPrice oraz tryb kosztowy. priority (wartość logiczna) zużywa ten materiał w pierwszej kolejności; material (dowolny ciąg znaków) ogranicza go do elementów o tym samym materiale.
Opcje
kerf
szerokość rzazu piły (domyślnie 0)
tolerance
akceptuj cięcia przekraczające materiał o nie więcej niż tę wartość
trim
okrawanie każdej krawędzi osobno: left, right, top, bottom
oceniaj warianty według najniższej łącznej ceny materiału; przy kilku wycenionych rozmiarach materiału łączy je (2D) albo wybiera najtańszą długość (1D), aby obniżyć rachunek, nawet jeśli zużywa przy tym więcej materiału
respectStock
traktuj qty każdego wiersza materiału jako twardy limit
minOffcut
raportuj tylko resztki, których krótszy bok jest nie mniejszy niż ta wartość
maxCutStages
limit etapów piły panelowej — liczba faz, a nie surowa głębokość drzewa
minimizeRotations
preferuj układy obracające mniej elementów
effort
'fast' | 'balanced' (domyślnie 'balanced'). Głębokość przeszukiwania: 'balanced' wykonuje pełne best-of z wieloma strategiami; 'fast' pomija jedno kosztowne przeszukiwanie kombinacji powierzchni na płytę — wyraźnie szybszy przy dużych zleceniach kosztem kilku punktów wykorzystania, nadal gilotynowy i nigdy gęstszy niż 'balanced'. Przy małych zleceniach wynik jest zwykle identyczny. Tylko silnik heuristic
include robi dwie rzeczy. PRZYCINANIE — "cutPlan" i "offcuts" są domyślnie włączone; obecna tablica zachowuje wyłącznie te tokeny przycinania, które wymienia (pusta tablica usuwa oba). EKSPORT DODATKOWY — "svg", "csv" i "dxf" dodają do odpowiedzi dany eksport jako CIĄG ZNAKÓW: svg to samodzielny rysunek układu 2D (tylko 2D — zapytanie 1d/wood zwraca zamiast tego ostrzeżenie), dxf to rysunek R12/AC1009 na warstwach STOCK/PARTS/LABELS, csv to lista cięć. Tokeny eksportu nie wpływają na przycinanie, więc include:["svg"] dodaje svg i — nie wymieniając żadnego tokenu przycinania — usuwa cutPlan/offcuts; użyj ["cutPlan","offcuts","svg"], aby zachować wszystko i dodać 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.
Opcja effort równoważy czas obliczeń z wykorzystaniem. Oto ten kompromis, zmierzony na jednym wymagającym zleceniu — każda liczba pochodzi z prawdziwego packera.
W większości (mniejszych) zleceń oba są identyczne; różnica pojawia się tylko przy dużych zleceniach jak to. balanced jest wartością domyślną i nigdy nie jest gęstszy, niż może osiągnąć fast.
I tak jest szybko: nawet największe zlecenia produkcyjne — 2000 elementów i więcej — rozwiązywane są w kilka sekund na domyślnym silniku, z zapasem mieszcząc się w budżecie czasu API.
cutLines vs sawPasses — cutLines scala cięcia współliniowe (jedno ustawienie prowadnicy); sawPasses liczy każde przejście. To dwie uczciwe miary tego samego planu, a nie deklaracja zgodności z liczbami któregokolwiek konkurenta.
guillotineValid / cutPlan — Gdy układu nie da się pociąć od krawędzi do krawędzi, guillotineValid ma wartość false, a cutPlan jest null. To realna informacja — takiego układu nie wykonasz na pile panelowej — a nie błąd.
unplaced + warnings — Niewykonalne zlecenie zwraca 200 z elementami wypisanymi w unplaced i adnotacją w warnings. Plan, na którym można działać, jest wart więcej niż kod statusu.
edgeBanding — Gdy jakikolwiek element niesie edgeBanding, odpowiedź dodaje blok edgeBanding: metry bieżące, które zużywa każde oznaczenie typu, w rozbiciu na element i jako suma dla całego zlecenia. To dokładna geometria bez naddatku na odpad — własny naddatek dokłada warsztat — i zakłada wejście w milimetrach (÷1000 na metry). Przy zleceniu bez okleinowania klucz w ogóle nie występuje.
materials / unmatchedMaterials — Gdy jakikolwiek element lub materiał bazowy niesie material, odpowiedź dodaje materials (zestawienie per materiał — material, sheetCount/rodCount, yieldPct, placed, total, totalPrice) oraz unmatchedMaterials (zapotrzebowanie, którego material nie ma pasującego materiału bazowego). W drewnie material jedzie zamiast tego per sekcja przekroju. Oba klucze nie występują przy zleceniu bez materiału, które pozostaje bajt w bajt identyczne.
Pola odpowiedzi
Nazwy i typy pól są częścią kontraktu, dlatego poniższe tabele pozostają po angielsku we wszystkich językach — przetłumaczona nazwa pola dokumentowałaby API, które nie istnieje.
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
Gdy jakikolwiek element niesie edgeBanding, odpowiedź dodaje blok edgeBanding: metry bieżące, które zużywa każde oznaczenie typu, w rozbiciu na element i jako suma dla całego zlecenia. To dokładna geometria bez naddatku na odpad — własny naddatek dokłada warsztat — i zakłada wejście w milimetrach (÷1000 na metry). Przy zleceniu bez okleinowania klucz w ogóle nie występuje.
Gdy jakikolwiek element lub materiał bazowy niesie material, odpowiedź dodaje materials (zestawienie per materiał — material, sheetCount/rodCount, yieldPct, placed, total, totalPrice) oraz unmatchedMaterials (zapotrzebowanie, którego material nie ma pasującego materiału bazowego). W drewnie material jedzie zamiast tego per sekcja przekroju. Oba klucze nie występują przy zleceniu bez materiału, które pozostaje bajt w bajt identyczne.
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 — co fizycznie oznacza jeden krok
Jeden step to jeden ruch piły, a lista jest w takiej kolejności, w jakiej faktycznie da się ciąć: cięcie nadrzędne przed cięciami wewnątrz kawałka, który z niego powstał, bo paska nie przetniesz w poprzek, dopóki go nie odetniesz. axis "h" oznacza, że piła przesuwa się wzdłuż x i oddziela górę od dołu; axis "v" oznacza, że przesuwa się wzdłuż y i oddziela lewą stronę od prawej. pos to krawędź rzazu o NIŻSZEJ współrzędnej — wartość y dla "h", wartość x dla "v" — a nie jego linia środkowa: rzaz zajmuje przedział od pos do pos + kerf, więc piła zabiera materiał w kierunku rosnącej współrzędnej, czyli w dół dla "h" i w prawo dla "v". Materiał po stronie niższej współrzędnej — nad linią dla "h", po jej lewej stronie dla "v" — to kawałek, który uwalnia to cięcie. length to droga, jaką piła przebywa w tym jednym cięciu: rozpiętość obszaru, przez który przechodzi, a nie szerokość całej płyty.
stage to jedno przejście maszyny. Zaczyna się od 1 i zwiększa się tylko wtedy, gdy axis zmienia się względem cięcia nadrzędnego, więc rozcięcie płyty na sześć pasków to jeden etap, a ich poprzeczne przecięcie to następny. To właśnie tak rozumie się „cięcie trzyetapowe” na pile panelowej — nie jako głębokość drzewa cięć — i właśnie to ogranicza opcja maxCutStages. sheet to liczony od 0 indeks w sheets, a step zaczyna się od 1 na każdej płycie, zamiast biec przez całe zlecenie.
cutPlan ma wartość null — nie brakuje go i nie jest pusty — zawsze wtedy, gdy guillotineValid ma wartość false: układ, którego nie da się pociąć od krawędzi do krawędzi, nie ma sekwencji cięcia do zwrócenia. W odpowiedzi nie ma go w ogóle, jeśli pominięto go w include.
Elementy przyjmują length (oraz qty, name, priority); materiał bazowy przyjmuje length, qty i price. Zarówno elementy, jak i materiał bazowy przyjmują też opcjonalny znacznik material (materiał bazowy również priority) — material ogranicza element do materiału bazowego o tym samym materiale, a odpowiedź dodaje wtedy materials i unmatchedMaterials jak w 2D. Dostępne opcje to kerf, tolerance, trim.start / trim.end, minimizeCost, respectStock i minOffcut. Odpowiedź zwraca rods zamiast sheets, każdy z przypisanymi elementami, pozostałą długością i resztkami.
rods zastępuje sheets i nie ma pola guillotineValid, ponieważ cięcie liniowe zawsze da się wykonać. Pole pos każdego elementu to odległość jego bliższego końca od tego końca pręta, który okrawa trim.start, więc pierwszy element zaczyna się dokładnie na trim.start, a każde kolejne pos dodaje jedną szerokość rzazu. remaining to UŻYTECZNA resztka: szerokość rzazu tego cięcia, które uwalnia ją od ostatniego elementu, jest już odjęta, więc jest to długość możliwa do odzyskania, a nie surowa przerwa. Jest raportowane dla każdego pręta, nawet gdy jest mniejsze niż minOffcut — minOffcut filtruje wyłącznie tablicę offcuts, która zawiera najwyżej jeden wpis. W planie cięcia sheet to indeks pręta, axis ma zawsze wartość "v", stage ma zawsze wartość 1, a length zawsze 0: poprzeczne przecięcie pręta nie ma drogi przejazdu, którą można by zaraportować — i właśnie dlatego metrics dla 1D zawiera cuts, ale nie cutLength.
Drewno ma tożsamość, której nie ma zwykły pręt: elementu 50×150 nie da się wyciąć z materiału 50×100, niezależnie od tego, ile długości zostało. Elementy i materiał niosą więc sw i sh, dwa boki przekroju, w dowolnej kolejności — 50×100 i 100×50 to ta sama belka obrócona i trafiają do jednego przekroju. Zlecenie jest dzielone według przekroju, każdy przekrój jest dopasowywany do własnego materiału i rozwiązywany osobno, a jedno wywołanie zwraca całość. Elementy i materiał bazowy przyjmują też opcjonalny znacznik material (materiał bazowy również priority): dzięki niemu dąb 50×100 i sosna 50×100 stają się dwiema osobnymi sekcjami, a każda sekcja niesie swój materiał. Opcje są takie same jak w 1D.
sections zastępuje rods na najwyższym poziomie: każdy wpis to jeden przekrój z własnymi rods (identycznymi w formie jak w 1D) i własnymi metrics, więc wartości dla danego materiału są od razu dostępne, bez przeliczania. unmatched nie ma odpowiednika w 1D — to zapotrzebowanie, dla którego przekroju nie podałeś żadnego materiału. To inny problem niż unplaced (elementy, które miały materiał i się nie zmieściły) i wymaga innej korekty, dlatego oba nigdy się nie mieszają. metrics.total liczy każdy zamówiony element, łącznie z tymi z unmatched. W planie cięcia każdy krok podaje też swoją section, a sheet to indeks pręta W OBRĘBIE tego przekroju, a nie licznik dla całego zlecenia.
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.
Trzy powyższe tryby pakują prostokąty. POST /v1/optimize/nest pakuje DOWOLNE WIELOKĄTY: element to obrys (polygon, z opcjonalnymi wewnętrznymi holes), a nie szerokość×wysokość, więc elementy wsuwają się we wklęsłe kieszenie sąsiadów, a powietrze wcięć, które marnuje prostokąt otaczający, jest odzyskiwane — na reprezentatywnym zleceniu 6 płyt tam, gdzie te same elementy według prostokąta otaczającego potrzebują 9. To inna klasa algorytmu (geometryczny silnik kolizji, a nie packer gilotynowy), do cięcia laserem, plazmą i strumieniem wody. W komplecie dwie rzeczy, których prostokątne API nie potrafi wyrazić: strefy wykluczeń na każdą płytę (stock[].exclusions — wada, ślad docisku, obszar zadrukowany; strefa o quality równym 0 to obszar zakazany dla dowolnego elementu) oraz otwory kształtów rzeczywistych. Podział na material i przekazywanie meta działają jak wszędzie indziej. Odstęp ustawiany jest dwukrotnie: options.minSeparation między elementami i options.edgeClearance przy krawędzi płyty (domyślnie przyjmuje wartość minSeparation). Gdy dla jednego materiału jest kilka rozmiarów materiału bazowego, oba silniki porównują je i zachowują najlepszy plan — według powierzchni płyty, albo według ceny z minimizeCost — i mogą mieszać rozmiary (do połowy pusta ostatnia płyta przechodzi na mniejszy rozmiar), więc wynik nie zależy od kolejności, w jakiej je podasz. Domyślny silnik, lbf, odpowiada natychmiast; engine "max" uruchamia to samo zadanie asynchronicznie i szuka układu z mniejszą liczbą płyt (patrz Zadania asynchroniczne). Poniższy przykład to jedno prawdziwe przechwycone wywołanie — osiem elementów na jednej płycie z wykluczonym uszkodzonym narożnikiem.
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, synchronous, instant) or "max" (asynchronous: 202 + poll GET /v1/jobs/{id}; runs lbf first, then a time-budgeted search that fills sheets one at a time looking for FEWER sheets, and never returns more than lbf, nor a plan that ranks worse; several sheet sizes per material and respectStock are modelled, while a polygon sheet or exclusion zones are served by lbf inside the job, with a warning). "sparrow" is a deprecated alias of "max".
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. With options.respectStock (a capped supply, where not every part may fit) these parts are placed first, so a plain part never takes the sheet space a must-cut part needs, and every plan comparison ranks the plan that keeps more of them ahead. Both engines. Without respectStock every part that fits is placed anyway, so it changes nothing. A must-cut part that fits no sheet is still listed in unplaced.
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. Summed into totalPrice and stockUsage[].totalPrice, and the ranking key of options.minimizeCost.
cost
integer
Integer relative per-sheet cost, used by options.minimizeCost for a row that has no price (e.g. when price is not money).
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. The sheet-edge part can be set separately with edgeClearance.
edgeClearance
number
Minimum distance between any part and the SHEET EDGE, independent of minSeparation (e.g. 5 between parts, 0 or 10 at the edge). Omit it and the edge keeps minSeparation, exactly as before. Both engines. Rectangular sheets only: with a polygon sheet it is a 400, as is a value that leaves no room on a sheet. Exclusion zones keep minSeparation, like parts.
seed
integer
Determinism: a fixed seed → a reproducible layout. Omit and the server pins a fixed default so the response stays reproducible.
minimizeCost
boolean
How "lbf" picks the sheet size when a material has two or more stock rows. It always tries the rows in your order, each row on its own and a cheapest-first order, and keeps the best — so the answer does not depend on the order you list them in. Ranking: most must-cut (priority) parts placed → most parts placed → (with minimizeCost) lowest total price → least total sheet area → fewest sheets. minimizeCost ranks by price (or cost) only when EVERY row of that material has one; otherwise it falls back to sheet area with a warning. Then every sheet, the least-filled first, is re-nested onto the cheaper or smaller rows and replaced when the whole plan ranks better — so sizes can be MIXED within a material (e.g. two large sheets and a small last one). "max" uses the same ranking to decide whether its search result replaces the lbf plan.
respectStock
boolean
Treat each stock qty as a hard cap — on both engines; the mixed-size re-nest never uses a row more often than its qty.
simplifyTolerance
number
Polygon simplification tolerance (max area deviation as a fraction). Speeds up dense DXF outlines with hundreds of vertices. 0 disables.
compact
boolean
Default true, both engines (OPEN-298). After the plan is final, the least-filled sheet of each material is re-nested with the same parts on the same sheet, also with rotation SUBSETS of what each part allows ({0,180}, {0,90,180,270}, one orientation for every copy; intersected with a discrete allowedRotations, never a new angle), and the variant with the smallest used area (usedWidth × usedHeight) is kept. Sheet count, sizes, price and placed parts never change. Within a work budget; false returns the uncompacted layout.
compactFor
"box" | "horizontal" | "vertical"
OPEN-299 — which used measure the compaction minimises; match it to how you charge a partial sheet. "box" (default): the smallest usedArea. "horizontal": the lowest usedHeight (a strip across the full sheet width). "vertical": the smallest usedWidth (a strip over the full sheet height). Ties fall back to the area; the strip modes also try every copy turned 90° or 270° where allowedRotations permits. Sheet count, sizes, price and placed parts never change; never worse than the uncompacted layout on the chosen measure.
timeBudgetMs
integer
Search budget for engine "max", in ms: default 60000, clamped to 10000–180000 (a clamp is reported in warnings). Ignored by "lbf" (single-pass). The job finishes about 1–2 s after the budget plus any queueing; for a job of a few dozen parts 15000 is usually enough. Poll GET /v1/jobs/{id} every 2–3 s — a poll costs no quota.
Każdy wpis w sheets to jedna użyta płyta; rozmieszczony element niesie transformację sztywną (rotation w stopniach, a następnie przesunięcie x/y), a NIE ponownie wyemitowany wielokąt — obróć wejściowy obrys o rotation wokół jego początku i dodaj (x, y), aby dokładnie odtworzyć rozmieszczenie. rotation może być ujemne; odtworzenie jest dokładne niezależnie od znaku. ⚠️ density to powierzchnia rozmieszczonego WIELOKĄTA względem powierzchni użytej płyty — uczciwe wypełnienie, w którym wklęsłe kieszenie liczą się jako puste — i NIE jest porównywalne z yieldPct packera prostokątnego (który liczy każdy prostokąt otaczający jako pełny, więc wypada wyżej dla gorszego wyniku); miarą porównywalną między nimi jest sheetCount na tych samych elementach. Układ jest deterministyczny: ustaw options.seed, aby go odtworzyć. exclusions jest zwracane na każdej płycie na potrzeby renderowania. Każda płyta niesie stock — indeks wiersza materiału bazowego, z którego została wycięta — a odpowiedź sumuje w stockUsage płyty, cenę i gęstość dla każdego wiersza materiału bazowego, dzięki czemu wycenę można ustalić dla każdego rozmiaru płyty. Każda płyta niesie też cutLength i pierces (metrics sumuje oba): sumę obwodów obrysów i otworów rozmieszczonych na niej elementów oraz jedno przebicie na każdy zamknięty kontur — wartości geometryczne, bez cięcia wspólną linią ani najazdów. Każda płyta niesie też usedWidth i usedHeight — najdalszy punkt, do którego sięgają jej rozmieszczone elementy od narożnika początkowego płyty, czyli wykorzystany prostokąt otaczający do naliczania opłaty za część arkusza — oraz usedArea (stockUsage sumuje to dla każdego wiersza); oba silniki domyślnie zagęszczają najsłabiej wypełnioną płytę w kierunku tego narożnika, z podzbiorami obrotów dopuszczonych dla każdego elementu (options.compact: false to wyłącza; options.compactFor wybiera wielkość do zminimalizowania — "box" (domyślnie) wykorzystaną powierzchnię, "horizontal" wykorzystaną wysokość dla naliczania opłaty za pas na pełną szerokość, "vertical" wykorzystaną szerokość dla pasa na pełną wysokość; liczba płyt i cena nigdy się nie zmieniają). Poproś o include:["svg","dxf"], a odpowiedź poniesie też samodzielny rysunek SVG oraz plik DXF R12 uzyskanego nestingu, w treści.
POST /v1/optimize/nest — top level
engine
string
Which nesting engine served the request: "lbf", or "max" in the result of a finished async max job.
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
true for "lbf" — guaranteed by the pinned seed (false only if the comparison between several stock rows hit its time limit on the server, with a warning). For "max": false when the time-limited search shaped the result, true when the lbf pass alone decided it.
sheets
array
One entry per used sheet.
metrics
object
Job totals (see below).
stockUsage
array
Sheets used per request stock ROW (see below). Always present.
unplaced
array
Demand that could not be placed — a plan-plus-warning, not an error.
warnings
string[]
e.g. unplaced parts, a material with no matching stock, or — for "max" — how the result was reached (for instance that the lbf layout was kept because no layout with fewer sheets was found in the time budget).
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 — always the real sheet size, also with edgeClearance.
polygon
number[][]
Present for arbitrary-outline sheets instead of w/h.
stock
integer
Index into the request stock[] this sheet was cut from — the key to tie the sheet back to your stock row.
price
number | null
The stock row's price, or null.
density
number
This sheet's fill = placed polygon area / sheet area.
cutLength
number
Σ perimeter of the placed parts' outer outlines AND holes on this sheet, 3 decimals, in your unit — the contour a laser / plasma / waterjet head follows. Geometric: no common-line cutting, lead-in/out or micro-joints (your CAM adds those).
pierces
integer
Pierce points on this sheet: one per closed contour (each placed part's outline + one per hole).
usedWidth, usedHeight
number
OPEN-298 — the farthest x and y any placed part reaches, measured from the sheet origin (0,0), 3 decimals, in your unit; real sheet coordinates (with edgeClearance the edge gap is included). The used bounding box from the origin corner — what an ERP needs to charge a partial sheet (horizontal cut = usedHeight × w, vertical cut = usedWidth × h, boundary box = usedArea); the charging rule stays yours. A polygon sheet: the same, in its own coordinate frame.
usedArea
number
usedWidth × usedHeight, 2 decimals.
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.
cutLength
number
Σ sheets[].cutLength — placed parts only (outlines + holes), in your unit.
pierces
integer
Σ sheets[].pierces.
stockUsage[] (nest)
stock
integer
Index into the request stock[]. One entry per USED row, sorted by stock. The unit is the row, not the size: two rows of the same w × h with a different price or meta are two entries.
w, h
number
Present for rectangular rows.
material
string
Present when the row carries a material.
sheetCount
integer
Sheets cut from this row.
totalPrice
number
Sum of those sheets' prices (0 for a row without a price, like metrics.totalPrice).
density
number
Placed polygon area / total area of this row's sheets.
usedArea
number
OPEN-298 — Σ sheets[].usedArea of this row's sheets: the used bounding area to total per sheet size.
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.
Elementy z pliku (SVG · DXF)
Element nie musi przychodzić jako współrzędne. Umieść dokument SVG lub DXF w parts[].source, a serwer wyciągnie z niego obrys — wraz z otworami — tym samym czytnikiem, którego aplikacja CutOptim używa, gdy upuścisz rysunek na jej tryb Nesting. Plik zastępuje WYŁĄCZNIE geometrię: qty, material, allowedRotations, minQuality, priority i meta zachowują się dokładnie tak jak przy elemencie polygon, więc biblioteka elementów istniejąca już jako pliki CAD nie wymaga własnego spłaszczania krzywych i łuków. Jedno source opisuje JEDEN element; rysunek zawierający kilka oddzielnych części zwraca 400 i kieruje do poniższego endpointu importu. Odpowiedź niesie wtedy blok imported: ile wierszy pochodzi z pliku, ile wierzchołków wytworzyły i jakie jednostki te pliki zadeklarowały — zgłoszone, nigdy zastosowane, bo to API niczego nie przelicza.
Nic nie jest przechowywane. Bajty istnieją wyłącznie jako treść żądania, są przetwarzane w pamięci i znikają, gdy odpowiedź zostaje zapisana: żadnego dysku, żadnej bazy danych, żadnego pliku tymczasowego, żadnego wpisu w logu. Nie ma potem czego usuwać i nic nie zostaje — ta sama bezstanowość, którą utrzymuje każdy inny endpoint.
Nazwy i typy pól są częścią kontraktu, dlatego poniższe tabele pozostają po angielsku we wszystkich językach — przetłumaczona nazwa pola dokumentowałaby API, które nie istnieje.
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 — jeden plik, wszystkie obrysy
Gdy jeden rysunek zawiera kilka różnych elementów, najpierw go zaimportuj: ten endpoint zwraca każdy zamknięty obrys, jaki zawiera, od największego, dokładnie w postaci, jakiej oczekuje wiersz parts[]. Wklej te, których potrzebujesz, dodaj własne qty i material i wyślij to do /v1/optimize/nest. To także sposób, by zobaczyć, co jest w pliku, zanim wydasz na niego obliczenie. Wymaga klucza — spłaszczanie dowolnej geometrii to realna praca procesora, a anonimowy procesor to zły interes — ale niczego nie rezerwuje: Twój limit pozostaje nienaruszony i nie wracają nagłówki rate limit, dokładnie jak przy odpytywaniu zadania.
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".
Silniki
heuristic(domyślny) — wielostrategiowy algorytm pakowania gilotynowego. Najwyższe wykorzystanie, każdy układ da się pociąć na pile, zawsze pełny cutPlan.
balanced — algorytm pakowania MaxRects ze swobodnym nestingiem. Znacznie szybszy przy dużych zleceniach (zmierzone ~25× przy 2000 elementów) za cenę niewielkiego spadku wykorzystania, a jego układy często nie są gilotynowe (guillotineValid: false, cutPlan: null). Nie modeluje tolerance, minimizeCost, grainGroup, maxCutStages ani minimizeRotations — po ustawieniu któregoś z nich ostrzeżenie poinformuje, że został zignorowany.
max — asynchroniczny poziom. W 2D to przeszukiwanie drzewa, które osiąga dowiedzione optimum przy znacznie większej liczbie zleceń kosztem sekund do minuty na obliczenie. Nadal deterministyczny i gilotynowy. Nie zwraca planu bezpośrednio — patrz Zadania asynchroniczne poniżej. Modeluje JEDEN format materiału w pełnym rozmiarze płyty, w nieograniczonej ilości, ze stałym 3-etapowym schematem gilotynowym: drugi wiersz materiału, trim, respectStock, material lub grainGroup są odrzucane kodem 400, zanim wywołanie zostanie zarezerwowane; tolerance, minimizeCost, maxCutStages, minimizeRotations, firstCut i effort przechodzą, ale są ignorowane z ostrzeżeniem, a wynik max nie raportuje ścinków. Takie zlecenia kieruj do silnika heuristic. W nestingu kształtów rzeczywistych (POST /v1/optimize/nest) max najpierw wykonuje przebieg lbf, a następnie przeszukuje, w granicach options.timeBudgetMs (domyślnie 60 s, od 10 do 180), szukając układu z mniejszą liczbą płyt — nigdy więcej; jego wyniki nestingu niosą deterministic: false. Tam przyjmuje kilka rozmiarów płyt na materiał oraz respectStock i zachowuje swój wynik tylko wtedy, gdy jest lepszy w rankingu niż wynik lbf (według ceny z minimizeCost, w przeciwnym razie według powierzchni płyty); płyta w kształcie wielokąta albo strefy wykluczeń są obsługiwane przez lbf wewnątrz zadania, z ostrzeżeniem.
Zadania asynchroniczne (engine = max)
Obliczenie max trwa od sekund do minuty, więc POST /v1/optimize/2d albo /v1/optimize/nest z engine:"max" nie zwraca wyniku — zwraca 202 Accepted z polem jobId, a wywołanie jest naliczane przy zgłoszeniu. Odpytuj GET /v1/jobs/{id}, aż status będzie "succeeded" (result zawiera wtedy tę samą odpowiedź, którą zwraca obliczenie synchroniczne danego trybu — plan 2D albo nest; mode mówi który) albo "failed" (error zawiera komunikat). Odpytywanie nie zużywa limitu; widzisz wyłącznie własne zadania. Tam, gdzie ten poziom nie jest włączony we wdrożeniu, engine:"max" jest odrzucane — fail closed — z kodem 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
"2d" (submitted to POST /v1/optimize/2d) or "nest" (submitted to POST /v1/optimize/nest), and always "max".
pollAfterMs
integer
202 only — suggested delay before the first poll.
result
object
Present once succeeded — the same shape as the synchronous response of that mode: a 2D plan, or a nest.
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.
Determinizm i wersjonowanie
Każda odpowiedź zawiera engineVersion. Algorytmy są deterministyczne (jeden wyjątek, ograniczone czasowo przeszukiwanie max w nestingu, raportuje deterministic: false), więc ulepszenie jednego z nich zmienia wynik dla tych samych danych wejściowych — a to zmiana łamiąca zgodność, jeśli buforujesz wyniki. Przypnij zachowanie, przesyłając engine jawnie i obserwując engineVersion; wersja w ścieżce /v1/ zmienia się tylko wtedy, gdy zmienia się struktura odpowiedzi.
Każdy silnik jest wersjonowany niezależnie, więc zmiana w jednym nigdy nie przesuwa wersji drugiego.
Błędy
400
invalid_request
Błąd schematu. details.path wskazuje pole, które go wywołało.
401
unauthorized
Brakujący lub nieznany klucz API.
402
quota_exceeded
Wyczerpany miesięczny limit. Retry-After podaje liczbę sekund do początku nowego miesiąca.
403
key_revoked
Klucz istnieje, ale nie może być użyty: został unieważniony albo subskrypcja Engine API tego konta nie jest już aktywna. Pole message mówi, o który przypadek chodzi.
404
not_found
Nie ma takiej trasy — to samo otrzymasz, gdy ścieżka jest poprawna, ale metoda nie.
413
too_large
Dane wejściowe przekraczają limit (patrz Limity).
429
busy
Chwilowy brak wolnych zasobów albo — na każdej ścieżce uwierzytelnianej kluczem — Twój adres wysłał w ciągu minuty zbyt wiele nieznanych kluczy API. Retry-After w sekundach; to nigdy nie obciąża Twojego limitu.
500
internal
Nieoczekiwany błąd albo backend uwierzytelniania jest nieosiągalny (zapytania są wtedy odrzucane — fail closed).
503
service_unavailable
Żądany silnik nie może być teraz obsłużony — silnik nestingu albo asynchroniczny silnik max. Dla max są dwie przyczyny, a pole message mówi która: poziom nie jest wbudowany w to wdrożenie, albo jest wbudowany, ale worker rozwiązujący zadania nie odpowiada. Fail-closed przed zarezerwowaniem wywołania, więc nigdy nic nie kosztuje.
504
solve_timeout
Obliczenia przekroczyły swój twardy limit czasu. Na ścieżkach prostokątnych wymusza go proxy; przy /v1/optimize/nest silnik wymusza własny, krótszy budżet i odpowiada kodem solve_timeout w zwykłej kopercie. Wywołanie, z którego proxy zrezygnowało, nie jest naliczane.
Treść błędu
Każdy błąd generowany przez sam silnik korzysta z tej samej koperty. Rozgałęziaj logikę na polu error, które jest stabilnym kodem; nigdy na message, którego brzmienie może się zmienić między wydaniami. details występuje przy invalid_request, gdzie path wskazuje pole, które wywołało błąd, oraz przy too_large, gdzie max i got podają limit i to, co zostało przesłane.
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" }
}
Zarówno 402, jak i 429 zwracają nagłówek Retry-After w sekundach. Przy 402 odlicza on czas do zerowania limitu o 00:00 UTC pierwszego dnia następnego miesiąca; przy 429 jest to krótkie wstrzymanie, a 429 nigdy nie zużywa limitu — zarezerwowane wywołanie jest oddawane.
Błąd routingu odpowiada kodem not_found, który celowo znajduje się poza powyższą listą, bo generuje go router, a nie kontrakt API. Otrzymasz 404, a nie 405, gdy ścieżka jest poprawna, ale metoda nie: wszystkie cztery endpointy optymalizacji przyjmują wyłącznie POST.
Na ścieżkach prostokątnych 504 pochodzi z reverse proxy, a nie z silnika, więc jego treść należy do proxy i nie jest tą kopertą JSON; pojedyncze zadanie w granicach poniższych limitów wejściowych mieści się w nim z dużym zapasem, ale kilka najcięższych zadań naraz może go przekroczyć w kolejce — a wywołanie, z którego proxy zrezygnowało, nie jest naliczane. Wyjątkiem jest /v1/optimize/nest: to obliczenie jest podprocesem z własnym budżetem, celowo utrzymanym poniżej limitu proxy — przekroczenie czasu używa tam tej koperty, z kodem solve_timeout.
Nagłówki limitu zapytań
Udane wywołanie optymalizacji zwraca X-RateLimit-Limit (miesięczny limit KONTA — wszystkie klucze konta dzielą jeden) oraz X-RateLimit-Remaining (liczba wywołań pozostałych kontu w tym miesiącu, już po tym wywołaniu). Wysyłają je wyłącznie endpointy optymalizacji: licznik jest rezerwowany w ramach autoryzacji obliczeń, więc /v1/usage i /v1/health nie mają o czym raportować.
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.
Tylko do odczytu: nie zużywa wywołania i nie wysyła nagłówków limitu. ⚠️ used i limit opisują KONTO, a nie klucz, którym wywołałeś: każdy aktywny klucz konta czerpie z jednej wspólnej puli, więc tworzenie kolejnych kluczy nie tworzy dodatkowego limitu. used liczy bieżący miesiąc kalendarzowy UTC dla wszystkich, remaining to limit minus used i nigdy nie schodzi poniżej zera, periodEnd to dzień zerowania jako zwykła data YYYY-MM-DD, a keyPrefix to jawny prefiks użytego klucza. Sam klucz nie jest zwracany przez żaden endpoint — przechowywany jest wyłącznie jego hash, więc zgubiony klucz się wymienia, a nie odzyskuje.
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.
Bez klucza, bez limitu, bez bazy danych. Celowo nie sięga do niczego, co przechowuje stan, więc awaria magazynu kluczy nie może sprawić, że usługa wygląda dla orkiestratora na martwą. engines wypisuje identyfikatory, które to wdrożenie przyjmuje w polu engine, a engineVersion to wersja silnika domyślnego.
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.
Limity
2,000 elementów na zapytanie (łączna ilość, po rozwinięciu qty)
50 wierszy materiału bazowego · treść zapytania do 1 MB
10 aktywnych kluczy na konto — dzielą JEDEN miesięczny limit: klucze oddzielają środowiska i integracje, nie zwiększają puli
10 MB treści żądania na dwóch ścieżkach nest, które mogą nieść rysunek (/v1/optimize/nest i /v1/import/nest); jeden plik source najwyżej 4 000 000 znaków, 8 000 000 na żądanie
współbieżność jest ograniczona po stronie serwera — obliczenia idą pojedynczo za krótką kolejką (ok. 8 s); burst ponad nią dostaje 429, a odrzucone żądanie nigdy nie jest naliczane. Endpointy validate bez klucza mają dodatkowo limit na adres (429 z Retry-After); z działającym kluczem nigdy nie ma takiego dławienia. Adres, który w ciągu minuty wyśle ponad 30 nieznanych kluczy API, dostaje 429 do końca tej minuty — jeszcze przed sprawdzeniem klucza. Konto może mieć jednocześnie 5 zadań max w stanie queued/running.
Specyfikacja OpenAPI
Czytelny maszynowo dokument OpenAPI 3.1 opisuje wszystkie dwanaście endpointów, każdą treść zapytania, każdą strukturę odpowiedzi i każdy błąd. Skieruj na niego swój generator klienta, zamiast przepisywać tę stronę. Sam dokument jest wyłącznie w języku angielskim: składa się z elementów kontraktu, a OpenAPI nie ma mechanizmu lokalizacji.
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.