Sari la conținutul principal
← CutOptim Engine API

Documentație API

URL de bază https://api.cutoptim.com · versiunea contractului v1

Creează o cheie în panoul de control, apoi trimite-o ca Authorization: Bearer <key>.

Specificația OpenAPI: /engine/openapi.json

Endpointuri

POST/v1/optimize/2doptimizare 2D pentru panouri
POST/v1/optimize/1doptimizare 1D / liniară (bare, profile, țeavă)
POST/v1/optimize/woodoptimizarea lemnului — 1D cu potrivirea secțiunii
POST/v1/optimize/nestnesting true-shape — poligoane neregulate pe plăci fixe, cu zone de excludere (laser / plasmă / jet de apă)
POST/v1/validate/2dvalidează o cerere fără s-o rezolve — gratuit, fără cheie, fără cotă (2d / 1d / wood / nest)
POST/v1/validate/1dvalidează o cerere fără s-o rezolve — gratuit, fără cheie, fără cotă (2d / 1d / wood / nest)
POST/v1/validate/woodvalidează o cerere fără s-o rezolve — gratuit, fără cheie, fără cotă (2d / 1d / wood / nest)
POST/v1/validate/nestvalidează o cerere fără s-o rezolve — gratuit, fără cheie, fără cotă (2d / 1d / wood / nest)
GET/v1/jobs/{id}interoghează o lucrare asincronă a motorului max — returnează status-ul ei și, odată finalizată, planul (fără cotă; doar propriile lucrări)
POST/v1/import/nestcitește conturul pieselor dintr-un fișier SVG sau DXF — necesită o cheie, nu consumă cotă
GET/v1/usageconsumul și cota CONTULUI în luna curentă
GET/v1/healthverificare de disponibilitate — fără cheie, fără limitare de rată, fără bază de date

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.

Unități de măsură

API-ul este agnostic față de unitatea de măsură. Alege o singură unitate — milimetri, inch, orice — folosește-o pentru fiecare număr pe care îl trimiți, iar fiecare număr pe care îl primești înapoi este exprimat în aceeași unitate. Nimic nu este convertit pe server și niciun nume de câmp nu impune o unitate.

Acest lucru acoperă dimensiunile pieselor și ale materialului de bază, kerf, tolerance, trim și minOffcut la intrare, precum și fiecare coordonată, poziție, lungime rămasă, rest și lungime de tăiere la ieșire. Amestecarea unităților în cadrul aceleiași cereri produce un plan care trece validarea, dar este greșit din punct de vedere fizic, iar serverul nu poate detecta asta.

Sistemul de coordonate

Originea este colțul din stânga sus al plăcii: x crește spre dreapta, pe lățimea plăcii, iar y crește în jos, pe înălțimea plăcii. Valorile x și y ale unei piese indică colțul ei din stânga sus, iar w și h sunt dimensiunile așa cum a fost plasată — deja interschimbate atunci când rotated este true — astfel încât dreptunghiul x, y, w, h este amprenta pe placă, fără niciun calcul suplimentar. Dreptunghiurile resturilor folosesc același sistem.

Tăierea de margine deplasează plasările: trim.left împinge fiecare piesă spre dreapta, iar trim.top împinge fiecare piesă în jos, deoarece piesele sunt aranjate în interiorul zonei utilizabile și apoi decalate înapoi pe placa întreagă. trim.right și trim.bottom micșorează zona utilizabilă fără să mute originea. Valorile w și h ale unei plăci sunt întotdeauna dimensiunile complete ale materialului, inclusiv tăierea de margine — și tocmai de aceea tăierea de margine se numără ca deșeu în yieldPct.

2D — cerere

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"]
}

Câmpurile pieselor

