Przejdź do głównej treści
← CutOptim Engine API

Dokumentacja API

Bazowy URL https://api.cutoptim.com · wersja kontraktu v1

Utwórz klucz w swoim panelu, a następnie przesyłaj go jako Authorization: Bearer <key>.

Specyfikacja OpenAPI: /engine/openapi.json

Endpointy

POST/v1/optimize/2doptymalizacja cięcia płyt 2D
POST/v1/optimize/1doptymalizacja 1D / liniowa (pręty, profile, rury)
POST/v1/optimize/woodoptymalizacja drewna — 1D z dopasowaniem przekroju
POST/v1/optimize/nestnesting kształtów rzeczywistych — nieregularne wielokąty na płytach o stałym rozmiarze, ze strefami wykluczeń (laser / plazma / strumień wody)
POST/v1/validate/2dwalidacja zapytania bez rozwiązywania — bezpłatnie, bez klucza, bez limitu (2d / 1d / wood / nest)
POST/v1/validate/1dwalidacja zapytania bez rozwiązywania — bezpłatnie, bez klucza, bez limitu (2d / 1d / wood / nest)
POST/v1/validate/woodwalidacja zapytania bez rozwiązywania — bezpłatnie, bez klucza, bez limitu (2d / 1d / wood / nest)
POST/v1/validate/nestwalidacja 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/nestodczytuje obrysy elementów z pliku SVG lub DXF — wymaga klucza, nie zużywa limitu
GET/v1/usagezużycie i limit KONTA w bieżącym miesiącu
GET/v1/healthliveness — bez klucza, bez limitu zapytań, bez bazy danych

POST /v1/validate/{2d,1d,wood,nest} — response

validbooleanAlways 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.
modestring"2d" | "1d" | "wood" | "nest" — the endpoint you called.
contractVersionstringShape version. Currently "1".
engineEnabledbooleanNEST ONLY — whether the nesting engine is built into this deployment. Absent for the rectangular modes.
partsobject{ rows, total } — part rows sent, and the total quantity after qty expansion. Check it against the part cap before you spend a call.
stockobject{ rows, total } — the same for stock.
warningsstring[]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.

2D — zapytanie

POST /v1/optimize/2d
Authorization: Bearer co_live_…
Content-Type: application/json

{
  "parts": [{ "name": "Door", "w": 600, "h": 400, "qty": 4, "rotatable": true }],
  "stock": [{ "w": 2440, "h": 1220, "qty": 10, "price": 42 }],
  "options": {
    "kerf": 3,
    "trim": { "left": 10, "top": 10 },
    "minimizeCost": true,
    "effort": "balanced"
  },
  "engine": "heuristic",
  "include": ["cutPlan", "offcuts"]
}

Pola elementu

w, hwymagane — wymiary elementu
qtydomyślnie 1 — rozwijane po stronie serwera; liczy się do limitu 2,000 elementów
nameopcjonalna etykieta, zwracana przy każdym rozmieszczeniu
rotatabledomyślnie true — czy element można obrócić o 90°
grainGroupelementy jednej grupy pozostają na tej samej płycie (dopasowanie słojów)
priorityelement obowiązkowy: wygrywa miejsce na płycie, gdy zapas jest ograniczony (przy respectStock)
edgeBandingokleinowanie 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
materialznacznik 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

kerfszerokość rzazu piły (domyślnie 0)
toleranceakceptuj cięcia przekraczające materiał o nie więcej niż tę wartość
trimokrawanie każdej krawędzi osobno: left, right, top, bottom
firstCut'auto' | 'horizontal' | 'vertical' (domyślnie 'auto')
minimizeCostoceniaj 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
respectStocktraktuj qty każdego wiersza materiału jako twardy limit
minOffcutraportuj tylko resztki, których krótszy bok jest nie mniejszy niż ta wartość
maxCutStageslimit etapów piły panelowej — liczba faz, a nie surowa głębokość drzewa
minimizeRotationspreferuj 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.

parts[].meta · stock[].meta — passthrough (2d · 1d · wood · nest)

parts[].metaobjectOpaque 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[].metaobjectThe 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.

Przełącznik effort: wykorzystanie materiału vs czas obliczeńPrzełącznik effort: wykorzystanie materiału vs czas obliczeń. fast: 350 płyty · 76.2% · ≈1.9 s. balanced: 330 płyty · 80.8% · ≈4.8 s. max: zarezerwowane — gęściej = wolniejsze przeszukiwanie. 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.Przełącznik effort: wykorzystanie materiału vs czas obliczeńJedno wymagające zlecenie — około 1550 elementów na płycie 2,07 × 5,6 m. Każda liczba zmierzona na prawdziwym packerze.74%76%78%80%82%84%02 s4 s6 sczas obliczeń · szybciej →wykorzystanie materiału · gęściej ↑⇄ przełącznik effort+4,6 pp wykorzystania · −20 płyt−5,7% materiału · ≈2,5× wolniejfast350 płyty · 76.2% · ≈1.9 s★ balanced · domyślny330 płyty · 80.8% · ≈4.8 smaxzarezerwowanegęściej =wolniejszeprzeszukiwanie
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.

