Přejít na hlavní obsah
← CutOptim Engine API

Reference API

Základní URL https://api.cutoptim.com · verze kontraktu v1

Klíč si vytvořte na své nástěnce a pak jej posílejte jako Authorization: Bearer <key>.

Specifikace OpenAPI: /engine/openapi.json

Endpointy

POST /v1/optimize/2d optimalizace 2D desek
POST /v1/optimize/1d optimalizace 1D / lineárního materiálu (tyče, profily, trubky)
GET /v1/usage spotřeba a kvóta volajícího klíče v aktuálním měsíci
GET /v1/health kontrola dostupnosti — bez klíče, bez rate limitu, bez databáze

Jednotky

API je vůči jednotkám neutrální. Zvolte si jednu jednotku — milimetry, palce, cokoli — používejte ji pro každé číslo, které posíláte, a každé číslo, které dostanete zpátky, bude ve stejné jednotce. Na serveru se nic nepřepočítává a žádný název pole žádnou jednotku nepředepisuje.

Platí to pro rozměry dílů i materiálu, kerf, tolerance, trim a minOffcut na vstupu a pro každou souřadnici, pozici, zbývající délku, zbytek i délku řezu na výstupu. Míchání jednotek v jednom požadavku vytvoří plán, který projde validací a přitom je fyzicky špatný — a server to nemá jak poznat.

Souřadný systém

Počátek je levý horní roh desky: x roste doprava po šířce desky, y roste dolů po výšce desky. x a y dílu jsou jeho levý horní roh a w a h jsou rozměry tak, jak je díl umístěný — už prohozené, když je rotated true — takže obdélník x, y, w, h je přímo obrys na desce, bez dalšího přepočítávání. Obdélníky zbytků používají stejný systém.

Ořez posouvá umístění: trim.left posune každý díl doprava a trim.top každý díl dolů, protože díly se rozmisťují do využitelné plochy a ta se pak přesouvá zpátky na celou desku. trim.right a trim.bottom využitelnou plochu zmenšují, ale počátek neposouvají. w a h desky jsou vždy plné rozměry materiálu, včetně ořezu — a právě proto se ořez v yieldPct počítá jako odpad.

2D — požadavek

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

Pole dílu

w, hpovinné — rozměry dílu
qtyvýchozí 1 — rozepisuje se na serveru; počítá se do limitu 2,000
namenepovinné označení, vrací se u každého umístění
rotatablevýchozí true — smí se díl otočit o 90°
grainGroupčlenové skupiny zůstanou na jedné desce (shoda vzoru dřeva)
prioritymusí být vyříznut: při omezeném materiálu má přednost na desce (spolu s respectStock)

Pole materiálu

w a h jsou povinné. qty má výchozí hodnotu 1 a pevným limitem je jen ve spojení s respectStock. price je cena za desku a vstupuje do totalPrice a do režimu nákladů.

Volby

kerfšířka pilového kotouče (výchozí 0)
tolerancepřijme řezy, které přesahují nejvýše o tuto hodnotu
trimořez hran po jednotlivých stranách: left, right, top, bottom
firstCut'auto' | 'horizontal' | 'vertical' (výchozí 'auto')
minimizeCosthodnotí kandidáty podle celkové ceny materiálu místo počtu desek
respectStockbere qty každého řádku materiálu jako pevný limit
minOffcutvrací jen zbytky, jejichž krátká strana je alespoň takto velká
maxCutStageslimit fází pro formátovací pilu — počet fází, ne hloubka stromu řezů
minimizeRotationspreferuje rozvržení, která otáčejí méně dílů

include zúží odpověď: pošlete ["cutPlan","offcuts"] (výchozí jsou obě), nebo jednu z nich vynechte a v odpovědi nebude.

2D — odpověď

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

Pole odpovědi

Názvy polí a jejich typy jsou součástí kontraktu, takže tabulky níže zůstávají ve všech jazycích v angličtině — přeložený název pole by dokumentoval API, které neexistuje.

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 — co jeden krok fyzicky znamená

Jeden krok je jeden pohyb kotouče a seznam je v tom pořadí, ve kterém se skutečně dá řezat: nadřazený řez stojí před řezy uvnitř kusu, který jím vznikl, protože pásek nemůžete zkrátit napříč, dokud ho neodříznete. axis "h" znamená, že kotouč jede podél x a odděluje horní část od dolní; axis "v" znamená, že jede podél y a odděluje levou část od pravé. pos je NIŽŠÍ hrana kotouče z hlediska souřadnic — hodnota y pro "h", hodnota x pro "v" — nikoli jeho osa: šířka řezu (kerf) zabírá úsek od pos do pos + kerf, takže kotouč ubírá materiál ve směru, ve kterém souřadnice roste — u "h" tedy dolů a u "v" doprava. Materiál na nižší straně řezné linie — u "h" nad ní, u "v" vlevo od ní — je ten kus, který daný řez uvolní. length je vzdálenost, kterou kotouč na tom jednom řezu ujede: rozsah oblasti, kterou přejíždí, ne šířka celé desky.

