Ugrás a tartalomhoz
← CutOptim Engine API

API-referencia

Alap-URL https://api.cutoptim.com · szerződésverzió v1

Készíts kulcsot az irányítópultodon, majd így küldd el: Authorization: Bearer <key>.

OpenAPI-specifikáció: /engine/openapi.json

Végpontok

POST /v1/optimize/2d 2D lapoptimalizálás
POST /v1/optimize/1d 1D / lineáris optimalizálás (rúd, profil, cső)
GET /v1/usage a hívó kulcs aktuális hónapra eső felhasználása és kvótája
GET /v1/health életjel — nincs kulcs, nincs kéréslimit, nincs adatbázis

Mértékegységek

Az API mértékegység-független. Válassz egy egységet — milliméter, hüvelyk, bármi —, azt használd minden beküldött számhoz, és minden visszakapott szám ugyanabban az egységben lesz. A szerver semmit nem konvertál, és egyetlen mezőnév sem állít egységet.

Ez érvényes a darab- és alapanyag-méretekre, a kerf, tolerance, trim és minOffcut értékekre a bemeneten, valamint minden koordinátára, pozícióra, fennmaradó hosszra, maradékra és vágáshosszra a kimeneten. Ha egy kérésen belül egységeket keversz, olyan tervet kapsz, amely átmegy az ellenőrzésen, és fizikailag hibás — a szerver ezt nem tudja észlelni.

Koordináta-rendszer

Az origó a tábla bal felső sarka: az x a tábla szélessége mentén jobbra, az y a magassága mentén lefelé növekszik. Egy darab x és y értéke a bal felső sarka, a w és h pedig az elhelyezés szerinti mérete — ha a rotated true, akkor már felcserélve —, így az x, y, w, h téglalap minden további számolás nélkül a táblán elfoglalt helyet adja. A maradék-téglalapok ugyanezt a rendszert használják.

A széllevágás elmozdítja az elhelyezéseket: a trim.left minden darabot jobbra, a trim.top minden darabot lefelé tol, mert a darabok a hasznos területen belülre kerülnek, majd azt toljuk vissza a teljes táblára. A trim.right és a trim.bottom a hasznos területet szűkíti, az origót nem mozdítja el. Egy tábla w és h értéke mindig a teljes alapanyag-méret, a széllevágással együtt — és épp ezért számít a széllevágás hulladéknak a yieldPct-ben.

2D — kérés

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 } },
  "engine": "heuristic",
  "include": ["cutPlan", "offcuts"]
}

A darab mezői

w, hkötelező — a darab méretei
qtyalapérték 1 — a szerver bontja ki; beleszámít a 2,000 darabos plafonba
nameopcionális megnevezés, minden elhelyezésnél visszaadjuk
rotatablealapérték true — elforgatható-e a darab 90°-kal
grainGroupegy csoport tagjai egy táblán maradnak (erezet-egyeztetés)
prioritykötelezően kivágandó: korlátozott készlet esetén elsőbbséget kap a táblán (respectStock mellett)

Az alapanyag mezői

A w és a h kötelező. A qty alapértéke 1, és csak respectStock mellett kemény korlát. A price táblánkénti ár, ez határozza meg a totalPrice-t és a költség-módot.

Beállítások

kerffűrészlap-vastagság (alapérték 0)
tolerancelegfeljebb ennyivel túllépő vágásokat is elfogad
trimoldalankénti széllevágás: left, right, top, bottom
firstCut'auto' | 'horizontal' | 'vertical' (alapérték 'auto')
minimizeCosta jelölteket a teljes anyagár szerint rangsorolja, nem a táblák száma szerint
respectStockminden alapanyag-sor qty értékét kemény korlátként kezeli
minOffcutcsak azokat a maradékokat jelenti, amelyek rövidebb oldala legalább ennyi
maxCutStagespanelfűrész fázis-korlát — fázisszám, nem nyers famélység
minimizeRotationsa kevesebb darabot elforgató elrendezéseket részesíti előnyben