2D — odpowiedź

{
  "engine": "heuristic",
  "engineVersion": "1.0.0+10e0c941",
  "contractVersion": "1",
  "deterministic": true,
  "sheets": [
    {
      "w": 2440,
      "h": 1220,
      "price": 42,
      "parts": [
        { "name": "Door", "x": 10, "y": 10, "w": 600, "h": 400, "rotated": false },
        { "name": "Door", "x": 613, "y": 10, "w": 600, "h": 400, "rotated": false },
        { "name": "Door", "x": 1216, "y": 10, "w": 600, "h": 400, "rotated": false },
        { "name": "Door", "x": 1819, "y": 10, "w": 600, "h": 400, "rotated": false }
      ],
      "offcuts": [
        { "x": 2422, "y": 10, "w": 18, "h": 400 },
        { "x": 10, "y": 413, "w": 2430, "h": 807 }
      ]
    }
  ],
  "metrics": {
    "sheetCount": 1,
    "yieldPct": 32.25,
    "placed": 4,
    "total": 4,
    "cutLines": 5,
    "sawPasses": 5,
    "cutLength": 4030,
    "totalPrice": 42
  },
  "unplaced": [],
  "warnings": [],
  "guillotineValid": true,
  "timing": { "solveMs": 3.13 },
  "cutPlan": [
    { "sheet": 0, "step": 1, "axis": "h", "pos": 410, "length": 2430, "stage": 1 },
    { "sheet": 0, "step": 2, "axis": "v", "pos": 610, "length": 400, "stage": 2 },
    { "sheet": 0, "step": 3, "axis": "v", "pos": 1213, "length": 400, "stage": 2 },
    { "sheet": 0, "step": 4, "axis": "v", "pos": 1816, "length": 400, "stage": 2 },
    { "sheet": 0, "step": 5, "axis": "v", "pos": 2419, "length": 400, "stage": 2 }
  ]
}
  • 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

enginestringWhich engine ran: "heuristic", "balanced" or "max".
engineVersionstringAlgorithm identity — package version + a content hash of the algorithm source. Moves automatically on any packer change, and independently per engine.
contractVersionstringShape version, matching the /v1/ in the path. Currently "1".
deterministicbooleanAlways true. Present so a client can assert the guarantee it relies on.
sheetsarrayOne entry per sheet used, in cutting order.
metricsobjectAggregate numbers for the whole job.
cutPlanarray | nullThe sawing plan. null when guillotineValid is false; absent when excluded via include.
unplacedarrayParts that did not fit, aggregated by name + size. Empty when everything fitted.
warningsstring[]Free-text notes about the plan. Do not parse — branch on unplaced, guillotineValid and metrics.
guillotineValidbooleanTrue when the layout is producible with edge-to-edge cuts, i.e. on a panel saw.
edgeBandingobjectLinear metres of edge banding, grouped by type reference. ABSENT unless a part requested banding via parts[].edgeBanding. 2D only.
materialsarrayPer-material rollup. ABSENT unless a part or stock row carried material — a material-free job stays byte-identical. (OPEN-256)
unmatchedMaterialsarrayDemand whose material has no matching stock at all. ABSENT when it does not happen. A missing-material report, not a did-not-fit one.
svgstringInline SVG of the 2D layout (self-contained, no external refs). Present ONLY when include contains "svg". 2D only. (OPEN-223)
csvstringInline CSV cut list. Present ONLY when include contains "csv".
dxfstringInline DXF (R12/AC1009) on layers STOCK/PARTS/LABELS. Present ONLY when include contains "dxf".
timing.solveMsnumberMilliseconds inside the packer, 2 decimals. Excludes parsing, auth and queueing.

sheets[]

w, hnumberFULL stock dimensions, trim included — not the usable area.
pricenumber | nullPrice of the stock row this sheet came from, or null if none was given.
partsarrayPlacements on this sheet.
offcutsarrayUsable leftover rectangles, filtered by options.minOffcut. Absent (not empty) when excluded via include.
metaobjectPresent only when the stock row carried meta — echoed verbatim from stock[].meta. (OPEN-224)

sheets[].parts[]

namestringThe requested name, or the generated default "Part <row>".
x, ynumberTop-left corner of the part, from the top-left corner of the sheet.
w, hnumberDimensions AS PLACED — already swapped when rotated is true. No client-side swap needed.
rotatedbooleanTrue if the part was turned 90° from the requested w×h. Informational only.
metaobjectPresent only when the part carried meta — echoed verbatim from parts[].meta on every placed piece. (OPEN-224)

sheets[].offcuts[]

x, y, w, hnumberA leftover rectangle, in the same coordinate system as the placements.

metrics (2D)