w, hobligatorii — dimensiunile piesei
qtyimplicit 1 — expandat pe server; se numără în plafonul de 2,000 piese
nameetichetă opțională, returnată la fiecare plasare
rotatableimplicit true — poate fi rotită piesa la 90°
grainGroupmembrii unui grup sunt păstrați pe aceeași placă (potrivirea fibrei)
priorityde tăiat obligatoriu: câștigă spațiu pe placă atunci când stocul este plafonat (împreună cu respectStock)
edgeBandingcantuire pe fiecare latură: denumește o referință de tip pe oricare dintre top / right / bottom / left (un șir liber, codul tău) — răspunsul însumează metrii pe referință. Doar metadate, nu mută niciodată o piesă. Doar 2D
materialetichetă de material (un șir liber, codul tău): piesele și materialul de bază cu același material sunt aranjate doar împreună. Spre deosebire de edgeBanding, schimbă aranjamentul. Absent = un singur bazin nespecificat. Funcționează în fiecare mod

Câmpurile materialului de bază

w și h sunt obligatorii. qty este implicit 1 și devine plafon strict doar împreună cu respectStock. price este per placă și determină totalPrice și modul cost. priority (boolean) folosește acest material de bază cu prioritate; material (un șir liber) îl restrânge la piesele cu același material.

Opțiuni

kerflățimea lamei (implicit 0)
toleranceacceptă tăieri care depășesc cu până la această valoare
trimtăiere de margine pe fiecare latură: left, right, top, bottom
firstCut'auto' | 'horizontal' | 'vertical' (implicit 'auto')
minimizeCostordonează variantele după cel mai mic preț total al materialului; cu mai multe dimensiuni de material cu preț, le combină (2D) sau alege lungimea cea mai ieftină (1D) pentru a reduce costul, chiar dacă astfel se folosește mai mult material
respectStocktratează qty de pe fiecare rând de material ca plafon strict
minOffcutraportează doar resturile a căror latură scurtă atinge cel puțin această valoare
maxCutStageslimită de etape pentru ferăstrăul de panouri — un număr de faze, nu adâncimea brută a arborelui
minimizeRotationspreferă aranjamentele care rotesc mai puține piese
effort'fast' | 'balanced' (implicit 'balanced'). Adâncimea căutării: 'balanced' rulează best-of-ul complet cu mai multe strategii; 'fast' sare peste singura căutare costisitoare de combinații de arii per placă — considerabil mai rapid la lucrările mari, cu prețul câtorva puncte de utilizare, rămâne ghilotină-valid și niciodată mai dens decât 'balanced'. La lucrările mici, rezultatul este de obicei identic. Doar motorul heuristic

include face două lucruri. RESTRÂNGERE — "cutPlan" și "offcuts" sunt active implicit; un array prezent păstrează doar tokenurile de restrângere pe care le listează (un array gol le elimină pe amândouă). EXPORT ADIȚIONAL — "svg", "csv" și "dxf" adaugă fiecare acel export în răspuns ca ȘIR: svg un desen de layout 2D de sine stătător (doar 2D — o cerere 1d/wood returnează în schimb un avertisment), dxf un desen R12/AC1009 pe straturile STOCK/PARTS/LABELS, csv o listă de tăiere. Tokenurile de export nu afectează restrângerea, așa că include:["svg"] adaugă svg și — nedenumind niciun token de restrângere — elimină cutPlan/offcuts; folosește ["cutPlan","offcuts","svg"] pentru a păstra totul și a adăuga 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.

Opțiunea effort echilibrează timpul de calcul cu utilizarea. Iată acest compromis, măsurat pe o lucrare solicitantă — fiecare cifră provine din packerul real.

Comutatorul effort: utilizarea materialului vs timpul de calculComutatorul effort: utilizarea materialului vs timpul de calcul. fast: 350 plăci · 76.2% · ≈1.9 s. balanced: 330 plăci · 80.8% · ≈4.8 s. max: rezervat — mai dens = căutare mai lentă. La majoritatea lucrărilor (mai mici) cele două sunt identice; diferența apare doar la lucrările mari ca aceasta. balanced este valoarea implicită și nu este niciodată mai dens decât poate atinge fast.Comutatorul effort: utilizarea materialului vs timpul de calculO lucrare solicitantă — circa 1.550 de piese pe o placă de 2,07 × 5,6 m. Fiecare cifră măsurată pe packerul real.74%76%78%80%82%84%02 s4 s6 stimp de calcul · mai rapid →utilizarea materialului · mai dens ↑⇄ comutatorul effort+4,6 pp utilizare · −20 plăci−5,7% material · ≈2,5× mai lentfast350 plăci · 76.2% · ≈1.9 s★ balanced · implicit330 plăci · 80.8% · ≈4.8 smaxrezervatmai dens =căutare mai lentă
La majoritatea lucrărilor (mai mici) cele două sunt identice; diferența apare doar la lucrările mari ca aceasta. balanced este valoarea implicită și nu este niciodată mai dens decât poate atinge fast.

