{
  "openapi": "3.1.1",
  "info": {
    "title": "Kerfwise Cut List & Cost API",
    "version": "1.0.0",
    "summary": "Cabinets or a Kerfwise project → cut list, edge banding, hardware, sheets and costs.",
    "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://kerfwisecutlist.usefulweb.app"
    }
  ],
  "paths": {
    "/api/v1/calculate": {
      "post": {
        "operationId": "calculate",
        "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/tool": {
      "get": {
        "operationId": "getToolDefinition",
        "summary": "Inputs, output schema and examples for this tool",
        "responses": {
          "200": {
            "description": "Tool definition.",
            "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"
                }
              }
            }
          }
        }
      },
      "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"
              }
            }
          }
        }
      }
    }
  }
}