sheetCountintegerSheets used. Equals sheets.length.
yieldPctnumberPlaced part area ÷ total FULL sheet area × 100, 2 decimals. Trim and kerf count as waste.
placedintegerPieces placed, after qty expansion.
totalintegerPieces requested, after qty expansion.
cutLinesintegerCollinear cuts merged: same axis, same coordinate, same stage counted once — one fence setting.
sawPassesintegerEvery cut, one per strip crossed — how many separate passes the saw makes.
cutLengthnumberTotal distance sawn, 3 decimals. Same under either counting convention. In your unit.
totalPricenumberSum of the used sheets’ prices, 2 decimals. 0 when no stock row carried a price.

unplaced[] (2D)

namestringThe part name.
w, hnumberAs requested.
qtyintegerHow many of this part could not be placed.

edgeBanding (2D — present only when a part is banded)

totalMetersnumberOrder-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[]

sheetinteger0-based index into sheets (2D) or rods (1D). Named sheet in both.
stepinteger1-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.
posnumberThe 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.
lengthnumberDistance the blade travels on this cut: the extent of the region crossed. Always 0 in 1D.
stageinteger1-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.

POST /v1/optimize/2d

{
  "parts": [
    {
      "name": "Door",
      "w": 600,
      "h": 400,
      "qty": 2,
      "edgeBanding": {
        "top": "ABS oak 22",
        "bottom": "ABS oak 22",
        "left": "ABS white 22",
        "right": "ABS white 22"
      }
    },
    { "name": "Shelf", "w": 800, "h": 300, "edgeBanding": { "top": "ABS oak 22" } }
  ],
  "stock": [{ "w": 2440, "h": 1220, "qty": 10, "price": 42 }],
  "options": { "kerf": 3 }
}
{
  "edgeBanding": {
    "totalMeters": 4.8,
    "byType": [
      { "reference": "ABS oak 22", "meters": 3.2 },
      { "reference": "ABS white 22", "meters": 1.6 }
    ],
    "byPart": [
      {
        "name": "Door",
        "meters": 4,
        "byType": [
          { "reference": "ABS oak 22", "meters": 2.4 },
          { "reference": "ABS white 22", "meters": 1.6 }
        ]
      },
      {
        "name": "Shelf",
        "meters": 0.8,
        "byType": [{ "reference": "ABS oak 22", "meters": 0.8 }]
      }
    ]
  }
}

material

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.

POST /v1/optimize/2d

{
  "parts": [
    { "name": "Door", "w": 600, "h": 400, "qty": 4, "material": "MDF 18" },
    { "name": "Shelf", "w": 800, "h": 300, "qty": 6, "material": "Oak 18" },
    { "name": "Back panel", "w": 1000, "h": 500, "qty": 2, "material": "Ply 6" }
  ],
  "stock": [
    { "w": 2440, "h": 1220, "qty": 10, "price": 42, "material": "MDF 18" },
    { "w": 2440, "h": 1220, "qty": 10, "price": 68, "material": "Oak 18", "priority": true }
  ],
  "options": { "kerf": 3 },
  "engine": "heuristic"
}
{
  "materials": [
    {
      "material": "MDF 18",
      "sheetCount": 1,
      "yieldPct": 32.25,
      "placed": 4,
      "total": 4,
      "totalPrice": 42
    },
    {
      "material": "Oak 18",
      "sheetCount": 1,
      "yieldPct": 48.37,
      "placed": 6,
      "total": 6,
      "totalPrice": 68
    }
  ],
  "unmatchedMaterials": [
    {
      "material": "Ply 6",
      "parts": [{ "name": "Back panel", "w": 1000, "h": 500, "qty": 2 }]
    }
  ]
}

materials[] — per-material rollup (2d · 1d · nest)

materialstringThe tag exactly as you sent it. Free text, matched exactly.
sheetCount | rodCountintegerStock consumed for this material — sheetCount on 2D and nest, rodCount on 1D.
yieldPct | densitynumberThis material’s own fill — yieldPct on the rectangular modes, density on nest (a polygon fill, not comparable to yieldPct).
placed, totalintegerPieces placed and requested for this material, after qty expansion.
totalPricenumberSum of the prices of the stock used for this material.

unmatchedMaterials[] — demand with no matching stock

materialstringThe tag that has no stock of its own anywhere in the request.
partsarrayThe 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.

1D — liniowe

POST /v1/optimize/1d

{
  "parts": [{ "name": "Rail", "length": 1200, "qty": 6 }],
  "stock": [{ "length": 3000, "qty": 5, "price": 12.5 }],
  "options": { "kerf": 3, "trim": { "start": 10, "end": 0 } }
}

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.

1D — odpowiedź