Și oricum este rapid: chiar și cele mai mari lucrări de producție — 2.000 de piese și mai mult — se rezolvă în câteva secunde pe motorul implicit, confortabil în bugetul de timp al API-ului.

2D — răspuns

{
  "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 unifică tăierile coliniare (o singură reglare a opritorului); sawPasses numără fiecare trecere. Două măsuri oneste ale aceluiași plan, nu o pretenție de a coincide cu numărul raportat de vreun concurent.
  • guillotineValid / cutPlan — Când un aranjament nu poate fi tăiat de la o margine la alta, guillotineValid este false și cutPlan este null. Aceasta este o informație reală — nu se poate realiza pe un ferăstrău pentru panouri — nu o eroare.
  • unplaced + warnings — O lucrare imposibil de satisfăcut returnează 200, cu piesele listate în unplaced și o notă în warnings. Un plan cu care poți lucra este mai util decât un cod de stare.
  • edgeBanding — Când o piesă poartă edgeBanding, răspunsul adaugă un bloc edgeBanding: metrii liniari pe care îi consumă fiecare referință de tip, per piesă și ca total pe comandă. Este geometrie exactă, fără adaos pentru pierderi — atelierul îl adaugă singur — și presupune intrare în milimetri (÷1000 pentru metri). Cheia lipsește complet pentru o lucrare fără cantuire.
  • materials / unmatchedMaterials — Când o piesă sau un material de bază poartă material, răspunsul adaugă materials (un rezumat per material — material, sheetCount/rodCount, yieldPct, placed, total, totalPrice) și unmatchedMaterials (cererea al cărei material nu are material de bază potrivit). La lemn, materialul călătorește în schimb pe fiecare secțiune de secțiune transversală. Ambele chei lipsesc pentru o lucrare fără material, care rămâne identică la nivel de octet.

Câmpurile răspunsului

Numele și tipurile câmpurilor fac parte din contract, așa că tabelele de mai jos rămân în engleză în toate limbile — un nume de câmp tradus ar documenta un API care nu există.

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

Când o piesă poartă edgeBanding, răspunsul adaugă un bloc edgeBanding: metrii liniari pe care îi consumă fiecare referință de tip, per piesă și ca total pe comandă. Este geometrie exactă, fără adaos pentru pierderi — atelierul îl adaugă singur — și presupune intrare în milimetri (÷1000 pentru metri). Cheia lipsește complet pentru o lucrare fără cantuire.

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

Când o piesă sau un material de bază poartă material, răspunsul adaugă materials (un rezumat per material — material, sheetCount/rodCount, yieldPct, placed, total, totalPrice) și unmatchedMaterials (cererea al cărei material nu are material de bază potrivit). La lemn, materialul călătorește în schimb pe fiecare secțiune de secțiune transversală. Ambele chei lipsesc pentru o lucrare fără material, care rămâne identică la nivel de octet.

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 — ce înseamnă fizic un pas

Un step este o singură mișcare a lamei, iar lista este exact în ordinea în care poți tăia efectiv: o tăiere părinte înaintea tăierilor din interiorul bucății pe care a produs-o, pentru că nu poți reteza transversal o fâșie înainte de a o fi desprins. axis "h" înseamnă că lama se deplasează pe direcția x și separă partea de sus de cea de jos; axis "v" înseamnă că se deplasează pe direcția y și separă stânga de dreapta. pos este muchia lamei cu coordonata MAI MICĂ — valoarea y pentru "h", valoarea x pentru "v" — nu axa ei mediană: banda de kerf ocupă intervalul de la pos la pos + kerf, deci lama mușcă în direcția în care crește coordonata, adică în jos pentru "h" și spre dreapta pentru "v". Materialul aflat pe partea cu coordonata mai mică a liniei — deasupra ei pentru "h", la stânga ei pentru "v" — este bucata pe care o eliberează acea tăiere. length arată cât parcurge lama la acea singură tăiere: întinderea zonei pe care o traversează, nu lățimea întregii plăci.

stage este o trecere a mașinii. Începe de la 1 și crește doar atunci când axa se schimbă față de tăierea părinte, așa că despicarea unei plăci în șase fâșii este o singură etapă, iar retezarea lor transversală este următoarea. Acesta este sensul în care se vorbește despre „tăiere în trei etape” la ferăstrăul pentru panouri, nu adâncimea arborelui de tăiere, și este exact ceea ce limitează maxCutStages. sheet este un index în sheets care pornește de la 0, iar step reîncepe de la 1 pe fiecare placă, în loc să continue pe toată lucrarea.

cutPlan este null — nu lipsește, nu este gol — de fiecare dată când guillotineValid este false: un aranjament care nu poate fi tăiat de la o margine la alta nu are nicio secvență de tăiere de returnat. Lipsește complet din payload dacă l-ai exclus din include.

1D — liniar

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 } }
}