Az include szűkíti a választ: adj meg ["cutPlan","offcuts"] értéket (alapértelmezésben mindkettő), vagy hagyd ki bármelyiket, ha nem kell a válaszban.

2D — válasz

{
  "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": 400, "h": 600, "rotated": true },
        { "name": "Door", "x": 413, "y": 10, "w": 400, "h": 600, "rotated": true },
        { "name": "Door", "x": 816, "y": 10, "w": 400, "h": 600, "rotated": true },
        { "name": "Door", "x": 1219, "y": 10, "w": 400, "h": 600, "rotated": true }
      ],
      "offcuts": [
        { "x": 1622, "y": 10, "w": 818, "h": 600 },
        { "x": 10, "y": 613, "w": 2430, "h": 607 }
      ]
    }
  ],
  "metrics": {
    "sheetCount": 1,
    "yieldPct": 32.25,
    "placed": 4,
    "total": 4,
    "cutLines": 5,
    "sawPasses": 5,
    "cutLength": 4830,
    "totalPrice": 42
  },
  "unplaced": [],
  "warnings": [],
  "guillotineValid": true,
  "timing": { "solveMs": 3.13 },
  "cutPlan": [
    { "sheet": 0, "step": 1, "axis": "h", "pos": 610, "length": 2430, "stage": 1 },
    { "sheet": 0, "step": 2, "axis": "v", "pos": 410, "length": 600, "stage": 2 },
    { "sheet": 0, "step": 3, "axis": "v", "pos": 813, "length": 600, "stage": 2 },
    { "sheet": 0, "step": 4, "axis": "v", "pos": 1216, "length": 600, "stage": 2 },
    { "sheet": 0, "step": 5, "axis": "v", "pos": 1619, "length": 600, "stage": 2 }
  ]
}

Válaszmezők

A mezőnevek és a típusok a szerződés részei, ezért az alábbi táblázatok minden nyelven angolul maradnak — egy lefordított mezőnév olyan API-t dokumentálna, amely nem létezik.

POST /v1/optimize/2d — top level

engine string Which engine ran: "heuristic" or "balanced".
engineVersion string Algorithm identity — package version + a content hash of the algorithm source. Moves automatically on any packer change, and independently per engine.
contractVersion string Shape version, matching the /v1/ in the path. Currently "1".
deterministic boolean Always true. Present so a client can assert the guarantee it relies on.
sheets array One entry per sheet used, in cutting order.
metrics object Aggregate numbers for the whole job.
cutPlan array | null The sawing plan. null when guillotineValid is false; absent when excluded via include.
unplaced array Parts that did not fit, aggregated by name + size. Empty when everything fitted.
warnings string[] Free-text notes about the plan. Do not parse — branch on unplaced, guillotineValid and metrics.
guillotineValid boolean True when the layout is producible with edge-to-edge cuts, i.e. on a panel saw.
timing.solveMs number Milliseconds inside the packer, 2 decimals. Excludes parsing, auth and queueing.

sheets[]

w, h number FULL stock dimensions, trim included — not the usable area.
price number | null Price of the stock row this sheet came from, or null if none was given.
parts array Placements on this sheet.
offcuts array Usable leftover rectangles, filtered by options.minOffcut. Absent (not empty) when excluded via include.

sheets[].parts[]

name string The requested name, or the generated default "Part <row>".
x, y number Top-left corner of the part, from the top-left corner of the sheet.
w, h number Dimensions AS PLACED — already swapped when rotated is true. No client-side swap needed.
rotated boolean True if the part was turned 90° from the requested w×h. Informational only.

sheets[].offcuts[]

x, y, w, h number A leftover rectangle, in the same coordinate system as the placements.

metrics (2D)