{
  "engine": "heuristic",
  "engineVersion": "1.0.0+10e0c941",
  "contractVersion": "1",
  "deterministic": true,
  "rods": [
    {
      "length": 3000,
      "price": 12.5,
      "remaining": 584,
      "parts": [
        { "name": "Rail", "pos": 10, "length": 1200 },
        { "name": "Rail", "pos": 1213, "length": 1200 }
      ],
      "offcuts": [584]
    },
    {
      "length": 3000,
      "price": 12.5,
      "remaining": 584,
      "parts": [
        { "name": "Rail", "pos": 10, "length": 1200 },
        { "name": "Rail", "pos": 1213, "length": 1200 }
      ],
      "offcuts": [584]
    },
    {
      "length": 3000,
      "price": 12.5,
      "remaining": 584,
      "parts": [
        { "name": "Rail", "pos": 10, "length": 1200 },
        { "name": "Rail", "pos": 1213, "length": 1200 }
      ],
      "offcuts": [584]
    }
  ],
  "metrics": {
    "rodCount": 3,
    "yieldPct": 80,
    "placed": 6,
    "total": 6,
    "cuts": 6,
    "totalPrice": 37.5,
    "toleranceAcceptedCount": 0
  },
  "unplaced": [],
  "warnings": [],
  "timing": { "solveMs": 2.19 },
  "cutPlan": [
    { "sheet": 0, "step": 1, "axis": "v", "pos": 1210, "length": 0, "stage": 1 },
    { "sheet": 0, "step": 2, "axis": "v", "pos": 2413, "length": 0, "stage": 1 },
    { "sheet": 1, "step": 1, "axis": "v", "pos": 1210, "length": 0, "stage": 1 },
    { "sheet": 1, "step": 2, "axis": "v", "pos": 2413, "length": 0, "stage": 1 },
    { "sheet": 2, "step": 1, "axis": "v", "pos": 1210, "length": 0, "stage": 1 },
    { "sheet": 2, "step": 2, "axis": "v", "pos": 2413, "length": 0, "stage": 1 }
  ]
}

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 — przekrój

POST /v1/optimize/wood

{
  "parts": [
    { "name": "Rafter", "sw": 50, "sh": 100, "length": 2400, "qty": 2 },
    { "name": "Noggin", "sw": 50, "sh": 100, "length": 600, "qty": 4 },
    { "name": "Beam", "sw": 50, "sh": 150, "length": 3000, "qty": 2 }
  ],
  "stock": [
    { "name": "C24 50x100", "sw": 50, "sh": 100, "length": 4000, "qty": 5, "price": 12.5 },
    { "name": "C24 50x150", "sw": 50, "sh": 150, "length": 4000, "qty": 3, "price": 18 }
  ],
  "options": { "kerf": 3 }
}

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.

Drewno — odpowiedź

{
  "engine": "heuristic",
  "engineVersion": "1.0.0+7c1f3a62",
  "contractVersion": "1",
  "deterministic": true,
  "sections": [
    {
      "section": "50x100",
      "sw": 50,
      "sh": 100,
      "stockName": "C24 50x100",
      "rods": [
        {
          "length": 4000,
          "price": 12.5,
          "remaining": 391,
          "parts": [
            { "name": "Rafter", "pos": 0, "length": 2400 },
            { "name": "Noggin", "pos": 2403, "length": 600 },
            { "name": "Noggin", "pos": 3006, "length": 600 }
          ],
          "offcuts": [391]
        },
        {
          "length": 4000,
          "price": 12.5,
          "remaining": 391,
          "parts": [
            { "name": "Rafter", "pos": 0, "length": 2400 },
            { "name": "Noggin", "pos": 2403, "length": 600 },
            { "name": "Noggin", "pos": 3006, "length": 600 }
          ],
          "offcuts": [391]
        }
      ],
      "metrics": {
        "rodCount": 2,
        "yieldPct": 90,
        "placed": 6,
        "total": 6,
        "cuts": 6,
        "totalPrice": 25
      },
      "unplaced": []
    },
    {
      "section": "50x150",
      "sw": 50,
      "sh": 150,
      "stockName": "C24 50x150",
      "rods": [
        {
          "length": 4000,
          "price": 18,
          "remaining": 997,
          "parts": [{ "name": "Beam", "pos": 0, "length": 3000 }],
          "offcuts": [997]
        },
        {
          "length": 4000,
          "price": 18,
          "remaining": 997,
          "parts": [{ "name": "Beam", "pos": 0, "length": 3000 }],
          "offcuts": [997]
        }
      ],
      "metrics": {
        "rodCount": 2,
        "yieldPct": 75,
        "placed": 2,
        "total": 2,
        "cuts": 2,
        "totalPrice": 36
      },
      "unplaced": []
    }
  ],
  "unmatched": [],
  "metrics": {
    "sectionCount": 2,
    "rodCount": 4,
    "yieldPct": 82.5,
    "placed": 8,
    "total": 8,
    "cuts": 8,
    "totalPrice": 61,
    "toleranceAcceptedCount": 0
  },
  "unplaced": [],
  "warnings": [],
  "timing": { "solveMs": 2.96 },
  "cutPlan": [
    {
      "section": "50x100",
      "sheet": 0,
      "step": 1,
      "axis": "v",
      "pos": 2400,
      "length": 0,
      "stage": 1
    },
    {
      "section": "50x100",
      "sheet": 0,
      "step": 2,
      "axis": "v",
      "pos": 3003,
      "length": 0,
      "stage": 1
    },
    {
      "section": "50x100",
      "sheet": 0,
      "step": 3,
      "axis": "v",
      "pos": 3606,
      "length": 0,
      "stage": 1
    },
    {
      "section": "50x100",
      "sheet": 1,
      "step": 1,
      "axis": "v",
      "pos": 2400,
      "length": 0,
      "stage": 1
    },
    {
      "section": "50x100",
      "sheet": 1,
      "step": 2,
      "axis": "v",
      "pos": 3003,
      "length": 0,
      "stage": 1
    },
    {
      "section": "50x100",
      "sheet": 1,
      "step": 3,
      "axis": "v",
      "pos": 3606,
      "length": 0,
      "stage": 1
    },
    {
      "section": "50x150",
      "sheet": 0,
      "step": 1,
      "axis": "v",
      "pos": 3000,
      "length": 0,
      "stage": 1
    },
    {
      "section": "50x150",
      "sheet": 1,
      "step": 1,
      "axis": "v",
      "pos": 3000,
      "length": 0,
      "stage": 1
    }
  ]
}

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)