Piesele primesc length (plus qty, name, priority); materialul de bază primește length, qty și price. Atât piesele, cât și materialul de bază acceptă în plus o etichetă opțională material (materialul de bază și priority) — material restrânge o piesă la material de bază cu același material, iar răspunsul adaugă atunci materials și unmatchedMaterials, ca la 2D. Opțiunile sunt kerf, tolerance, trim.start / trim.end, minimizeCost, respectStock și minOffcut. Răspunsul returnează rods în loc de sheets, fiecare cu piesele sale (parts), lungimea rămasă și resturile (offcuts).

1D — răspuns

{
  "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 înlocuiește sheets și nu există guillotineValid, pentru că o tăiere liniară poate fi realizată întotdeauna. Valoarea pos a fiecărei piese este distanța de la capătul ei apropiat până la capătul barei pe care îl retează trim.start, așa că prima piesă începe exact la trim.start, iar fiecare pos următor adaugă câte un kerf. remaining este restul UTILIZABIL: kerf-ul tăierii care îl desprinde de ultima piesă este deja scăzut, deci este lungimea recuperabilă, nu golul brut. Este raportat pentru fiecare bară, chiar și atunci când se află sub minOffcut — minOffcut filtrează doar lista offcuts, care conține cel mult o intrare. În planul de tăiere, sheet este indexul barei, axis este întotdeauna "v", stage este întotdeauna 1 și length este întotdeauna 0: o retezare de bară nu are o distanță de parcurs de raportat, motiv pentru care metrics din 1D conține cuts, dar nu cutLength.

Lemn — secțiune

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 }
}

Lemnul are o identitate pe care o bară simplă nu o are: o piesă 50×150 nu poate ieși din material 50×100, oricâtă lungime ar rămâne. De aceea piesele și materialul poartă sw și sh, cele două laturi ale secțiunii, în orice ordine — 50×100 și 100×50 sunt aceeași grindă întoarsă și formează o singură secțiune. Lucrarea se împarte pe secțiuni, fiecare secțiune este potrivită cu materialul ei și rezolvată separat, iar un singur apel returnează totul. Piesele și materialul de bază acceptă în plus o etichetă opțională material (materialul de bază și priority): cu ea, un stejar 50×100 și un pin 50×100 devin două secțiuni separate, iar fiecare secțiune poartă materialul ei. Opțiunile sunt aceleași ca la 1D.

Lemn — răspuns

{
  "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 înlocuiește rods la nivelul de sus: fiecare intrare este o secțiune cu propriile rods (identice ca formă cu cele din 1D) și propriile metrics, așa că cifrele pe material există fără să le recalculezi. unmatched nu are echivalent în 1D — este cererea pentru a cărei secțiune nu ai furnizat deloc material, o problemă diferită de unplaced (piese care aveau material și nu au încăput) și cu altă rezolvare, de aceea cele două nu se amestecă niciodată. metrics.total numără fiecare piesă cerută, inclusiv cele din unmatched. În planul de tăiere fiecare pas își indică și section, iar sheet este indexul barei ÎN INTERIORUL acelei secțiuni, nu un contor pe toată lucrarea.

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 true-shape

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 }]
    }
  ]
}

