{
  "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.site"
    }
  ],
  "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"
              ]
            }
          }
        ],
        "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, 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\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"
                  }
                }
              }
            }
          }
        },
        "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); solid stock and worktops use first-fit-decreasing 1D optimisation.\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 parts on sheets with kerf, trim and grain: layouts, yield, offcuts, SVG + DXF.",
        "description": "A cutting optimiser for panel saws and CNC routers: list rectangular parts 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, reusable offcuts, the guillotine cut sequence, SVG drawings and a DXF R12 file.\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\nHeuristic optimisation: good, fast layouts, not a proven optimum. 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
                  }
                }
              }
            }
          }
        },
        "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/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",
              "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"
            ],
            "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"
            ],
            "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",
              "none"
            ],
            "default": "bar128"
          },
          "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"
                      }
                    }
                  }
                }
              },
              "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",
                    "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"
            ],
            "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"
            ],
            "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": "%"
          }
        },
        "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"
                    }
                  }
                }
              },
              "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"
                }
              },
              "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"
          }
        },
        "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"
                          }
                        }
                      }
                    },
                    "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"
                }
              }
            }
          },
          "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"
              }
            }
          }
        }
      }
    }
  }
}