sectionsarrayReplaces rods at the top level: one entry per cross-section, each matched to its own stock and solved on its own.
unmatchedarrayDemand 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.
unplacedarrayDid-not-fit demand, aggregated across the sections that DID have stock. Same shape as 1D.
metricsobjectJob-wide totals across every section (below).
cutPlanarray | nullAs 1D, except every step also carries section, and sheet is the rod index WITHIN that section, not a job-wide counter.
csv, dxfstringInline export, present only when include names the token. svg is 2D-only — a wood request asking for it gets a warning instead.

sections[]

sectionstringNormalised cross-section key, e.g. "50x100" — short side first, so 50×100 and 100×50 are one section.
sw, shnumberThe two cross-section sides: sw the SHORT one, sh the long one, whatever order they arrived in.
stockNamestring | nullThe name of the stock row this section was matched to, or null when that row carried none.
materialstringPresent only when the section carries a material — an oak 50×100 and a pine 50×100 are two sections. (OPEN-256)
rodsarrayIdentical in shape to the 1D rods[] above, meta and offcuts included.
metricsobjectThis section’s own totals: rodCount, yieldPct, placed, total, cuts, totalPrice — so the per-section figures need no recomputation.
unplacedarrayThis section’s parts that had stock and still did not fit.

metrics (wood)

sectionCountintegerCross-sections solved. Equals sections.length.
rodCountintegerBars used across every section.
yieldPctnumberPlaced length ÷ total FULL bar length × 100 over the whole job, 2 decimals.
placedintegerPieces placed, after qty expansion.
totalintegerPieces REQUESTED, after qty expansion — unmatched ones included.
cutsintegerCrosscuts across every used bar.
totalPricenumberSum of the used bars’ prices, 2 decimals.
toleranceAcceptedCountintegerPieces that fitted only because options.tolerance allowed an overshoot.

unmatched[]

sectionstringThe cross-section key nothing in stock matched.
sw, shnumberThat cross-section’s two sides.
materialstringPresent when the cross-section DOES exist in stock but only in a different material. (OPEN-256)
partsarrayThe demand rows in that section, in the 1D unplaced shape: name, length, qty.
qtyintegerTotal pieces in this section that had no stock at all.

Nesting kształtów rzeczywistych

POST /v1/optimize/nest

{
  "parts": [
    {
      "name": "bracket",
      "polygon": [[0, 0], [300, 0], [300, 100], [100, 100], [100, 300], [0, 300]],
      "qty": 4,
      "allowedRotations": [0, 90, 180, 270]
    },
    {
      "name": "gusset",
      "polygon": [[0, 0], [280, 0], [0, 280]],
      "qty": 4,
      "allowedRotations": [0, 90, 180, 270]
    }
  ],
  "stock": [
    {
      "w": 2440,
      "h": 1220,
      "price": 12.5,
      "exclusions": [{ "polygon": [[0, 0], [300, 0], [0, 300]], "quality": 0 }]
    }
  ]
}

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)

partsarrayOne or more NestPart (see below). Required.
stockarrayOne or more NestStock sheet types (see below). Required.
optionsobjectSolve options (see below). Optional.
enginestring"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".
includestring[]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)

polygonnumber[][]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.
sourceobjectOPEN-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.
holesnumber[][][]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.
qtyintegerCopies to place. Default 1.
allowedRotationsnumber[] | "continuous"Allowed rotations in DEGREES (e.g. [0,90,180,270]). Omit or "continuous" for free rotation.
minQualityintegerThe 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.
prioritybooleanMust-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.
materialstringOPEN-256 — a part of material X nests only on material-X sheets; the job is partitioned by material.
namestringOptional label, echoed on every placement. Defaults to "Part <1-based row index>".
metaobjectOPEN-224 — opaque JSON (your ERP ids), echoed verbatim on every placed copy. Never affects the layout.