Cele trei moduri de mai sus împachetează dreptunghiuri. POST /v1/optimize/nest împachetează POLIGOANE ARBITRARE: o piesă este un contur (polygon, cu holes interioare opționale), nu o lățime×înălțime, așa că piesele se întrepătrund în buzunarele concave ale celorlalte, iar golul de aer pe care îl irosește un bounding box este recuperat — pe o lucrare reprezentativă, 6 sheets acolo unde aceleași piese după bounding box au nevoie de 9. Este o clasă diferită de algoritm (un motor geometric de coliziune, nu packerul ghilotină), pentru tăiere cu laser, plasmă și jet de apă. Vin la pachet două lucruri pe care API-ul dreptunghiular nu le poate exprima: zone de excludere per placă (stock[].exclusions — un defect, amprenta unei cleme, o zonă pretipărită; o zonă cu quality 0 este o regiune interzisă oricărei piese) și holes true-shape. Partiționarea pe material și transmiterea meta funcționează ca peste tot. Spațierea se setează de două ori: options.minSeparation între piese și options.edgeClearance la marginea plăcii (implicit este egală cu minSeparation). Cu mai multe dimensiuni de material de bază pentru un material, ambele motoare le compară și păstrează cel mai bun plan — după aria plăcii, sau după preț cu minimizeCost — și pot amesteca dimensiunile (o ultimă placă pe jumătate goală trece pe o dimensiune mai mică), așa că rezultatul nu depinde de ordinea în care le listezi. Motorul implicit, lbf, răspunde instantaneu; engine "max" rulează aceeași lucrare asincron și caută un aranjament cu mai puține plăci (vezi Lucrări asincrone). Exemplul de mai jos este un apel real capturat — opt piese pe o singură placă, cu un colț deteriorat exclus.

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 — răspuns

{
  "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 }
}

Fiecare intrare din sheets este o placă folosită; o piesă plasată poartă transformarea rigidă (rotation în grade, apoi translația x/y), NU un polygon reemis — rotește conturul de intrare cu rotation în jurul originii sale și adaugă (x, y) pentru a reconstrui plasarea exact. rotation poate fi negativă; reconstrucția este exactă indiferent de semn. ⚠️ density este aria POLIGONULUI plasat raportată la aria plăcii folosite — umplerea onestă, cu buzunarele concave numărate ca goale — și NU este comparabilă cu yieldPct al unui packer dreptunghiular (care numără fiecare bounding box ca fiind plin, deci arată mai mare pentru un rezultat mai slab); metrica direct comparabilă între cele două este sheetCount pe aceleași piese. Layoutul este determinist: setează options.seed pentru a-l reproduce. exclusions este returnat pe fiecare placă pentru randare. Fiecare placă poartă stock — indexul rândului de material de bază din cerere din care a fost tăiată — iar stockUsage totalizează plăcile, prețul și densitatea pe rând de material de bază, astfel încât o ofertă să poată fi calculată pe dimensiune de placă. Fiecare placă mai poartă și cutLength și pierces (metrics le totalizează): perimetrul însumat al conturilor și al găurilor pieselor sale plasate, și câte o străpungere pentru fiecare contur închis — geometric, fără tăiere pe linie comună sau intrări de tăiere. Fiecare placă mai poartă și usedWidth și usedHeight — cel mai îndepărtat punct la care ajung piesele ei plasate din originea plăcii, bounding box-ul utilizat pentru facturarea unei părți de placă — și usedArea (stockUsage îl totalizează pe rând); ambele motoare compactează implicit placa cea mai puțin umplută spre acel colț, cu subseturi de rotații din cele permise fiecărei piese (options.compact: false îl dezactivează; options.compactFor alege măsura de minimizat — "box" (implicit) aria utilizată, "horizontal" înălțimea utilizată pentru facturarea unei fâșii pe toată lățimea, "vertical" lățimea utilizată pentru o fâșie pe toată înălțimea; numărul de plăci și prețul nu se schimbă niciodată).

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.

