{
  "openapi": "3.1.1",
  "info": {
    "title": "Kerfwise API",
    "version": "1.0.0",
    "summary": "Deterministic tools for people and AI agents.",
    "description": "Every endpoint performs the same calculation as the human page and returns inputs, result, units, assumptions, sources, warnings and computedAt. MCP endpoint: /mcp."
  },
  "servers": [
    {
      "url": "https://kerfwise.usefulweb.app"
    }
  ],
  "paths": {
    "/api/v1/tools": {
      "get": {
        "operationId": "listTools",
        "summary": "Catalog of all tools",
        "responses": {
          "200": {
            "description": "Tool list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/{id}": {
      "get": {
        "operationId": "getTool",
        "summary": "Definition of one tool",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "kerfwise-cabinet-builder",
                "kerfwise-cut-list",
                "kerfwise-sheet-nesting",
                "kerfwise-cnc-gcode"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tool definition.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/kerfwise-cabinet-builder/calculate": {
      "post": {
        "operationId": "calculateKerfwiseCabinetBuilder",
        "summary": "Parametric cabinet → every part with cut sizes, edge banding, drilling and hardware.",
        "description": "Give a cabinet type (base, wall, tall, drawer bank, blind corner, diagonal corner base or wall cabinet, L-shaped corner, angled end, quarter-round end shelf, open shelf, wardrobe, bookshelf, desk, table, bed frame) with its size, back panel, doors, drawers, shelves and joinery, and get the complete parts list with finished and cut sizes, grain direction, edge banding per edge, hinge and shelf-pin drilling, hardware and a front view. The same engine powers the Kerfwise app.\n\nMethodology:\n- Carcass: sides run full height, so side height = cabinet height − plinth height; top, bottom and fixed shelves sit between the sides (length = inner width = width − 2 × panel thickness), plus 2 × dado depth (T/3) for dado joints or 2 × rabbet depth (T/2) for rabbets.\n- Back panels: grooved = (inner width + 2 × groove depth) × (inner height + 2 × groove depth) and the shelves lose groove set-back + back thickness of depth; inset = inner width × inner height; nailed = outer size − 2 mm, and the carcass depth includes the back.\n- Doors: full overlay = width / n − gap by carcass height − gap (gap 3 mm = 1.5 mm reveal per edge); inset = (opening − (n + 1) × gap) / n. Hinges per door by height: ≤ 900 mm → 2, ≤ 1600 → 3, ≤ 2000 → 4, else 5, placed 100 mm from the ends; Ø35 × 13 mm cup 22.5 mm from the edge with two Ø8 dowel holes 45 mm apart, mounting plates on the side 37 mm from the front.\n- Drawers: slide length = largest standard length ≤ inner depth − 10 mm; box length = nominal slide length; box width = opening − 25.4 mm (side-mount, 12.7 mm per side) or opening − 42 mm + 2 × side thickness (undermount); box height = front height − 40 mm (side) / − 47 mm (undermount). Front/back length: butt or dowel = box width − 2t, dado = + t, dovetail = full box width.\n- Adjustable shelves: inner width − 2 mm, 20 mm set back, on Ø5 holes at 32 mm pitch in two rows (37 mm from the front, rear row on the 32 mm grid). Fasteners: 50 mm from each end, ≤ 150 mm apart (dowels, confirmat, screws, pocket screws), cams 2–3 per joint.\n- Cut size = finished size − edge-banding thickness on each banded edge (L1/L2 run along the length and reduce the width; W1/W2 reduce the length) when deduction is on.\n- Shaped cabinets (diagonal corner, L-shaped corner, angled end, quarter-round end): the plan footprint is a polygon. End panels run through at the corners, wall-side backs (carcass material) butt against them; panel ends that meet a front at an angle are bevelled (angle and long point in the notes). Bottom, top and shelves are the footprint inset by the panel thickness (+1 mm and −20 mm front set-back for adjustable shelves) and get a cut outline for CNC nesting. Diagonal doors use +45° angled hinges; the L-shaped corner uses a bi-fold connector or two doors with a corner post.\n\nDimensions follow common European frameless practice and the stated rules. Always check hardware manufacturers’ planning data for the exact hinges and slides you buy, and verify critical sizes before cutting expensive material.",
        "tags": [
          "apps"
        ],
        "x-mcp-tool": "kerfwise_cabinet_builder",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KerfwiseCabinetBuilderInput"
              },
              "examples": {
                "example1": {
                  "summary": "Kitchen base cabinet 600, grooved back, legs, 1 door",
                  "value": {
                    "type": "base",
                    "width": 600,
                    "height": 870,
                    "depth": 560,
                    "backType": "groove",
                    "grooveDepth": 8,
                    "plinthType": "legs",
                    "plinthHeight": 150,
                    "doorCount": 1,
                    "shelfCount": 1,
                    "carcassJoint": "dowel",
                    "topType": "rails"
                  }
                },
                "example2": {
                  "summary": "Drawer bank 800 with undermount slides and dovetails",
                  "value": {
                    "type": "drawer",
                    "width": 800,
                    "height": 870,
                    "depth": 560,
                    "drawerCount": 4,
                    "slideType": "under",
                    "drawerJoint": "dovetail",
                    "carcassJoint": "confirmat"
                  }
                },
                "example3": {
                  "summary": "Wardrobe 1000 × 2200, cam joints, 2 doors",
                  "value": {
                    "type": "wardrobe",
                    "width": 1000,
                    "height": 2200,
                    "depth": 600,
                    "carcassJoint": "cam",
                    "plinthType": "board",
                    "plinthHeight": 80
                  }
                },
                "example4": {
                  "summary": "Dining table 1600 × 900 with mortise & tenon aprons",
                  "value": {
                    "type": "table",
                    "width": 1600,
                    "depth": 900,
                    "height": 750,
                    "materialId": "oakpanel-27",
                    "frameJoint": "mortise-tenon"
                  }
                },
                "example5": {
                  "summary": "Diagonal corner base 900 × 900 with a 45° door",
                  "value": {
                    "type": "diagonal",
                    "width": 900,
                    "returnWidth": 900,
                    "height": 870,
                    "depth": 560,
                    "carcassJoint": "dowel"
                  }
                },
                "example6": {
                  "summary": "L-shaped corner base with bi-fold doors",
                  "value": {
                    "type": "l-corner",
                    "width": 1000,
                    "returnWidth": 900,
                    "cornerDoors": "bifold",
                    "handle": "profile"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KerfwiseCabinetBuilderResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/kerfwise-cut-list/calculate": {
      "post": {
        "operationId": "calculateKerfwiseCutList",
        "summary": "Cabinets or a Kerfwise project → cut list, edge banding, hardware, sheets and costs.",
        "description": "Paste a Kerfwise project (.kerfwise.json) or list cabinets by type and size, and get the complete grouped cut list with edge banding per edge, banding metres, hardware list, optimised sheet count and yield per material, worktops, labour minutes and a full cost estimate with markup and tax. CSV export included.\n\nMethodology:\n- Every cabinet is generated with the Kerfwise cabinet engine (see kerfwise-cabinet-builder for the joinery rules); identical cut pieces (same material, size, grain and banding) are grouped into one row with all their labels.\n- Edge banding metres = sum of banded finished edge lengths × (1 + banding waste %, default 10 %), rounded up to 0.1 m.\n- Sheets: parts of each sheet material are nested with the kerfwise-sheet-nesting optimiser (kerf, trim margins, grain locked for grained materials, shaped parts as polygons); solid stock and worktops use first-fit-decreasing 1D optimisation.\n- Worktop corners: butt = the later run butts against the earlier one (2 connector bolts); mitre = both pieces cut on the line from the outer wall corner to the inner front corner (angle = atan(depth A / depth B), bolts every ~200 mm + 4 biscuits); one-piece = the two halves merged into one L/U polygon nested on the slab material (falls back to a mitre when it does not fit). A diagonal corner cabinet adds a triangular infill (butt/mitre) or a chamfer (one-piece) in the inner corner.\n- Cost = materials (full sheets or used area × (1 + waste %)) + banding + hardware + labour (cut minutes per part, banding minutes per metre, drilling minutes per hole, assembly minutes per cabinet) + worktop cut-outs, then markup and tax.\n\nPrices are editable defaults, not quotes. Labour times are rough workshop averages — adjust them to your shop. Check the cut list against your drawings before cutting.",
        "tags": [
          "apps"
        ],
        "x-mcp-tool": "kerfwise_cut_list",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KerfwiseCutListInput"
              },
              "examples": {
                "example1": {
                  "summary": "Three kitchen cabinets (base, drawer bank, wall)",
                  "value": {
                    "cabinets": [
                      {
                        "type": "base",
                        "width": 600,
                        "doors": 1,
                        "shelves": 1
                      },
                      {
                        "type": "drawer",
                        "width": 600,
                        "drawers": 3
                      },
                      {
                        "type": "wall",
                        "width": 800,
                        "doors": 2
                      }
                    ],
                    "kerf": 4,
                    "trim": 10,
                    "labourRate": 45,
                    "currency": "EUR"
                  }
                },
                "example2": {
                  "summary": "Bookshelf ×2 and a desk, 19 % VAT",
                  "value": {
                    "cabinets": [
                      {
                        "type": "bookshelf",
                        "width": 800,
                        "qty": 2
                      },
                      {
                        "type": "desk",
                        "width": 1400,
                        "drawers": 2
                      }
                    ],
                    "materialId": "oak-18",
                    "frontMaterialId": "oak-18",
                    "taxPct": 19,
                    "markupPct": 15
                  }
                },
                "example3": {
                  "summary": "Project JSON: wall cabinet in walnut",
                  "value": {
                    "project": "{\"format\":\"kerfwise\",\"version\":1,\"name\":\"Walnut wall cabinet\",\"cabinets\":[{\"type\":\"wall\",\"params\":{\"width\":900,\"doorCount\":2,\"frontMaterialId\":\"walnut-19\",\"frontBandingId\":\"walnut-veneer-05\"}}]}"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KerfwiseCutListResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/kerfwise-sheet-nesting/calculate": {
      "post": {
        "operationId": "calculateKerfwiseSheetNesting",
        "summary": "Nest rectangular and shaped parts on sheets: kerf, grain, yield, lower bound, offcuts, SVG + DXF.",
        "description": "A cutting optimiser for panel saws and CNC routers: list parts (rectangles, L-shapes, corner cuts or quarter rounds) with quantity and grain, choose the sheet size (e.g. 2800 × 2070), saw kerf and trim margin, and get guillotine-cuttable or MaxRects layouts, sheet count, yield per sheet, the lower bound on the sheet count and the gap to it, reusable offcuts, the guillotine cut sequence, SVG drawings and a DXF R12 file. An optional deep search tries many seeded variations and stops as soon as the layout is proven optimal.\n\nMethodology:\n- Parts are inflated by one kerf and the usable sheet (sheet − 2 × trim) by one kerf, so neighbouring parts are exactly one kerf apart and the outer parts touch the trimmed edge.\n- Guillotine: free-rectangle packing with best-area-fit / best-short-side-fit and split rules (shorter-leftover-axis, longer-leftover-axis, min-area); every placement records its edge-to-edge cuts, giving a panel-saw cut sequence. MaxRects: maximal free rectangles with best-short-side, best-area or bottom-left fit (tighter, for CNC).\n- Each method runs a fixed set of strategies (4 sort orders × fit/split rules × rotation on/off) and keeps the best: fewest unplaced parts → fewest sheets → least sheet area → largest single offcut → guillotine preferred. Deterministic for identical input.\n- Grain: on a grained material, parts marked grain keep their length along the sheet length; other parts may rotate 90°. Yield = part area / full sheet area. Offcuts = non-overlapping free rectangles ≥ the minimum offcut size.\n- Shaped parts: nested by their bounding box in 0/90/180/270° (grained: 0/180°); the empty notches of the polygon inside the box go back into the MaxRects free list (inset by the kerf), so smaller parts fill them. Guillotine layouts cannot contain shaped parts — auto picks MaxRects then. Yield counts the true polygon area.\n- Lower bound = max(⌈Σ (part + kerf) area / usable area⌉, number of parts larger than half the usable sheet in both directions). Gap = sheets used − lower bound; a gap of 0 proves the sheet count optimal. Deep search: a mulberry32-seeded multi-start search (perturbed sort orders, random fit/split rules, rotation preferences) that keeps the best layout and stops early at the bound.\n\nHeuristic optimisation: the sheet count is proven optimal only when the gap to the lower bound is 0. Check that your saw can follow the cut sequence and measure your blade’s real kerf.",
        "tags": [
          "apps"
        ],
        "x-mcp-tool": "kerfwise_sheet_nesting",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KerfwiseSheetNestingInput"
              },
              "examples": {
                "example1": {
                  "summary": "16 panels 600 × 500 on 2800 × 2070 (exactly one sheet)",
                  "value": {
                    "parts": [
                      {
                        "name": "Panel",
                        "length": 600,
                        "width": 500,
                        "qty": 16
                      }
                    ],
                    "sheetLength": 2800,
                    "sheetWidth": 2070,
                    "kerf": 4,
                    "trim": 10,
                    "method": "guillotine",
                    "grainedMaterial": false
                  }
                },
                "example2": {
                  "summary": "Base cabinet carcass parts, oak veneer with grain",
                  "value": {
                    "parts": [
                      {
                        "name": "Side",
                        "length": 720,
                        "width": 559,
                        "qty": 4,
                        "grain": true
                      },
                      {
                        "name": "Bottom",
                        "length": 564,
                        "width": 559,
                        "qty": 2,
                        "grain": true
                      },
                      {
                        "name": "Rail",
                        "length": 564,
                        "width": 99,
                        "qty": 4,
                        "grain": true
                      },
                      {
                        "name": "Shelf",
                        "length": 562,
                        "width": 515,
                        "qty": 2,
                        "grain": true
                      },
                      {
                        "name": "Door",
                        "length": 716,
                        "width": 596,
                        "qty": 2,
                        "grain": true
                      }
                    ],
                    "sheetLength": 2800,
                    "sheetWidth": 2070,
                    "sheetPrice": 148,
                    "kerf": 4,
                    "trim": 10,
                    "method": "auto",
                    "grainedMaterial": true
                  }
                },
                "example3": {
                  "summary": "Plywood 2440 × 1220, mixed parts, MaxRects",
                  "value": {
                    "parts": [
                      {
                        "name": "Top",
                        "length": 1200,
                        "width": 600,
                        "qty": 1
                      },
                      {
                        "name": "Side",
                        "length": 700,
                        "width": 580,
                        "qty": 2
                      },
                      {
                        "name": "Shelf",
                        "length": 1160,
                        "width": 300,
                        "qty": 3
                      },
                      {
                        "name": "Strip",
                        "length": 1160,
                        "width": 80,
                        "qty": 4
                      }
                    ],
                    "sheetLength": 2440,
                    "sheetWidth": 1220,
                    "sheetPrice": 74,
                    "kerf": 3.2,
                    "trim": 5,
                    "method": "maxrects",
                    "grainedMaterial": false
                  }
                },
                "example4": {
                  "summary": "L-shaped tops and corner-cut shelves with small parts in the notches",
                  "value": {
                    "parts": [
                      {
                        "name": "L top",
                        "length": 1200,
                        "width": 900,
                        "qty": 2,
                        "shape": "l",
                        "a": 650,
                        "b": 500
                      },
                      {
                        "name": "Corner shelf",
                        "length": 600,
                        "width": 600,
                        "qty": 4,
                        "shape": "chamfer",
                        "a": 300,
                        "b": 300
                      },
                      {
                        "name": "Strip",
                        "length": 500,
                        "width": 120,
                        "qty": 6
                      }
                    ],
                    "sheetLength": 2800,
                    "sheetWidth": 2070,
                    "kerf": 8,
                    "trim": 10,
                    "method": "maxrects",
                    "grainedMaterial": false,
                    "effort": "deep",
                    "iterations": 200
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KerfwiseSheetNestingResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/kerfwise-cnc-gcode/calculate": {
      "post": {
        "operationId": "calculateKerfwiseCncGcode",
        "summary": "Nest parts and get CNC router G-code: compensated profiles, tabs, ramps, drilling cycles, time.",
        "description": "Turns a part list into a CNC router program: parts (rectangles, L-shapes, corner cuts, quarter rounds) are nested with a gap of one cutter diameter, then every part gets an outside profile with the tool-radius offset already in the path (arcs around corners), multiple depth passes with a ramped entry, holding tabs or an onion skin, and the cut order small parts first. Holes become drilling cycles (G81, or G83 peck for deep holes) for LinuxCNC / Fanuc-style controls, or expanded moves for GRBL. You get the G-code, a toolpath preview, a DXF per sheet and the estimated machining time.\n\nMethodology:\n- Nesting: the kerfwise sheet nesting optimiser (MaxRects) with the kerf set to the cutter diameter, so the slots of neighbouring parts never overlap; the margin to the sheet edge must be ≥ the cutter radius.\n- Tool-radius compensation in the path: each outside profile is the part outline offset outward by the radius — straight edges shifted along their outward normal, an arc (G2/G3, centre = part corner) around every convex corner and a line intersection at inside corners of L-shapes. Inner contours are offset inward. The controller runs with G40 (no G41/G42).\n- Passes: n = ⌈(thickness + 0.3 mm overcut) / depth per pass⌉, equal steps. Entry ramps down along the first edge (40 mm), the ramp wedge is cleaned at the end of each pass. Tabs: 2–6 per part on the longest edges, raised to 2 mm above the spoilboard in the passes below that height. Onion skin: the last pass stops 0.3 mm above the bottom.\n- Climb milling: outside profiles run clockwise, inner contours counter-clockwise (conventional = reversed). Order: drilling (one tool at a time, nearest-neighbour tour), helical bores, inner contours, outside profiles from the smallest part to the largest.\n- Drilling: holes go to the drill whose diameter matches (±0.25 mm) — G98 G81 (G83 peck for holes deeper than 3 × Ø) on LinuxCNC/Fanuc, expanded G0/G1 plunges on GRBL. Holes without a matching drill are helically bored with the end mill. Rapids (G0) are only used at or above the clearance height (3 mm) once over the material.\n- Time = feed moves ÷ their feed rate + rapids ÷ 10 m/min + 22 s per tool change; acceleration is ignored, so real machines take longer (add 20–40 %).\n\nSimulate every program in your CAM or controller before running it. Check work zero, tool lengths, feeds and speeds for your machine and material. Kerfwise is not responsible for crashes, broken tools or scrapped material.",
        "tags": [
          "apps"
        ],
        "x-mcp-tool": "kerfwise_cnc_gcode",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KerfwiseCncGcodeInput"
              },
              "examples": {
                "example1": {
                  "summary": "Wardrobe side panels with shelf-pin rows (GRBL)",
                  "value": {
                    "parts": [
                      {
                        "name": "Side",
                        "length": 2000,
                        "width": 580,
                        "qty": 2
                      },
                      {
                        "name": "Shelf",
                        "length": 764,
                        "width": 560,
                        "qty": 3
                      }
                    ],
                    "holes": [
                      {
                        "part": 1,
                        "x": 300,
                        "y": 37,
                        "diameter": 5,
                        "depth": 12
                      },
                      {
                        "part": 1,
                        "x": 332,
                        "y": 37,
                        "diameter": 5,
                        "depth": 12
                      },
                      {
                        "part": 1,
                        "x": 300,
                        "y": 543,
                        "diameter": 5,
                        "depth": 12
                      },
                      {
                        "part": 1,
                        "x": 332,
                        "y": 543,
                        "diameter": 5,
                        "depth": 12
                      }
                    ],
                    "sheetLength": 2800,
                    "sheetWidth": 2070,
                    "thickness": 18,
                    "dialect": "grbl"
                  }
                },
                "example2": {
                  "summary": "Doors with Ø35 hinge cups (LinuxCNC, G81 cycles)",
                  "value": {
                    "parts": [
                      {
                        "name": "Door",
                        "length": 716,
                        "width": 446,
                        "qty": 4
                      }
                    ],
                    "holes": [
                      {
                        "part": 1,
                        "x": 100,
                        "y": 22.5,
                        "diameter": 35,
                        "depth": 13
                      },
                      {
                        "part": 1,
                        "x": 616,
                        "y": 22.5,
                        "diameter": 35,
                        "depth": 13
                      }
                    ],
                    "sheetLength": 2440,
                    "sheetWidth": 1220,
                    "thickness": 19,
                    "dialect": "linuxcnc"
                  }
                },
                "example3": {
                  "summary": "L-shaped desk top and corner shelves (Fanuc-style)",
                  "value": {
                    "parts": [
                      {
                        "name": "L desk top",
                        "length": 1600,
                        "width": 1200,
                        "shape": "l",
                        "a": 1000,
                        "b": 600
                      },
                      {
                        "name": "Corner shelf",
                        "length": 400,
                        "width": 400,
                        "qty": 3,
                        "shape": "quarter"
                      }
                    ],
                    "sheetLength": 2800,
                    "sheetWidth": 2070,
                    "thickness": 25,
                    "dialect": "fanuc",
                    "holdDown": "onion"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KerfwiseCncGcodeResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/workflows": {
      "get": {
        "operationId": "listWorkflows",
        "summary": "Multi-tool workflows",
        "responses": {
          "200": {
            "description": "Workflow list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem details. 422 responses include per-field `errors`.",
        "required": [
          "type",
          "title",
          "status",
          "detail"
        ],
        "properties": {
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "field",
                "message"
              ],
              "properties": {
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "KerfwiseCabinetBuilderInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "type": {
            "title": "Cabinet type",
            "type": "string",
            "enum": [
              "base",
              "wall",
              "tall",
              "drawer",
              "corner",
              "diagonal",
              "diagonal-wall",
              "l-corner",
              "angled-end",
              "round-end",
              "open",
              "wardrobe",
              "bookshelf",
              "desk",
              "table",
              "bed"
            ],
            "default": "base"
          },
          "width": {
            "title": "Width",
            "description": "Outer width (table/desk: top length).",
            "type": "number",
            "minimum": 150,
            "maximum": 4000,
            "default": 600,
            "x-unit": "mm"
          },
          "height": {
            "title": "Height",
            "description": "Outer height incl. plinth, without worktop. Empty = type default.",
            "type": "number",
            "minimum": 150,
            "maximum": 3000,
            "x-unit": "mm"
          },
          "depth": {
            "title": "Depth",
            "description": "Outer depth. Empty = type default.",
            "type": "number",
            "minimum": 100,
            "maximum": 2500,
            "x-unit": "mm"
          },
          "materialId": {
            "title": "Carcass material",
            "type": "string",
            "enum": [
              "mel-18",
              "oak-18",
              "birch-15",
              "mdf-19",
              "walnut-19",
              "oakpanel-27",
              "hdf-8",
              "hdf-3",
              "wt-slab-20"
            ],
            "default": "mel-18"
          },
          "frontMaterialId": {
            "title": "Front material",
            "type": "string",
            "enum": [
              "mel-18",
              "oak-18",
              "birch-15",
              "mdf-19",
              "walnut-19",
              "oakpanel-27",
              "hdf-8",
              "hdf-3",
              "wt-slab-20"
            ],
            "default": "oak-18"
          },
          "backType": {
            "title": "Back panel",
            "type": "string",
            "enum": [
              "groove",
              "inset",
              "nailed",
              "none"
            ],
            "default": "groove"
          },
          "grooveDepth": {
            "title": "Groove depth",
            "type": "number",
            "minimum": 0,
            "maximum": 20,
            "default": 8,
            "x-unit": "mm"
          },
          "topType": {
            "title": "Top",
            "type": "string",
            "enum": [
              "full",
              "rails"
            ],
            "default": "full"
          },
          "plinthType": {
            "title": "Plinth / toe kick",
            "type": "string",
            "enum": [
              "legs",
              "board",
              "none"
            ],
            "default": "legs"
          },
          "plinthHeight": {
            "title": "Plinth height",
            "type": "number",
            "minimum": 0,
            "maximum": 300,
            "default": 150,
            "x-unit": "mm"
          },
          "doorCount": {
            "title": "Doors",
            "description": "Empty = type default.",
            "type": "integer",
            "minimum": 0,
            "maximum": 4
          },
          "doorStyle": {
            "title": "Door style",
            "type": "string",
            "enum": [
              "overlay",
              "inset"
            ],
            "default": "overlay"
          },
          "drawerCount": {
            "title": "Drawers",
            "description": "Empty = type default. With doors, drawers go on top (150 mm each).",
            "type": "integer",
            "minimum": 0,
            "maximum": 8
          },
          "slideType": {
            "title": "Drawer slides",
            "type": "string",
            "enum": [
              "side",
              "under"
            ],
            "default": "side"
          },
          "shelfCount": {
            "title": "Shelves",
            "description": "Empty = type default.",
            "type": "integer",
            "minimum": 0,
            "maximum": 12
          },
          "adjustableShelves": {
            "title": "Adjustable shelves (32 mm system holes)",
            "type": "boolean",
            "default": true
          },
          "faceFrame": {
            "title": "Face frame",
            "type": "boolean",
            "default": false
          },
          "carcassJoint": {
            "title": "Carcass joinery",
            "type": "string",
            "enum": [
              "butt",
              "dowel",
              "confirmat",
              "pocket",
              "cam",
              "dado",
              "rabbet"
            ],
            "default": "dowel"
          },
          "frameJoint": {
            "title": "Frame / apron joinery",
            "type": "string",
            "enum": [
              "pocket",
              "dowel",
              "mortise-tenon",
              "butt"
            ],
            "default": "pocket"
          },
          "drawerJoint": {
            "title": "Drawer box joinery",
            "type": "string",
            "enum": [
              "butt",
              "dado",
              "dovetail",
              "dowel"
            ],
            "default": "dado"
          },
          "handle": {
            "title": "Handles",
            "type": "string",
            "enum": [
              "bar128",
              "bar160",
              "knob",
              "cup",
              "profile",
              "push",
              "none"
            ],
            "default": "bar128"
          },
          "returnWidth": {
            "title": "Corner: second leg length",
            "description": "Diagonal and L-shaped corner cabinets: length along the adjoining wall. Empty = same as the width.",
            "type": "number",
            "minimum": 300,
            "maximum": 2000,
            "x-unit": "mm"
          },
          "cornerDoors": {
            "title": "L-shaped corner doors",
            "type": "string",
            "enum": [
              "bifold",
              "two"
            ],
            "default": "bifold"
          },
          "endAngle": {
            "title": "Angled end: angle to the wall",
            "type": "number",
            "minimum": 30,
            "maximum": 90,
            "default": 60,
            "x-unit": "°"
          },
          "bandingDeduct": {
            "title": "Deduct edge-banding thickness from cut sizes",
            "type": "boolean",
            "default": true
          }
        },
        "required": [
          "width"
        ]
      },
      "KerfwiseCabinetBuilderResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "required": [
              "type",
              "size",
              "dimensions",
              "parts",
              "hardware",
              "totals",
              "warnings",
              "project",
              "openInApp"
            ],
            "properties": {
              "type": {
                "type": "string"
              },
              "size": {
                "type": "object",
                "properties": {
                  "w": {
                    "type": "number"
                  },
                  "h": {
                    "type": "number"
                  },
                  "d": {
                    "type": "number"
                  }
                }
              },
              "dimensions": {
                "type": "object",
                "description": "Key derived dimensions (carcass height, inner sizes, door/back sizes, slide length …)."
              },
              "parts": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "label",
                    "name",
                    "L",
                    "W",
                    "T",
                    "cutL",
                    "cutW",
                    "material",
                    "grain",
                    "edges",
                    "holes"
                  ],
                  "properties": {
                    "label": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "group": {
                      "type": "string"
                    },
                    "L": {
                      "type": "number"
                    },
                    "W": {
                      "type": "number"
                    },
                    "T": {
                      "type": "number"
                    },
                    "cutL": {
                      "type": "number"
                    },
                    "cutW": {
                      "type": "number"
                    },
                    "material": {
                      "type": "string"
                    },
                    "grain": {
                      "type": "boolean"
                    },
                    "edges": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "holes": {
                      "type": "integer"
                    },
                    "notes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "shape": {
                      "enum": [
                        "rect",
                        "polygon"
                      ]
                    },
                    "outline": {
                      "type": "array",
                      "description": "Cut outline of shaped parts [u, v] in mm (u from the W1 end, v from the L1 edge).",
                      "items": {
                        "type": "array",
                        "items": {
                          "type": "number"
                        }
                      }
                    }
                  }
                }
              },
              "hardware": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "qty": {
                      "type": "number"
                    },
                    "unit": {
                      "type": "string"
                    },
                    "notes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              },
              "totals": {
                "type": "object",
                "properties": {
                  "parts": {
                    "type": "integer"
                  },
                  "holes": {
                    "type": "integer"
                  },
                  "areaM2": {
                    "type": "number"
                  },
                  "bandingM": {
                    "type": "number"
                  }
                }
              },
              "warnings": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "project": {
                "type": "object",
                "description": "Kerfwise project JSON containing this cabinet (open it in the app or pass it to kerfwise-cut-list)."
              },
              "openInApp": {
                "type": "string"
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "KerfwiseCutListInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "project": {
            "title": "Kerfwise project JSON (optional)",
            "description": "Paste a .kerfwise.json file. When given, the cabinet table below is ignored.",
            "type": "string",
            "maxLength": 400000
          },
          "cabinets": {
            "title": "Cabinets",
            "description": "Quick input without a project file. Empty height/depth/doors/drawers/shelves use the type defaults.",
            "type": "array",
            "maxItems": 60,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "type": {
                  "title": "Type",
                  "type": "string",
                  "enum": [
                    "base",
                    "wall",
                    "tall",
                    "drawer",
                    "corner",
                    "diagonal",
                    "diagonal-wall",
                    "l-corner",
                    "angled-end",
                    "round-end",
                    "open",
                    "wardrobe",
                    "bookshelf",
                    "desk",
                    "table",
                    "bed"
                  ]
                },
                "width": {
                  "title": "Width (mm)",
                  "type": "number",
                  "minimum": 150,
                  "maximum": 4000
                },
                "height": {
                  "title": "Height (mm)",
                  "type": "number",
                  "minimum": 150,
                  "maximum": 3000
                },
                "depth": {
                  "title": "Depth (mm)",
                  "type": "number",
                  "minimum": 100,
                  "maximum": 2500
                },
                "doors": {
                  "title": "Doors",
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 4
                },
                "drawers": {
                  "title": "Drawers",
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 8
                },
                "shelves": {
                  "title": "Shelves",
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 12
                },
                "qty": {
                  "title": "Qty",
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 50
                }
              },
              "required": [
                "type",
                "width"
              ]
            }
          },
          "materialId": {
            "title": "Carcass material (table input)",
            "type": "string",
            "enum": [
              "mel-18",
              "oak-18",
              "birch-15",
              "mdf-19",
              "walnut-19",
              "oakpanel-27",
              "hdf-8",
              "hdf-3",
              "wt-slab-20"
            ],
            "default": "mel-18"
          },
          "frontMaterialId": {
            "title": "Front material (table input)",
            "type": "string",
            "enum": [
              "mel-18",
              "oak-18",
              "birch-15",
              "mdf-19",
              "walnut-19",
              "oakpanel-27",
              "hdf-8",
              "hdf-3",
              "wt-slab-20"
            ],
            "default": "oak-18"
          },
          "currency": {
            "title": "Currency",
            "type": "string",
            "maxLength": 8,
            "default": "EUR"
          },
          "kerf": {
            "title": "Saw kerf",
            "type": "number",
            "minimum": 0,
            "maximum": 10,
            "default": 4,
            "x-unit": "mm"
          },
          "trim": {
            "title": "Sheet trim margin",
            "type": "number",
            "minimum": 0,
            "maximum": 50,
            "default": 10,
            "x-unit": "mm"
          },
          "labourRate": {
            "title": "Labour rate",
            "description": "Per hour, in the chosen currency.",
            "type": "number",
            "minimum": 0,
            "maximum": 1000,
            "default": 45
          },
          "costMode": {
            "title": "Material cost",
            "type": "string",
            "enum": [
              "full-sheets",
              "used-area"
            ],
            "default": "full-sheets"
          },
          "markupPct": {
            "title": "Markup",
            "type": "number",
            "minimum": 0,
            "maximum": 500,
            "default": 0,
            "x-unit": "%"
          },
          "taxPct": {
            "title": "Tax / VAT",
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "default": 0,
            "x-unit": "%"
          },
          "worktopJoint": {
            "title": "Worktop corner joint",
            "description": "Empty = the project setting (butt by default).",
            "type": "string",
            "enum": [
              "butt",
              "mitre",
              "one-piece"
            ]
          },
          "nestingEffort": {
            "title": "Nesting effort",
            "description": "Empty = the project setting.",
            "type": "string",
            "enum": [
              "fast",
              "deep"
            ]
          }
        },
        "required": []
      },
      "KerfwiseCutListResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "required": [
              "projectName",
              "rows",
              "banding",
              "hardware",
              "materials",
              "cost",
              "totals",
              "warnings"
            ],
            "properties": {
              "projectName": {
                "type": "string"
              },
              "rows": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "no",
                    "name",
                    "qty",
                    "L",
                    "W",
                    "T",
                    "cutL",
                    "cutW",
                    "material"
                  ],
                  "properties": {
                    "no": {
                      "type": "integer"
                    },
                    "labels": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "name": {
                      "type": "string"
                    },
                    "cabinets": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "qty": {
                      "type": "integer"
                    },
                    "L": {
                      "type": "number"
                    },
                    "W": {
                      "type": "number"
                    },
                    "T": {
                      "type": "number"
                    },
                    "cutL": {
                      "type": "number"
                    },
                    "cutW": {
                      "type": "number"
                    },
                    "material": {
                      "type": "string"
                    },
                    "grain": {
                      "type": "boolean"
                    },
                    "edges": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              },
              "banding": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "metres": {
                      "type": "number"
                    },
                    "cost": {
                      "type": "number"
                    }
                  }
                }
              },
              "hardware": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "qty": {
                      "type": "number"
                    },
                    "unit": {
                      "type": "string"
                    },
                    "cost": {
                      "type": "number"
                    }
                  }
                }
              },
              "materials": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "material": {
                      "type": "string"
                    },
                    "kind": {
                      "type": "string"
                    },
                    "stock": {
                      "type": "integer"
                    },
                    "yieldPct": {
                      "type": "number"
                    },
                    "cost": {
                      "type": "number"
                    },
                    "lowerBound": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "gap": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "provenOptimal": {
                      "type": "boolean"
                    }
                  }
                }
              },
              "worktops": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "cost": {
                "type": "object",
                "properties": {
                  "materials": {
                    "type": "number"
                  },
                  "banding": {
                    "type": "number"
                  },
                  "hardware": {
                    "type": "number"
                  },
                  "labour": {
                    "type": "number"
                  },
                  "labourMinutes": {
                    "type": "number"
                  },
                  "extras": {
                    "type": "number"
                  },
                  "subtotal": {
                    "type": "number"
                  },
                  "markup": {
                    "type": "number"
                  },
                  "tax": {
                    "type": "number"
                  },
                  "total": {
                    "type": "number"
                  },
                  "currency": {
                    "type": "string"
                  }
                }
              },
              "totals": {
                "type": "object"
              },
              "warnings": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "KerfwiseSheetNestingInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "parts": {
            "title": "Parts",
            "description": "Length runs along the grain. Tick grain to forbid rotation (on a grained material).",
            "type": "array",
            "maxItems": 300,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "name": {
                  "title": "Name",
                  "type": "string",
                  "maxLength": 60
                },
                "length": {
                  "title": "Length (mm)",
                  "type": "number",
                  "minimum": 1,
                  "maximum": 10000
                },
                "width": {
                  "title": "Width (mm)",
                  "type": "number",
                  "minimum": 1,
                  "maximum": 5000
                },
                "qty": {
                  "title": "Qty",
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 500
                },
                "grain": {
                  "title": "Grain",
                  "type": "boolean"
                },
                "shape": {
                  "title": "Shape",
                  "type": "string",
                  "enum": [
                    "rect",
                    "l",
                    "chamfer",
                    "quarter"
                  ]
                },
                "a": {
                  "title": "a (mm)",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 10000
                },
                "b": {
                  "title": "b (mm)",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 5000
                }
              },
              "required": [
                "name",
                "length",
                "width"
              ]
            }
          },
          "sheetLength": {
            "title": "Sheet length",
            "description": "Common sizes: 2800 × 2070, 2440 × 1220, 3050 × 1220, 2500 × 1250, 2620 × 2070, 2750 × 1830, 2800 × 2100, 2440 × 1830, 1220 × 610.",
            "type": "number",
            "minimum": 100,
            "maximum": 10000,
            "default": 2800,
            "x-unit": "mm"
          },
          "sheetWidth": {
            "title": "Sheet width",
            "type": "number",
            "minimum": 100,
            "maximum": 5000,
            "default": 2070,
            "x-unit": "mm"
          },
          "sheetQty": {
            "title": "Sheets available",
            "description": "0 = unlimited.",
            "type": "integer",
            "minimum": 0,
            "maximum": 1000,
            "default": 0
          },
          "sheetPrice": {
            "title": "Price per sheet",
            "type": "number",
            "minimum": 0,
            "maximum": 100000,
            "default": 0
          },
          "kerf": {
            "title": "Saw kerf",
            "type": "number",
            "minimum": 0,
            "maximum": 10,
            "default": 4,
            "x-unit": "mm"
          },
          "trim": {
            "title": "Trim margin per edge",
            "type": "number",
            "minimum": 0,
            "maximum": 50,
            "default": 10,
            "x-unit": "mm"
          },
          "method": {
            "title": "Method",
            "type": "string",
            "enum": [
              "auto",
              "guillotine",
              "maxrects"
            ],
            "default": "auto"
          },
          "grainedMaterial": {
            "title": "Material has grain",
            "type": "boolean",
            "default": true
          },
          "allowRotation": {
            "title": "Allow rotating parts without grain",
            "type": "boolean",
            "default": true
          },
          "minOffcutLength": {
            "title": "Min. offcut length to keep",
            "type": "number",
            "minimum": 0,
            "maximum": 3000,
            "default": 300,
            "x-unit": "mm"
          },
          "minOffcutWidth": {
            "title": "Min. offcut width to keep",
            "type": "number",
            "minimum": 0,
            "maximum": 3000,
            "default": 100,
            "x-unit": "mm"
          },
          "effort": {
            "title": "Optimisation",
            "type": "string",
            "enum": [
              "fast",
              "deep"
            ],
            "default": "fast"
          },
          "iterations": {
            "title": "Deep search iterations",
            "type": "integer",
            "minimum": 1,
            "maximum": 5000,
            "default": 400
          },
          "seed": {
            "title": "Search seed",
            "description": "Same seed + same input = same layout.",
            "type": "integer",
            "minimum": 1,
            "maximum": 1000000,
            "default": 1
          }
        },
        "required": [
          "parts"
        ]
      },
      "KerfwiseSheetNestingResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "required": [
              "method",
              "strategy",
              "sheetCount",
              "yieldPct",
              "sheets",
              "unplaced",
              "offcutAreaM2",
              "cutLengthM",
              "cost"
            ],
            "properties": {
              "method": {
                "type": "string"
              },
              "strategy": {
                "type": "string"
              },
              "sheetCount": {
                "type": "integer"
              },
              "yieldPct": {
                "type": "number"
              },
              "partAreaM2": {
                "type": "number"
              },
              "sheetAreaM2": {
                "type": "number"
              },
              "offcutAreaM2": {
                "type": "number"
              },
              "cutLengthM": {
                "type": "number"
              },
              "cost": {
                "type": "number"
              },
              "sheets": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "index",
                    "yieldPct",
                    "placements",
                    "offcuts"
                  ],
                  "properties": {
                    "index": {
                      "type": "integer"
                    },
                    "yieldPct": {
                      "type": "number"
                    },
                    "cutLength": {
                      "type": "number"
                    },
                    "placements": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "label": {
                            "type": "string"
                          },
                          "x": {
                            "type": "number"
                          },
                          "y": {
                            "type": "number"
                          },
                          "w": {
                            "type": "number"
                          },
                          "h": {
                            "type": "number"
                          },
                          "rotated": {
                            "type": "boolean"
                          },
                          "rot": {
                            "type": "integer"
                          },
                          "outline": {
                            "type": "array",
                            "items": {
                              "type": "array",
                              "items": {
                                "type": "number"
                              }
                            }
                          }
                        }
                      }
                    },
                    "offcuts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "x": {
                            "type": "number"
                          },
                          "y": {
                            "type": "number"
                          },
                          "w": {
                            "type": "number"
                          },
                          "h": {
                            "type": "number"
                          }
                        }
                      }
                    }
                  }
                }
              },
              "unplaced": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "lowerBound": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Proven minimum number of sheets (area bound / large-part bound); null when sheets are limited."
              },
              "gap": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Sheets used − lower bound; 0 = proven optimal sheet count."
              },
              "provenOptimal": {
                "type": "boolean"
              },
              "searchIterations": {
                "type": "integer"
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "KerfwiseCncGcodeInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "parts": {
            "title": "Parts",
            "description": "Finished cut sizes. Length runs along the grain.",
            "type": "array",
            "maxItems": 200,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "name": {
                  "title": "Name",
                  "type": "string",
                  "maxLength": 60
                },
                "length": {
                  "title": "Length (mm)",
                  "type": "number",
                  "minimum": 1,
                  "maximum": 10000
                },
                "width": {
                  "title": "Width (mm)",
                  "type": "number",
                  "minimum": 1,
                  "maximum": 5000
                },
                "qty": {
                  "title": "Qty",
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 200
                },
                "grain": {
                  "title": "Grain",
                  "type": "boolean"
                },
                "shape": {
                  "title": "Shape",
                  "type": "string",
                  "enum": [
                    "rect",
                    "l",
                    "chamfer",
                    "quarter"
                  ]
                },
                "a": {
                  "title": "a (mm)",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 10000
                },
                "b": {
                  "title": "b (mm)",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 5000
                }
              },
              "required": [
                "name",
                "length",
                "width"
              ]
            }
          },
          "holes": {
            "title": "Holes",
            "description": "Part = row number in the parts table; x along the length, y across, from the part’s first corner. Depth ≥ thickness = through.",
            "type": "array",
            "maxItems": 400,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "part": {
                  "title": "Part row",
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 200
                },
                "x": {
                  "title": "x (mm)",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 10000
                },
                "y": {
                  "title": "y (mm)",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 5000
                },
                "diameter": {
                  "title": "Ø (mm)",
                  "type": "number",
                  "minimum": 1,
                  "maximum": 100
                },
                "depth": {
                  "title": "Depth (mm)",
                  "type": "number",
                  "minimum": 0.5,
                  "maximum": 100
                }
              },
              "required": [
                "part",
                "x",
                "y",
                "diameter",
                "depth"
              ]
            }
          },
          "sheetLength": {
            "title": "Sheet length",
            "type": "number",
            "minimum": 100,
            "maximum": 10000,
            "default": 2800,
            "x-unit": "mm"
          },
          "sheetWidth": {
            "title": "Sheet width",
            "type": "number",
            "minimum": 100,
            "maximum": 5000,
            "default": 2070,
            "x-unit": "mm"
          },
          "thickness": {
            "title": "Material thickness",
            "type": "number",
            "minimum": 1,
            "maximum": 100,
            "default": 18,
            "x-unit": "mm"
          },
          "trim": {
            "title": "Margin to the sheet edge",
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "default": 15,
            "x-unit": "mm"
          },
          "grainedMaterial": {
            "title": "Material has grain",
            "type": "boolean",
            "default": false
          },
          "dialect": {
            "title": "Controller",
            "type": "string",
            "enum": [
              "grbl",
              "linuxcnc",
              "fanuc"
            ],
            "default": "grbl"
          },
          "origin": {
            "title": "Work zero (X/Y)",
            "type": "string",
            "enum": [
              "bottom-left",
              "top-left"
            ],
            "default": "bottom-left"
          },
          "toolDiameter": {
            "title": "End mill diameter",
            "type": "number",
            "minimum": 1,
            "maximum": 25,
            "default": 6,
            "x-unit": "mm"
          },
          "feed": {
            "title": "Cutting feed",
            "type": "number",
            "minimum": 100,
            "maximum": 50000,
            "default": 6000,
            "x-unit": "mm/min"
          },
          "plunge": {
            "title": "Plunge feed",
            "type": "number",
            "minimum": 50,
            "maximum": 20000,
            "default": 1500,
            "x-unit": "mm/min"
          },
          "rpm": {
            "title": "Spindle speed",
            "type": "integer",
            "minimum": 1000,
            "maximum": 30000,
            "default": 18000,
            "x-unit": "rpm"
          },
          "stepdown": {
            "title": "Depth per pass",
            "type": "number",
            "minimum": 0.5,
            "maximum": 50,
            "default": 6,
            "x-unit": "mm"
          },
          "holdDown": {
            "title": "Hold-down",
            "type": "string",
            "enum": [
              "tabs",
              "onion",
              "none"
            ],
            "default": "tabs"
          },
          "direction": {
            "title": "Cut direction",
            "type": "string",
            "enum": [
              "climb",
              "conventional"
            ],
            "default": "climb"
          },
          "safeZ": {
            "title": "Safe Z",
            "type": "number",
            "minimum": 2,
            "maximum": 100,
            "default": 15,
            "x-unit": "mm"
          },
          "sheetIndex": {
            "title": "Sheet to show",
            "type": "integer",
            "minimum": 1,
            "maximum": 100,
            "default": 1
          }
        },
        "required": [
          "parts"
        ]
      },
      "KerfwiseCncGcodeResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "required": [
              "sheetCount",
              "sheet",
              "gcode",
              "stats"
            ],
            "properties": {
              "sheetCount": {
                "type": "integer"
              },
              "sheet": {
                "type": "integer"
              },
              "dialect": {
                "type": "string"
              },
              "gcode": {
                "type": "string",
                "description": "G-code of the shown sheet."
              },
              "stats": {
                "type": "object",
                "properties": {
                  "feedLength": {
                    "type": "number"
                  },
                  "rapidLength": {
                    "type": "number"
                  },
                  "holes": {
                    "type": "integer"
                  },
                  "bores": {
                    "type": "integer"
                  },
                  "profiles": {
                    "type": "integer"
                  },
                  "toolChanges": {
                    "type": "integer"
                  },
                  "passes": {
                    "type": "integer"
                  },
                  "tabs": {
                    "type": "integer"
                  },
                  "timeSec": {
                    "type": "number"
                  },
                  "lines": {
                    "type": "integer"
                  }
                }
              },
              "sheetsTimeSec": {
                "type": "array",
                "items": {
                  "type": "number"
                }
              },
              "tools": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "diameter": {
                      "type": "number"
                    }
                  }
                }
              },
              "bounds": {
                "type": "object",
                "properties": {
                  "x0": {
                    "type": "number"
                  },
                  "y0": {
                    "type": "number"
                  },
                  "x1": {
                    "type": "number"
                  },
                  "y1": {
                    "type": "number"
                  },
                  "zMin": {
                    "type": "number"
                  }
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      }
    }
  }
}