{
  "openapi": "3.1.1",
  "info": {
    "title": "Kerfwise Sheet Nesting Optimizer API",
    "version": "1.0.0",
    "summary": "Nest rectangular and shaped parts on sheets: kerf, grain, yield, lower bound, offcuts, SVG + DXF.",
    "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://kerfwisesheetnesting.usefulweb.app"
    }
  ],
  "paths": {
    "/api/v1/calculate": {
      "post": {
        "operationId": "calculate",
        "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/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"
                }
              }
            }
          }
        }
      },
      "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"
              }
            }
          }
        }
      }
    }
  }
}