Piese dintr-un fișier (SVG · DXF)

O piesă nu trebuie să sosească neapărat sub formă de coordonate. Pune un document SVG sau DXF în parts[].source și serverul îi extrage conturul — și găurile — cu același cititor pe care aplicația CutOptim îl folosește când tragi un desen peste modul ei Nesting. Fișierul înlocuiește DOAR geometria: qty, material, allowedRotations, minQuality, priority și meta se comportă exact ca pe o piesă polygon, așa că o bibliotecă de piese care există deja ca fișiere CAD nu cere un aplatizor propriu de curbe și arce. O source descrie O singură piesă; un desen care conține mai multe componente separate returnează 400 și te trimite la endpointul de import de mai jos. Răspunsul poartă atunci un bloc imported: câte rânduri provin dintr-un fișier, câte vârfuri au produs și ce unități au declarat acele fișiere — raportate, niciodată aplicate, fiindcă acest API nu convertește nimic.

Nimic nu se stochează. Octeții există doar ca și corp al cererii, sunt prelucrați în memorie și dispar când răspunsul este scris: fără disc, fără bază de date, fără fișier temporar, fără linie de jurnal. Nu rămâne nimic de șters după aceea și nimic nu se păstrează — aceeași lipsă de stare pe care o ține fiecare alt 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 }]
}

Numele și tipurile câmpurilor fac parte din contract, așa că tabelele de mai jos rămân în engleză în toate limbile — un nume de câmp tradus ar documenta un API care nu există.

