{
  "openapi": "3.1.1",
  "info": {
    "title": "Moving Box Planner API",
    "version": "1.0.0",
    "summary": "How many moving boxes, how much tape, which truck and when to start packing.",
    "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://movingboxplanner.usefulweb.site"
    }
  ],
  "paths": {
    "/api/v1/calculate": {
      "post": {
        "operationId": "calculate",
        "summary": "How many moving boxes, how much tape, which truck and when to start packing.",
        "description": "List your rooms and how much stuff you own. The planner estimates boxes by size, packing supplies, load volume, a suitable truck class, crew size and hours, optional cost, and a packing schedule counting back from moving day. All counts are transparent rules of thumb you can check.\n\nMethodology:\n- Boxes per room = editorial base count for a medium room at average density × room size factor (small 0.7, medium 1, large 1.4) × stuff factor (minimalist 0.7, average 1, collector 1.4), rounded up per box type. One extra wardrobe box per person for hanging clothes.\n- Box sizes: small 40×30×30 cm (0.036 m³, books/heavy), medium 50×40×40 cm (0.08 m³), large 60×45×45 cm (0.1215 m³, light/bulky), wardrobe 50×50×120 cm (0.3 m³).\n- Furniture allowance per room (medium): bedroom 4, living 6, kitchen 2, bathroom 0.3, office 2.5, kids 3, garage 3, storage 1.5 m³, scaled by room size and stuff factor. Load volume = (boxes + furniture) × 1.15 for imperfect stacking.\n- Truck: smallest vehicle class whose nominal volume ≥ load (panel van 12, Luton 20, 7.5 t 35, 12 t 50, 18 t 70 m³); larger loads need multiple trips of the 18 t class.\n- Crew: 2 people up to 20 m³, 3 up to 40 m³, 4 above. Loading and unloading each at 2.5 m³ per person-hour, +10% per floor without elevator; driving at 60 km/h average one way.\n- Supplies: 3 m of tape per box on 50 m rolls (+1 spare roll), packing paper and bubble wrap per box by room type (kitchen and living highest), 1 marker per 2 people (min 2).\n- Packing pace: 10 boxes per day; rooms are packed in the order storage → garage → office → kids → living → bedroom → bathroom → kitchen, finishing the day before the move.\n\nRules of thumb, not a quote. Professional movers survey your home for a binding estimate.",
        "tags": [
          "home-travel"
        ],
        "x-mcp-tool": "moving_box_planner",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MovingBoxPlannerInput"
              },
              "examples": {
                "example1": {
                  "summary": "2-bedroom flat, couple",
                  "value": {
                    "rooms": [
                      {
                        "type": "bedroom",
                        "size": "medium"
                      },
                      {
                        "type": "bedroom",
                        "size": "small"
                      },
                      {
                        "type": "living",
                        "size": "medium"
                      },
                      {
                        "type": "kitchen",
                        "size": "medium"
                      },
                      {
                        "type": "bathroom",
                        "size": "small"
                      }
                    ],
                    "people": 2,
                    "density": "average",
                    "distanceKm": 15,
                    "floorsWithoutElevator": 3,
                    "moveDate": "2026-11-28",
                    "currency": "EUR"
                  }
                },
                "example2": {
                  "summary": "Family house with movers",
                  "value": {
                    "rooms": [
                      {
                        "type": "bedroom",
                        "size": "large"
                      },
                      {
                        "type": "kids",
                        "size": "medium"
                      },
                      {
                        "type": "kids",
                        "size": "medium"
                      },
                      {
                        "type": "living",
                        "size": "large"
                      },
                      {
                        "type": "kitchen",
                        "size": "large"
                      },
                      {
                        "type": "office",
                        "size": "medium"
                      },
                      {
                        "type": "bathroom",
                        "size": "medium"
                      },
                      {
                        "type": "garage",
                        "size": "medium"
                      }
                    ],
                    "people": 4,
                    "density": "collector",
                    "distanceKm": 250,
                    "floorsWithoutElevator": 1,
                    "moveDate": "2027-03-15",
                    "moverHourlyRate": 45,
                    "truckHourlyRate": 30,
                    "currency": "EUR"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MovingBoxPlannerResult"
                }
              }
            }
          },
          "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"
                }
              }
            }
          }
        }
      },
      "MovingBoxPlannerInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "rooms": {
            "title": "Rooms to pack",
            "type": "array",
            "minItems": 1,
            "maxItems": 20,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "type": {
                  "title": "Room",
                  "type": "string",
                  "enum": [
                    "bedroom",
                    "living",
                    "kitchen",
                    "bathroom",
                    "office",
                    "kids",
                    "garage",
                    "storage"
                  ]
                },
                "size": {
                  "title": "Size / fullness",
                  "type": "string",
                  "enum": [
                    "small",
                    "medium",
                    "large"
                  ],
                  "default": "medium"
                }
              },
              "required": [
                "type"
              ]
            }
          },
          "people": {
            "title": "People in household",
            "type": "integer",
            "minimum": 1,
            "maximum": 12,
            "default": 2
          },
          "density": {
            "title": "How much stuff?",
            "type": "string",
            "enum": [
              "minimalist",
              "average",
              "collector"
            ],
            "default": "average"
          },
          "distanceKm": {
            "title": "Driving distance",
            "type": "number",
            "minimum": 0,
            "maximum": 5000,
            "default": 20,
            "x-unit": "km"
          },
          "floorsWithoutElevator": {
            "title": "Floors without elevator (old + new home)",
            "type": "integer",
            "minimum": 0,
            "maximum": 20,
            "default": 0
          },
          "moveDate": {
            "title": "Moving day",
            "type": "string",
            "format": "date"
          },
          "moverHourlyRate": {
            "title": "Mover rate per person-hour",
            "description": "Optional; cost is only calculated if given.",
            "type": "number",
            "minimum": 0,
            "maximum": 1000
          },
          "truckHourlyRate": {
            "title": "Truck rate per hour",
            "description": "Optional.",
            "type": "number",
            "minimum": 0,
            "maximum": 1000
          },
          "currency": {
            "title": "Currency",
            "type": "string",
            "maxLength": 8,
            "default": "EUR"
          }
        },
        "required": [
          "rooms",
          "moveDate"
        ]
      },
      "MovingBoxPlannerResult": {
        "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",
            "additionalProperties": false,
            "required": [
              "boxes",
              "totalBoxes",
              "supplies",
              "volume",
              "truck",
              "crew",
              "cost",
              "timeline"
            ],
            "properties": {
              "boxes": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "small",
                  "medium",
                  "large",
                  "wardrobe"
                ],
                "properties": {
                  "small": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "medium": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "large": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "wardrobe": {
                    "type": "integer",
                    "minimum": 0
                  }
                }
              },
              "totalBoxes": {
                "type": "integer",
                "minimum": 0
              },
              "perRoom": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "room",
                    "size",
                    "small",
                    "medium",
                    "large",
                    "wardrobe",
                    "furnitureM3"
                  ],
                  "properties": {
                    "room": {
                      "type": "string"
                    },
                    "size": {
                      "type": "string"
                    },
                    "small": {
                      "type": "integer"
                    },
                    "medium": {
                      "type": "integer"
                    },
                    "large": {
                      "type": "integer"
                    },
                    "wardrobe": {
                      "type": "integer"
                    },
                    "furnitureM3": {
                      "type": "number"
                    }
                  }
                }
              },
              "supplies": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "tapeRolls",
                  "packingPaperKg",
                  "bubbleWrapM",
                  "markers"
                ],
                "properties": {
                  "tapeRolls": {
                    "type": "integer"
                  },
                  "packingPaperKg": {
                    "type": "number"
                  },
                  "bubbleWrapM": {
                    "type": "number"
                  },
                  "markers": {
                    "type": "integer"
                  }
                }
              },
              "volume": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "boxesM3",
                  "furnitureM3",
                  "loadM3"
                ],
                "properties": {
                  "boxesM3": {
                    "type": "number"
                  },
                  "furnitureM3": {
                    "type": "number"
                  },
                  "loadM3": {
                    "type": "number",
                    "description": "(boxes + furniture) × 1.15 stowage factor."
                  }
                }
              },
              "truck": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "vehicle",
                  "vehicleM3",
                  "trips"
                ],
                "properties": {
                  "vehicle": {
                    "type": "string"
                  },
                  "vehicleM3": {
                    "type": "number"
                  },
                  "trips": {
                    "type": "integer",
                    "minimum": 1
                  }
                }
              },
              "crew": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "people",
                  "loadingHours",
                  "drivingHours",
                  "totalHours"
                ],
                "properties": {
                  "people": {
                    "type": "integer"
                  },
                  "loadingHours": {
                    "type": "number"
                  },
                  "drivingHours": {
                    "type": "number"
                  },
                  "totalHours": {
                    "type": "number"
                  }
                }
              },
              "cost": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "calculated",
                  "labour",
                  "truck",
                  "total",
                  "currency"
                ],
                "properties": {
                  "calculated": {
                    "type": "boolean"
                  },
                  "labour": {
                    "type": "number"
                  },
                  "truck": {
                    "type": "number"
                  },
                  "total": {
                    "type": "number"
                  },
                  "currency": {
                    "type": "string"
                  }
                }
              },
              "timeline": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "date",
                    "task"
                  ],
                  "properties": {
                    "date": {
                      "type": "string",
                      "format": "date"
                    },
                    "task": {
                      "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"
              }
            }
          }
        }
      }
    }
  }
}