sheetCount integer Sheets used. Equals sheets.length.
yieldPct number Placed part area ÷ total FULL sheet area × 100, 2 decimals. Trim and kerf count as waste.
placed integer Pieces placed, after qty expansion.
total integer Pieces requested, after qty expansion.
cutLines integer Collinear cuts merged: same axis, same coordinate, same stage counted once — one fence setting.
sawPasses integer Every cut, one per strip crossed — how many separate passes the saw makes.
cutLength number Total distance sawn, 3 decimals. Same under either counting convention. In your unit.
totalPrice number Sum of the used sheets’ prices, 2 decimals. 0 when no stock row carried a price.

unplaced[] (2D)

name string The part name.
w, h number As requested.
qty integer How many of this part could not be placed.

cutPlan[]

sheet integer 0-based index into sheets (2D) or rods (1D). Named sheet in both.
step integer 1-based order within THIS sheet — it restarts at 1 on every sheet.
axis "h" | "v" "h": blade travels along x, separating top from bottom. "v": along y, separating left from right.
pos number The blade’s LOW-COORDINATE edge — the y value for "h", the x value for "v" — not its centre line. The kerf occupies pos to pos + kerf.
length number Distance the blade travels on this cut: the extent of the region crossed. Always 0 in 1D.
stage integer 1-based machine pass. Increments only when the axis flips relative to the parent cut.

cutPlan — mit jelent fizikailag egy lépés

Egy step egy fűrészlap-mozgatás, és a lista abban a sorrendben áll, ahogy tényleg fűrészelni lehet: a szülővágás előbb szerepel, mint az általa létrehozott darabon belüli vágások, mert egy csíkot nem lehet keresztbe vágni, amíg le nem szabtad. Az axis "h" azt jelenti, hogy a fűrészlap az x mentén halad, és a felső részt választja el az alsótól; az axis "v" azt, hogy az y mentén halad, és a bal oldalt választja el a jobbtól. A pos a fűrészlap KISEBB KOORDINÁTÁJÚ éle — "h" esetén az y, "v" esetén az x érték —, nem a középvonala: a fűrészrés a pos és a pos + kerf között foglal helyet, tehát a fűrészlap abba az irányba eszik, amerre a koordináta növekszik — "h" esetén lefelé, "v" esetén jobbra. A vonal kisebb koordinátájú oldalán lévő anyag — "h" esetén a vonal fölött, "v" esetén tőle balra — az a darab, amelyet ez a vágás leválaszt. A length az, hogy a fűrészlap azon az egy vágáson milyen messzire jut: az általa átvágott terület kiterjedése, nem a teljes tábla szélessége.

A stage egy gépi áthaladás. 1-nél kezdődik, és csak akkor lép eggyel, ha az axis a szülővágáshoz képest átfordul, tehát egy tábla hat csíkra szabása egy fázis, a csíkok keresztbe vágása pedig a következő. Ez a „háromfázisú vágás” panelfűrészes értelme, nem a vágásfa mélysége, és ezt korlátozza a maxCutStages. A sheet a sheets tömb 0-alapú indexe, a step pedig minden táblán újraindul 1-től, nem fut végig az egész munkán.

A cutPlan értéke null — nem hiányzó, nem üres —, valahányszor a guillotineValid false: egy éltől élig nem vágható elrendezéshez nincs visszaadható vágási sorrend. A válaszból teljesen kimarad, ha nem szerepeltetted az include-ban.

1D — lineáris

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

A darabok length értéket kapnak (plusz qty, name, priority); az alapanyag length, qty és price értéket. A beállítások: kerf, tolerance, trim.start / trim.end, minimizeCost, respectStock és minOffcut. A válasz sheets helyett rods elemeket ad, mindegyikben a rá eső darabokkal, a fennmaradó hosszal és a maradékokkal.

1D — válasz

