Skip to main content
← API reference

Engines and determinism

Which engine to pick, why results repeat, and how to discover what a deployment serves.

Engines

  • heuristic (default) — the guillotine multi-strategy packer. Highest yield, every layout saw-cuttable, always a full cutPlan.
  • balanced — a MaxRects free-nesting packer. Much faster on large jobs (measured ~25× at 2,000 parts) for a small yield cost, and its layouts are often not guillotine (guillotineValid: false, cutPlan: null). It does not model tolerance, minimizeCost, grainGroup, maxCutStages or minimizeRotations — set one and a warning tells you it was ignored.
  • max — the asynchronous tier. On 2D it is a tree search that reaches the proven optimum on far more jobs at the cost of seconds-to-a-minute per solve. Still deterministic and guillotine-valid. It does not return a plan directly — see Async jobs below.

    It models ONE stock format at full sheet size with unlimited supply and a fixed 3-stage guillotine pattern, so a second stock row, trim, respectStock, material, grainGroup or a non-empty matchedSets is refused with 400 before a call is reserved; tolerance, minimizeCost, maxCutStages, minimizeRotations, firstCut and effort go through but are ignored with a warning, and a max result reports no offcuts.

    Send those jobs to heuristic. On true-shape nesting (POST /v1/optimize/nest) max runs the lbf pass first and then searches, within options.timeBudgetMs (60 s by default, 10–180), for a layout with fewer sheets — never more; its nests carry deterministic: false.

    There it accepts several sheet sizes per material and respectStock, and keeps its result only when it ranks better than lbf’s (by price with minimizeCost, otherwise by sheet area); a polygon sheet or exclusion zones are served by lbf inside the job, with a warning.

Determinism & versioning

Every response carries engineVersion. The algorithms are deterministic (the one exception, the time-limited nesting max search, reports deterministic: false), so improving one changes output for the same input — which is a breaking change if you cache. Pin behaviour by sending engine explicitly and watching engineVersion; the path version /v1/ only changes if the response shape changes.

Each engine versions independently, so a change to one never moves the other's version.

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
}

No key, no quota, no database. It deliberately touches nothing stateful, so an outage in the key store cannot make the service look dead to an orchestrator. engines lists the ids this deployment accepts in engine, and engineVersion is the default engine’s version.

FieldTypeDescription
status"healthy"Always "healthy" when the process answers.
service"cutoptim-engine"Always "cutoptim-engine".
contractVersionstringShape version. Currently "1".
engineVersionstringThe DEFAULT engine's version (heuristic), not a per-engine list.
engines("heuristic" | "balanced")[]SYNCHRONOUS engine ids this deployment accepts in engine. The asynchronous max tier is reported in maxEngines, never here — it does not answer in-line.
modes("2d" | "1d" | "wood" | "nest")[]Optimize paths this deployment serves, so the endpoint set is discoverable without reading the docs — the same role engines plays for engine ids. nest is listed only where the nesting engine is built in.
nestEngines("lbf" | "max")[]Nesting engine ids this deployment can serve (empty where the nesting engine is not built in). max (async, preview) appears only where its binary is built in and the async tier is switched on — like maxEngines, built for, not a live worker check (the production API lists both).
maxEngines"max"[]The asynchronous tree-search tier this deployment is BUILT for — ["max"] where the binary is present and the tier is switched on, [] otherwise. ⚠️ Built for is not the same as ready right now: this endpoint deliberately reads no database (a datastore outage must not take health down with it), so it cannot see whether the worker that solves the jobs is alive. A submission can therefore still answer 503 while this list contains "max". An empty list means never; a populated one means normally.
uptimeSecintegerWhole seconds since process start.

Is an engine down? The status page shows the live state of the website, the Engine API, the nesting engine and the async max engine: Service status ↗