stock[] (NestStock)

w, hnumberRectangular sheet size. Give w & h OR polygon, not both.
polygonnumber[][]Arbitrary sheet outline (an off-cut remnant, a non-rectangular board); overrides w/h.
qtyintegerDefault 1. A HARD cap only when options.respectStock is true.
pricenumberPer-sheet price. Summed into totalPrice and stockUsage[].totalPrice, and the ranking key of options.minimizeCost.
costintegerInteger relative per-sheet cost, used by options.minimizeCost for a row that has no price (e.g. when price is not money).
exclusionsobject[]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.
materialstringOPEN-256 — this sheet serves only material-matching parts.
metaobjectOPEN-224 — echoed on every sheet cut from this stock row.

options (NestOptions)

minSeparationnumberMinimum 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.
edgeClearancenumberMinimum 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.
seedintegerDeterminism: a fixed seed → a reproducible layout. Omit and the server pins a fixed default so the response stays reproducible.
minimizeCostbooleanHow "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.
respectStockbooleanTreat each stock qty as a hard cap — on both engines; the mixed-size re-nest never uses a row more often than its qty.
simplifyTolerancenumberPolygon simplification tolerance (max area deviation as a fraction). Speeds up dense DXF outlines with hundreds of vertices. 0 disables.
compactbooleanDefault 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.
timeBudgetMsintegerSearch 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.

Nest — odpowiedź

{
  "engine": "lbf",
  "engineVersion": "1.0.0+nest-d4046d5-3780cda5",
  "contractVersion": "1",
  "deterministic": true,
  "sheets": [
    {
      "w": 2440,
      "h": 1220,
      "stock": 0,
      "price": 12.5,
      "density": 0.1217,
      "cutLength": 8623.919,
      "pierces": 8,
      "usedWidth": 580.204,
      "usedHeight": 1102.332,
      "usedArea": 639577.44,
      "parts": [
        { "name": "bracket", "sheet": 0, "x": 300.003, "y": 400.226, "rotation": -180 },
        { "name": "bracket", "sheet": 0, "x": 300.133, "y": 700.617, "rotation": -180 },
        { "name": "bracket", "sheet": 0, "x": 300.061, "y": 1000.969, "rotation": -180 },
        { "name": "bracket", "sheet": 0, "x": 400.095, "y": 1102.332, "rotation": -180 },
        { "name": "gusset", "sheet": 0, "x": 530.102, "y": 50.205, "rotation": 90 },
        { "name": "gusset", "sheet": 0, "x": 300.058, "y": 380.171, "rotation": -90 },
        { "name": "gusset", "sheet": 0, "x": 580.15, "y": 380.193, "rotation": 90 },
        { "name": "gusset", "sheet": 0, "x": 300.204, "y": 660.982, "rotation": -90 }
      ],
      "exclusions": [{ "polygon": [[0, 0], [300, 0], [0, 300]], "quality": 0 }]
    }
  ],
  "metrics": {
    "sheetCount": 1,
    "density": 0.1217,
    "placed": 8,
    "total": 8,
    "totalPrice": 12.5,
    "cutLength": 8623.919,
    "pierces": 8
  },
  "stockUsage": [
    {
      "stock": 0,
      "w": 2440,
      "h": 1220,
      "sheetCount": 1,
      "totalPrice": 12.5,
      "density": 0.1217,
      "usedArea": 639577.44
    }
  ],
  "unplaced": [],
  "warnings": [],
  "timing": { "solveMs": 26.34 }
}

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

enginestringWhich nesting engine served the request: "lbf", or "max" in the result of a finished async max job.
engineVersionstringThe nesting engine's algorithm identity (jagua-rs revision + build hash). Versions independently of the rectangular engines.
contractVersionstringNest contract version, currently "1". Versions independently of the /v1/ rectangular contract — it is a different path and engine family.
deterministicbooleantrue 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.
sheetsarrayOne entry per used sheet.
metricsobjectJob totals (see below).
stockUsagearraySheets used per request stock ROW (see below). Always present.
unplacedarrayDemand that could not be placed — a plan-plus-warning, not an error.
warningsstring[]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).
materialsarrayOPEN-256 per-material rollup — present only when parts/stock carry material.
unmatchedMaterialsarrayParts whose material has no matching stock — present only when it happens.
importedobjectOPEN-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, dxfstringThe achieved nest as an inline drawing — present ONLY when include contains that token. svg is self-contained (no external refs); dxf is R12/AC1009.
timingobject{ solveMs: number } — the solve time; environment-dependent.

sheets[] (nest)