{
  "engine": "heuristic",
  "engineVersion": "1.0.0+10e0c941",
  "contractVersion": "1",
  "deterministic": true,
  "rods": [
    {
      "length": 3000,
      "price": 12.5,
      "remaining": 587,
      "parts": [
        { "name": "Rail", "pos": 10, "length": 1200 },
        { "name": "Rail", "pos": 1213, "length": 1200 }
      ],
      "offcuts": [587]
    },
    {
      "length": 3000,
      "price": 12.5,
      "remaining": 587,
      "parts": [
        { "name": "Rail", "pos": 10, "length": 1200 },
        { "name": "Rail", "pos": 1213, "length": 1200 }
      ],
      "offcuts": [587]
    },
    {
      "length": 3000,
      "price": 12.5,
      "remaining": 587,
      "parts": [
        { "name": "Rail", "pos": 10, "length": 1200 },
        { "name": "Rail", "pos": 1213, "length": 1200 }
      ],
      "offcuts": [587]
    }
  ],
  "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 }
  ]
}

A rods váltja fel a sheets-et, és nincs guillotineValid, mert a lineáris vágás mindig legyártható. Minden darab pos értéke a közelebbi végének eltolása attól a rúdvégtől, amelyet a trim.start levág, tehát az első darab pontosan a trim.start értékénél kezdődik, és minden következő pos eggyel több kerf-fel nő. A remaining minden rúdnál szerepel, akkor is, ha kisebb, mint a minOffcut — a minOffcut csak az offcuts tömböt szűri, amelyben legfeljebb egy elem van. A vágástervben a sheet a rúd indexe, az axis mindig "v", a stage mindig 1, a length pedig mindig 0: egy rúd keresztvágásának nincs jelenthető úthossza, és épp ezért ad az 1D metrics cuts értéket, cutLength-et viszont nem.

rods[]

length number FULL bar length as supplied in stock.
price number | null Price of the stock row, or null.
remaining number Unused length left on this bar, 3 decimals. Reported even when below minOffcut.
parts array Placements, in cutting order along the bar.
offcuts number[] At most one entry: [remaining] when it is > 0 and ≥ minOffcut, else []. Absent when excluded via include.

rods[].parts[]

name string The requested name, or the generated default.
pos number Offset of the part’s NEAR end from the bar end that trim.start trims.
length number The part length, as requested.

metrics (1D)

rodCount integer Bars used. Equals rods.length.
yieldPct number Placed length ÷ total FULL bar length × 100, 2 decimals. Trim and kerf count as waste.
placed integer Pieces placed, after qty expansion.
total integer Pieces requested, after qty expansion.
cuts integer Total crosscuts across all used bars — one per placed piece.
totalPrice number Sum of used bar prices, 2 decimals.
toleranceAcceptedCount integer Pieces that fitted only because options.tolerance allowed an overshoot.

unplaced[] (1D)

name string The part name.
length number As requested.
qty integer How many could not be placed.

Motorok

Determinizmus és verziózás

Minden válasz tartalmazza az engineVersion értéket. Az algoritmus determinisztikus, ezért a továbbfejlesztése megváltoztatja a kimenetet ugyanarra a bemenetre — ami törő változás, ha cache-elsz. A viselkedést úgy szögezheted le, hogy az engine paramétert kifejezetten megadod, és figyeled az engineVersion értékét; az útvonal-verzió (/v1/) csak akkor változik, ha a válasz szerkezete változik.

A motorok verziói egymástól függetlenek, így az egyik változása soha nem mozdítja el a másik verzióját.

Hibák

400 invalid_request Sémahiba. A details.path a hibás mezőre mutat.
401 unauthorized Hiányzó vagy ismeretlen API-kulcs.
402 quota_exceeded A havi kvóta elfogyott. A Retry-After megadja, hány másodperc van a hónap átfordulásáig.
403 key_revoked A kulcs létezik, de vissza lett vonva.
404 not_found Nincs ilyen útvonal — ezt kapod akkor is, ha a helyes útvonalat rossz metódussal hívod.
413 too_large A bemenet átlép egy korlátot (lásd a Korlátokat).
429 busy Épp betelt a kapacitás. A Retry-After másodpercben — ez soha nem számít bele a kvótádba.
500 internal Váratlan hiba, vagy nem érhető el az azonosítási backend (a kérések fail-closed módon elbuknak).
504 solve_timeout A számítás túllépte a kemény falióra-korlátot (a proxy kényszeríti ki).