stage je jeden průjezd stroje. Začíná na 1 a zvyšuje se jen tehdy, když se axis oproti nadřazenému řezu překlopí — takže rozříznutí desky na šest pásků je jedna fáze a jejich příčné zkrácení je fáze další. Přesně takto rozumí „třífázovému řezání“ formátovací pila; není to hloubka stromu řezů a je to právě to, co omezuje maxCutStages. sheet je index do sheets počítaný od nuly a step začíná na každé desce znovu od 1, místo aby běžel přes celou zakázku.

cutPlan je null — ne chybějící, ne prázdný — vždy, když je guillotineValid false: rozvržení, které nelze rozřezat průchozími řezy od hrany k hraně, nemá žádnou sekvenci řezů, kterou by šlo vrátit. Z odpovědi zmizí úplně jen v případě, že jste jej vynechali z include.

1D — lineární

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

Díly mají length (plus qty, name, priority); materiál má length, qty a price. Volby jsou kerf, tolerance, trim.start / trim.end, minimizeCost, respectStock a minOffcut. Odpověď vrací rods místo sheets, každou s vlastními díly, zbývající délkou a zbytky.

1D — odpověď

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

rods nahrazuje sheets a guillotineValid tady není, protože lineární řez je vždy vyrobitelný. pos každého dílu je odstup jeho bližšího konce od toho konce tyče, který ořezává trim.start, takže první díl začíná přesně na trim.start a každé další pos přidává jednu šířku řezu (kerf). remaining se uvádí u každé tyče, i když je menší než minOffcut — minOffcut filtruje pouze pole offcuts, které obsahuje nejvýše jeden záznam. V plánu řezání je sheet index tyče, axis je vždy "v", stage je vždy 1 a length je vždy 0: příčné zkrácení tyče nemá žádnou dráhu, kterou by šlo hlásit, a proto také 1D metrics obsahují cuts, ale žádné cutLength.

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.

Jádra

Determinismus a verzování

Každá odpověď nese engineVersion. Algoritmus je deterministický, takže jeho zlepšení změní výstup pro stejný vstup — a to je pro toho, kdo cachuje, nekompatibilní změna. Chování si zafixujete tím, že budete engine posílat explicitně a sledovat engineVersion; verze v cestě /v1/ se mění jen tehdy, když se změní podoba odpovědi.

Každé jádro se verzuje samostatně, takže změna jednoho nikdy neposune verzi druhého.

Chyby

400 invalid_request Chyba schématu. details.path ukazuje na problematické pole.
401 unauthorized Chybějící nebo neznámý API klíč.
402 quota_exceeded Vyčerpaná měsíční kvóta. Retry-After udává počet sekund do začátku nového měsíce.
403 key_revoked Klíč existuje, ale byl zneplatněn.
404 not_found Taková cesta neexistuje — a totéž dostanete, když je cesta správná, ale metoda špatná.
413 too_large Vstup překračuje limit (viz Limity).
429 busy Momentálně plná kapacita. Retry-After v sekundách — do vaší kvóty se to nikdy nepočítá.
500 internal Neočekávaná chyba, nebo je nedostupný autentizační backend (požadavky se odmítají — fail closed).
504 solve_timeout Výpočet překročil tvrdý časový limit (vynucuje ho proxy).

Tělo chyby

Každá chyba, kterou vytvoří samotné jádro, používá stejnou obálku. Rozhodujte se podle error, což je stabilní kód; nikdy podle message, jehož formulace se mezi vydáními může změnit. details je přítomné u invalid_request, kde path pojmenovává problematické pole, a u too_large, kde max a got udávají limit a to, co jste poslali.

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

Hlavičky rate limitu

Úspěšné volání optimalizace nese X-RateLimit-Limit (měsíční strop klíče) a X-RateLimit-Remaining (kolik volání v tomto měsíci zbývá po tomto). Posílají je pouze optimalizační endpointy: počítadlo se rezervuje jako součást autorizace výpočtu, takže /v1/usage a /v1/health nemají co hlásit.

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

Jen pro čtení: nespotřebovává volání a neposílá žádné hlavičky rate limitu. plan a limit jsou tarif a strop zafixované na klíč v okamžiku jeho vytvoření, takže pozdější změna ceníku už existující klíč nikdy nepřepíše. used počítá aktuální kalendářní měsíc v UTC, remaining je limit minus used a nikdy nejde do minusu, periodEnd je den resetu jako obyčejné datum ve formátu YYYY-MM-DD a keyPrefix je netajný zobrazovaný prefix klíče, kterým jste volali. Samotný klíč nevrací žádný endpoint — ukládá se jen jeho hash, takže ztracený klíč se nahrazuje, ne obnovuje.

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
}

Bez klíče, bez kvóty, bez databáze. Záměrně se nedotýká ničeho stavového, takže výpadek úložiště klíčů nemůže službu ukázat orchestrátoru jako mrtvou. engines vypisuje id, která tato instalace přijímá v engine, a engineVersion je verze výchozího jádra.

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.

Limity

Specifikace OpenAPI

Strojově čitelný dokument OpenAPI 3.1 popisuje všechny čtyři endpointy, obě těla požadavků, každou podobu odpovědi a každou chybu. Nasměrujte na něj svůj generátor klienta místo toho, abyste přepisovali tuto stránku. Samotný dokument je jen v angličtině: skládá se z tokenů kontraktu a OpenAPI žádný lokalizační mechanismus nemá.

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

Otevřít dokument OpenAPI 3.1 →