w, hnumberPresent for rectangular sheets — always the real sheet size, also with edgeClearance.
polygonnumber[][]Present for arbitrary-outline sheets instead of w/h.
stockintegerIndex into the request stock[] this sheet was cut from — the key to tie the sheet back to your stock row.
pricenumber | nullThe stock row's price, or null.
densitynumberThis sheet's fill = placed polygon area / sheet area.
cutLengthnumberΣ 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).
piercesintegerPierce points on this sheet: one per closed contour (each placed part's outline + one per hole).
usedWidth, usedHeightnumberOPEN-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.
usedAreanumberusedWidth × usedHeight, 2 decimals.
partsarrayPlacements on this sheet (see below).
exclusionsobject[]The zones that applied to this sheet, echoed for rendering.
materialstringPresent when the sheet carried a material (OPEN-256).
metaobjectEchoed from the stock row's meta (OPEN-224).

sheets[].parts[] (nest — placed)

namestringThe requested name, or the generated default.
sheetinteger0-based index into sheets.
x, ynumberTranslation, applied AFTER rotation about the part's origin.
rotationnumber⚠️ 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.
materialstringThe material this copy nested from (OPEN-256).
metaobjectThe part's opaque passthrough (OPEN-224).

metrics (nest)

sheetCountintegerSheets used. Equals sheets.length. ⚠️ The honest, cross-comparable metric between nesting and the rectangular modes.
densitynumber⚠️ 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.
placedintegerPart copies placed, after qty expansion.
totalintegerPart copies requested, after qty expansion.
totalPricenumberSum of used sheet prices.
cutLengthnumberΣ sheets[].cutLength — placed parts only (outlines + holes), in your unit.
piercesintegerΣ sheets[].pierces.

stockUsage[] (nest)

stockintegerIndex 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, hnumberPresent for rectangular rows.
materialstringPresent when the row carries a material.
sheetCountintegerSheets cut from this row.
totalPricenumberSum of those sheets' prices (0 for a row without a price, like metrics.totalPrice).
densitynumberPlaced polygon area / total area of this row's sheets.
usedAreanumberOPEN-298 — Σ sheets[].usedArea of this row's sheets: the used bounding area to total per sheet size.

unplaced[] (nest)

namestringThe part name.
qtyintegerHow many copies could not be placed.

rods[]

lengthnumberFULL bar length as supplied in stock.
pricenumber | nullPrice of the stock row, or null.
remainingnumberUSABLE 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.
partsarrayPlacements, in cutting order along the bar.
offcutsnumber[]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.
metaobjectPresent only when the stock row carried meta — echoed from stock[].meta (OPEN-224). Applies to 1D and wood rods.

rods[].parts[]

namestringThe requested name, or the generated default.
posnumberOffset of the part’s NEAR end from the bar end that trim.start trims.
lengthnumberThe part length, as requested.
metaobjectPresent only when the part carried meta — echoed from parts[].meta (OPEN-224).

metrics (1D)

rodCountintegerBars used. Equals rods.length.
yieldPctnumberPlaced length ÷ total FULL bar length × 100, 2 decimals. Trim and kerf count as waste.
placedintegerPieces placed, after qty expansion.
totalintegerPieces requested, after qty expansion.
cutsintegerTotal crosscuts across all used bars — one per placed piece.
totalPricenumberSum of used bar prices, 2 decimals.
toleranceAcceptedCountintegerPieces that fitted only because options.tolerance allowed an overshoot.

unplaced[] (1D)

namestringThe part name.
lengthnumberAs requested.
qtyintegerHow 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.

POST /v1/optimize/nest

{
  "parts": [
    {
      "source": {
        "content": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 400 200\">\n  <path d=\"M0 0 H120 V80 H0 Z\"/>\n  <path d=\"M40 30 H80 V50 H40 Z\"/>\n</svg>",
        "filename": "washer-plate.svg"
      },
      "qty": 4,
      "meta": { "erpId": "ART-8891" }
    }
  ],
  "stock": [{ "w": 2440, "h": 1220 }]
}

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.

{
  "imported": { "parts": 1, "vertices": 8, "units": [] },
  "parts_after_import": [
    {
      "name": "washer-plate",
      "polygon": [[0, 0], [120, 0], [120, 80], [0, 80]],
      "holes": [[[40, 30], [80, 30], [80, 50], [40, 50]]],
      "qty": 4,
      "meta": { "erpId": "ART-8891" }
    }
  ]
}

parts[].source — geometry from an SVG / DXF file

contentstringThe 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.
filenamestringUsed 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.
flattenTolerancenumberCurve 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)

partsintegerPart ROWS whose geometry came from a file.
verticesintegerTotal vertices those files produced after flattening, outlines and holes together — the number to watch against the per-ring cap.
unitsstring[]⚠️ 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.

POST /v1/import/nest
Authorization: Bearer co_live_…