{
  "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 — un fișier, toate contururile

Când un singur desen conține mai multe piese diferite, importă-l mai întâi: acest endpoint returnează fiecare contur închis pe care îl conține, începând cu cel mai mare, exact în forma pe care o cere un rând parts[]. Lipește-le pe cele de care ai nevoie, adaugă propriile qty și material și trimite asta la /v1/optimize/nest. Este și modul în care vezi ce se află într-un fișier înainte să cheltuiești o rezolvare. Necesită o cheie — aplatizarea unei geometrii arbitrare este muncă reală de CPU, iar CPU anonim este o afacere proastă — dar nu rezervă nimic: cota ta rămâne neatinsă și nu se întorc anteturi de rate limit, exact ca la interogarea unui job.

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".

Motoare

  • heuristic (implicit) — algoritmul de împachetare ghilotină, cu mai multe strategii. Cea mai bună utilizare, fiecare aranjament tăiabil pe ferăstrău, întotdeauna un cutPlan complet.
  • balanced — un algoritm de împachetare MaxRects cu nesting liber. Mult mai rapid la lucrările mari (măsurat ~25× la 2.000 de piese), cu o utilizare puțin mai mică, iar aranjamentele sale adesea nu sunt ghilotină (guillotineValid: false, cutPlan: null). Nu modelează tolerance, minimizeCost, grainGroup, maxCutStages sau minimizeRotations — dacă setezi una, un avertisment îți spune că a fost ignorată.
  • max — nivelul asincron. La 2D este o căutare în arbore care atinge optimul demonstrat pe mult mai multe lucrări, cu prețul a câteva secunde până la un minut per rezolvare. Rămâne determinist și ghilotină-valid. Nu returnează un plan direct — vezi Lucrări asincrone mai jos. Modelează UN SINGUR format de stoc la dimensiunea completă a plăcii, cu disponibilitate nelimitată și un tipar ghilotină fix în 3 etape: un al doilea rând de stoc, trim, respectStock, material sau grainGroup sunt refuzate cu 400 înainte ca un apel să fie rezervat; tolerance, minimizeCost, maxCutStages, minimizeRotations, firstCut și effort trec, dar sunt ignorate cu un avertisment, iar un rezultat max nu raportează resturi. Trimite acele lucrări către motorul heuristic. La nesting true-shape (POST /v1/optimize/nest) max rulează întâi trecerea lbf și apoi caută, în limita options.timeBudgetMs (60 s implicit, 10–180), un aranjament cu mai puține plăci — niciodată mai multe; nesturile sale poartă deterministic: false. Acolo acceptă mai multe dimensiuni de placă per material și respectStock, și își păstrează rezultatul doar atunci când clasează mai bine decât cel al lui lbf (după preț cu minimizeCost, altfel după aria plăcii); o placă poligonală sau zonele de excludere sunt deservite de lbf din interiorul lucrării, cu un avertisment.

Lucrări asincrone (engine = max)

O rezolvare max durează de la câteva secunde până la un minut, așa că POST /v1/optimize/2d sau /v1/optimize/nest cu engine:"max" nu returnează un rezultat — returnează 202 Accepted cu un jobId, iar apelul este contorizat la trimitere. Interoghează GET /v1/jobs/{id} până când status este "succeeded" (result conține același răspuns pe care îl returnează o rezolvare sincronă a acelui mod — un plan 2D sau un nest; mode îți spune care) sau "failed" (error conține mesajul). Interogarea nu consumă cotă; vezi doar propriile lucrări. Acolo unde nivelul nu este activat pe o instalare, engine:"max" eșuează închis cu 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.

Determinism și versionare

Fiecare răspuns conține engineVersion. Algoritmii sunt deterministici (singura excepție, căutarea max pentru nesting, limitată în timp, raportează deterministic: false), deci îmbunătățirea unuia schimbă rezultatul pentru aceeași intrare — ceea ce este o modificare incompatibilă dacă folosești cache. Fixează comportamentul trimițând engine explicit și urmărind engineVersion; versiunea din cale, /v1/, se schimbă doar dacă se schimbă structura răspunsului.

Fiecare motor este versionat independent, așa că o modificare la unul nu mișcă niciodată versiunea celuilalt.

Erori

400invalid_requestEroare de schemă. details.path indică câmpul problematic.
401unauthorizedCheie API lipsă sau necunoscută.
402quota_exceededCota lunară a fost atinsă. Retry-After indică secundele rămase până la începutul lunii următoare.
403key_revokedCheia există, dar nu poate fi folosită: a fost revocată sau abonamentul Engine API al contului nu mai este activ. Câmpul message spune care dintre ele.
404not_foundNu există o astfel de rută — este și răspunsul pe care îl primești pentru calea corectă cu metoda greșită.
413too_largeIntrare peste o limită (vezi Limite).
429busyCapacitate atinsă momentan sau — pe orice rută autentificată cu cheie — adresa ta a trimis prea multe chei API necunoscute într-un minut. Retry-After în secunde; acest răspuns nu se scade niciodată din cota ta.
500internalEroare neașteptată sau backendul de autentificare este inaccesibil (cererile sunt respinse — fail closed).
503service_unavailableUn motor cerut nu poate fi servit acum — motorul de nesting sau motorul max asincron. Pentru max sunt două cauze, iar câmpul message spune care: nivelul nu este inclus în acest deployment, sau este inclus dar worker-ul care rezolvă lucrările nu răspunde. Fail-closed înainte ca un apel să fie rezervat, deci nu costă niciodată nimic.
504solve_timeoutRezolvarea a depășit limita sa strictă de timp. Pe traseele rectangulare o impune proxy-ul; pe /v1/optimize/nest motorul impune propriul buget, mai scurt, și răspunde cu solve_timeout în plicul obișnuit. Un apel abandonat de proxy nu este taxat.

Corpul erorii

Fiecare eroare produsă de motorul propriu-zis folosește același înveliș. Ramifică logica după error, care este un cod stabil; niciodată după message, a cărui formulare se poate schimba de la o versiune la alta. details este prezent la invalid_request, unde path numește câmpul problematic, și la too_large, unde max și got indică plafonul și valoarea pe care ai trimis-o.

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" }
  • Atât 402, cât și 429 conțin un header Retry-After exprimat în secunde. La 402 numără invers până la resetarea cotei, la 00:00 UTC în prima zi a lunii următoare; la 429 este o scurtă pauză înainte de reîncercare, iar un 429 nu consumă niciodată cotă — apelul rezervat este restituit.
  • O eroare de rutare răspunde cu not_found, un cod aflat intenționat în afara listei de mai sus, pentru că îl produce routerul, nu contractul API. Primești 404, și nu 405, atunci când calea este corectă, dar metoda este greșită: toate cele patru endpointuri de optimizare acceptă doar POST.
  • Pe traseele rectangulare, 504 vine de la reverse proxy, nu de la motor, așa că corpul răspunsului este al proxy-ului și nu acest înveliș JSON; o singură lucrare în limitele de intrare de mai jos rămâne mult sub ea, dar mai multe dintre cele mai grele lucrări sosite deodată o pot depăși în coadă — iar un apel abandonat de proxy nu este taxat. /v1/optimize/nest este excepția: acea rezolvare este un subproces cu buget propriu, ținut intenționat sub limita proxy-ului, deci acolo un timeout folosește acest înveliș, cu codul solve_timeout.

Headere de limitare a ratei

Un apel de optimizare reușit conține X-RateLimit-Limit (plafonul lunar al CONTULUI — toate cheile contului împart unul singur) și X-RateLimit-Remaining (apelurile rămase contului în luna curentă, după acesta). Sunt trimise doar de endpointurile de optimizare: contorul este rezervat ca parte a autorizării unei rezolvări, deci /v1/usage și /v1/health nu au nimic de raportat.

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"
}