A hiba törzse

Minden hiba, amelyet maga a motor állít elő, ugyanezt a borítékot használja. Az error értékére ágazz, mert az stabil kód; a message-re soha, mert a szövegezése verziók között változhat. A details az invalid_request esetén van jelen, ahol a path nevezi meg a hibás mezőt, és a too_large esetén, ahol a max és a got adja meg a korlátot, illetve azt, amit beküldtél.

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

Kéréslimit-fejlécek

A sikeres optimalizáló hívás X-RateLimit-Limit (a kulcs havi plafonja) és X-RateLimit-Remaining (a hónapból hátralévő hívások száma, ezt a hívást már leszámítva) fejlécet visz. Ezeket kizárólag az optimalizáló végpontok küldik: a számláló a számítás engedélyezésének részeként foglalódik le, így a /v1/usage és a /v1/health nem tud mit jelenteni.

X-RateLimit-Limit integer The key’s monthly quota.
X-RateLimit-Remaining integer Calls left this month, after this one.
Retry-After integer Seconds 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"
}

Csak olvasás: nem fogyaszt hívást, és nem küld kéréslimit-fejlécet. A plan és a limit az a szint és plafon, amely a kulcs létrehozásakor rögzült rá, így a termék későbbi átárazása soha nem írja át egy élő kulcsot. A used az aktuális UTC naptári hónapot számolja, a remaining a limit mínusz a used, és soha nem lesz negatív, a periodEnd a visszaállás napja egyszerű YYYY-MM-DD dátumként, a keyPrefix pedig a hívó kulcs nem titkos, megjelenítésre szánt előtagja. Magát a kulcsot egyetlen végpont sem adja vissza — csak a hash-e van eltárolva, tehát az elveszett kulcsot nem visszaszerezni, hanem lecserélni kell.

plan string Tier slug frozen onto the key when it was created.
used integer Calls counted in the current UTC calendar month.
limit integer The monthly cap frozen onto the key.
remaining integer limit − used, never negative.
periodEnd string Reset day as YYYY-MM-DD — a date, not a timestamp.
keyPrefix string Non-secret display prefix of the calling key.
contractVersion string Shape version. Currently "1".

GET /v1/health

{
  "status": "healthy",
  "service": "cutoptim-engine",
  "contractVersion": "1",
  "engineVersion": "1.0.0+10e0c941",
  "engines": ["heuristic", "balanced"],
  "uptimeSec": 16
}

Nincs kulcs, nincs kvóta, nincs adatbázis. Szándékosan nem nyúl semmilyen állapothoz, így a kulcstároló kiesése nem tudja halottnak mutatni a szolgáltatást egy orchestrator felé. Az engines felsorolja azokat az id-ket, amelyeket ez a telepítés elfogad az engine paraméterben, az engineVersion pedig az alapértelmezett motor verziója.

status string Always "healthy" when the process answers.
service string Always "cutoptim-engine".
contractVersion string Shape version. Currently "1".
engineVersion string The DEFAULT engine’s version, not a per-engine list.
engines string[] Engine ids this deployment accepts in engine.
uptimeSec integer Whole seconds since process start.

Korlátok

OpenAPI-specifikáció

Egy gépi olvasásra szánt OpenAPI 3.1 dokumentum leírja mind a négy végpontot, mindkét kéréstörzset, minden válaszszerkezetet és minden hibát. Erre állítsd rá a kliens-generátorodat, ahelyett hogy ezt a lapot írnád át kézzel. Maga a dokumentum csak angol nyelvű: szerződés-tokenekből áll, és az OpenAPI-nak nincs lokalizációs mechanizmusa.

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

OpenAPI 3.1 dokumentum megnyitása →