{
  "content": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 400 200\">\n  <path d=\"M0 0 H120 V80 H0 Z\"/>\n  <path d=\"M40 30 H80 V50 H40 Z\"/>\n</svg>",
  "filename": "washer-plate.svg"
}
{
  "format": "svg",
  "parts": [
    {
      "name": "washer-plate",
      "polygon": [[0, 0], [120, 0], [120, 80], [0, 80]],
      "holes": [[[40, 30], [80, 30], [80, 50], [40, 50]]]
    }
  ],
  "vertices": 8,
  "warnings": [],
  "engineEnabled": true,
  "contractVersion": "1"
}

POST /v1/import/nest — request

contentstringThe 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.
filenamestringUsed for detection and to NAME the results: a file with one outline keeps the bare name, several are numbered "<name> 1", "<name> 2", …
flattenTolerancenumberAs 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.
verticesintegerTotal 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.
warningsstring[]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.
engineEnabledbooleanWhether 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.
contractVersionstringNest 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.

fieldtypemeaning
jobIdstringThe 202 body's id. Poll GET /v1/jobs/{id}.
statusstringqueued → running → succeeded | failed.
mode · enginestring"2d" (submitted to POST /v1/optimize/2d) or "nest" (submitted to POST /v1/optimize/nest), and always "max".
pollAfterMsinteger202 only — suggested delay before the first poll.
resultobjectPresent once succeeded — the same shape as the synchronous response of that mode: a 2D plan, or a nest.
errorstringPresent once failed — the reason.
quotaobject202 only — reserved, used and limit: the one call metered at submission, and where the ACCOUNT stands this month.
createdAtstringPoll only — when the job was submitted (ISO-8601).
finishedAtstring | nullPoll 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

400invalid_requestBłąd schematu. details.path wskazuje pole, które go wywołało.
401unauthorizedBrakujący lub nieznany klucz API.
402quota_exceededWyczerpany miesięczny limit. Retry-After podaje liczbę sekund do początku nowego miesiąca.
403key_revokedKlucz 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.
404not_foundNie ma takiej trasy — to samo otrzymasz, gdy ścieżka jest poprawna, ale metoda nie.
413too_largeDane wejściowe przekraczają limit (patrz Limity).
429busyChwilowy 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.
500internalNieoczekiwany błąd albo backend uwierzytelniania jest nieosiągalny (zapytania są wtedy odrzucane — fail closed).
503service_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.
504solve_timeoutObliczenia 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" }
}
HTTP/1.1 402 Payment Required
Retry-After: 41400
Content-Type: application/json; charset=utf-8

{ "error": "quota_exceeded", "message": "monthly quota exhausted" }
  • 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-LimitintegerThe ACCOUNT’s monthly quota — the same number GET /v1/usage returns as limit, not a per-key cap (049).
X-RateLimit-RemainingintegerCalls left this month on the ACCOUNT, after this one.
Retry-AfterintegerSeconds to wait. Sent with 402 and 429 only.

GET /v1/usage

{
  "plan": "studio",
  "used": 137,
  "limit": 10000,
  "remaining": 9863,
  "periodEnd": "2026-08-01",
  "keyPrefix": "co_live_ab12",
  "contractVersion": "1"
}

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.

planstringTier slug frozen onto the key when it was created.
usedintegerCalls 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).
limitintegerThe 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.
remainingintegerlimit − used, never negative.
periodEndstringReset day as YYYY-MM-DD — a date, not a timestamp.
keyPrefixstringNon-secret display prefix of the calling key.
contractVersionstringShape version. Currently "1".

GET /v1/health

{
  "status": "healthy",
  "service": "cutoptim-engine",
  "contractVersion": "1",
  "engineVersion": "1.0.0+10e0c941",
  "engines": ["heuristic", "balanced"],
  "modes": ["2d", "1d", "wood", "nest"],
  "nestEngines": ["lbf", "max"],
  "maxEngines": ["max"],
  "uptimeSec": 16
}

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.

statusstringAlways "healthy" when the process answers.
servicestringAlways "cutoptim-engine".
contractVersionstringShape version. Currently "1".
engineVersionstringThe DEFAULT engine’s version, not a per-engine list.
enginesstring[]SYNCHRONOUS engine ids this deployment accepts in engine — ["heuristic","balanced"]. The async max tier is reported separately in maxEngines, never here.
modesstring[]Optimize paths this deployment serves: "2d", "1d", "wood", plus "nest" only where the nesting engine is built in. Endpoint discovery without reading this page.
nestEnginesstring[]Nesting engine ids this deployment can serve — ["lbf"] on the production API, [] where the Rust nesting stage is not built in.
maxEnginesstring[]The async tree-search tier — ["max"] where it is enabled, [] otherwise. Health never advertises a capability it cannot serve.
uptimeSecintegerWhole 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.

curl -s https://cutoptim.com/engine/openapi.json > cutoptim-engine.json

Otwórz dokument OpenAPI 3.1 →

Materiał do pobrania
Engine API one-pager

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.

PDF2 pagesFree
Pobierz PDF