Doar citire: nu consumă un apel și nu trimite anteturi de rate limit. ⚠️ used și limit descriu CONTUL, nu cheia cu care ai apelat: fiecare cheie activă a contului consumă dintr-o singură alocare comună, deci mai multe chei nu înseamnă mai multă cotă. used numără luna calendaristică UTC curentă pentru toate, remaining este limit minus used și nu coboară niciodată sub zero, periodEnd este ziua de resetare ca dată simplă YYYY-MM-DD, iar keyPrefix este prefixul public al cheii folosite. Cheia în sine nu este returnată de niciun endpoint — se stochează doar hash-ul ei, așa că o cheie pierdută se înlocuiește, nu se recuperează.

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
}

Fără cheie, fără cotă, fără bază de date. Nu atinge intenționat nimic care are stare, astfel încât o defecțiune a depozitului de chei să nu poată face serviciul să pară mort în ochii unui orchestrator. engines listează id-urile pe care această instalare le acceptă în engine, iar engineVersion este versiunea motorului implicit.

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.

Limite

  • 2,000 piese per cerere (cantitate totală, după expandarea qty)
  • 50 rânduri de material · corpul cererii până la 1 MB
  • 10 chei active per cont — împart O SINGURĂ cotă lunară: cheile separă mediile și integrările, nu adaugă alocare
  • 10 MB corp al cererii pe cele două rute nest care pot transporta un desen (/v1/optimize/nest și /v1/import/nest); un fișier source cel mult 4.000.000 de caractere, 8.000.000 pe cerere
  • concurența este limitată pe server — rezolvările rulează pe rând, în spatele unei cozi scurte (circa 8 s); un burst peste ea primește 429, iar o cerere refuzată nu este niciodată taxată. Endpoint-urile validate fără cheie au în plus un plafon per adresă (429 cu Retry-After); cu o cheie validă nu ești niciodată limitat astfel. O adresă care trimite peste 30 de chei API necunoscute într-un minut primește 429 pentru restul acelui minut, înainte de orice verificare a cheii. Un cont poate avea 5 lucrări max în queued/running simultan.

Specificația OpenAPI

Un document OpenAPI 3.1, procesabil automat, descrie toate cele douăsprezece endpointuri, fiecare corp de cerere, fiecare structură de răspuns și fiecare eroare. Îndreaptă generatorul tău de client către el, în loc să transcrii această pagină. Documentul în sine este doar în engleză: este alcătuit din tokenuri de contract, iar OpenAPI nu are niciun mecanism de localizare.

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

Deschide documentul OpenAPI 3.1 →

Resursă descărcabilă
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
Descarcă PDF-ul